[
  {
    "path": ".github/workflows/appimage.yml",
    "content": "name: Build and publish AppImages\n\non:\n  push:\n    tags:\n      - \"v*\"\n\nenv:\n  BASE_OS: ubuntu\n  APT_PUBKEY: 871920D1991BC93C\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: true\n      matrix:\n        platform:\n          - linux/amd64\n          - linux/arm64\n        codename:\n          - jammy\n          - lunar\n          - mantic\n\n    steps:\n      - uses: actions/checkout@v4\n      - uses: rlespinasse/github-slug-action@v4\n\n      - name: Setup qemu for docker\n        uses: docker/setup-qemu-action@v2\n        if: matrix.platform != 'linux/amd64'\n\n      - name: Setup buildx for docker\n        uses: docker/setup-buildx-action@v2\n\n      - name: Compile in docker\n        uses: docker/build-push-action@v4\n        with:\n          platforms: ${{ matrix.platform }}\n          outputs: build\n          build-args: |\n            BASE_OS\n            BASE_CODENAME=${{ matrix.codename }}\n\n      - name: Prepare environment to build AppImage\n        env:\n          TARGET_PLATFORM: ${{ matrix.platform }}\n        shell: bash\n        run: |\n          set -eua\n          if [ -r build/.build-metadata.env ]; then\n            . build/.build-metadata.env\n            rm build/.build-metadata.env\n          fi\n          APPIMAGE_SOURCE=build\n          APPIMAGE_VERSION=\"${GITHUB_REF_SLUG}-${{ matrix.codename }}\"\n          APPIMAGE_APT_ARCH=\"${TARGETARCH}\"\n          APPIMAGE_APT_DISTRO=\"${{ matrix.codename }}\"\n          APPIMAGE_APT_PUBKEY=\"${APT_PUBKEY}\"\n          APPIMAGE_ARCH=\"${TARGETMACHINE}\"\n          printenv | grep ^APPIMAGE_ >>\"${GITHUB_ENV}\"\n\n      - name: Build AppImage\n        uses: AppImageCrafters/build-appimage@v1.3\n\n      - name: Upload artifacts\n        uses: actions/upload-artifact@v3\n        with:\n          name: appimages\n          path: |\n            ./*.AppImage\n            ./*.AppImage.zsync\n          if-no-files-found: error\n\n  release:\n    if: startsWith(github.ref, 'refs/tags/')\n    runs-on: ubuntu-latest\n\n    needs:\n      - build\n\n    permissions:\n      contents: write\n\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@v3\n        with:\n          name: appimages\n          path: assets\n      - name: Create checksum for release assets\n        shell: bash\n        run: |\n          algo=\"${SHA_ALGORITHM:-256}\"\n          find assets -type f | while read -r asset; do\n            shasum --binary --algorithm \"${algo}\" \"${asset}\" >\"${asset}.sha${algo}\"\n          done\n      - name: Upload artifacts to GitHub release\n        uses: softprops/action-gh-release@v1\n        with:\n          files: assets/*\n"
  },
  {
    "path": ".github/workflows/scc-linux.yml",
    "content": "name: SCC Linux CI\n\non:\n  push:\n    branches:\n    - main\n    - master\n    - python3\n  pull_request:\n    branches:\n    - main\n    - master\n    - python3\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-python@v4\n      with:\n        python-version: '3.11' \n    # Install dependencies\n    - run: pip install pytest vdf\n    # Build\n    - run: python setup.py build \n    # Test\n    - run: python -m pytest tests\n"
  },
  {
    "path": "ADDITIONAL-LICENSES",
    "content": "All images in images/ directory, profile files in default_profiles/\nand profile_examples/ and menu files in default_menus/ directories\nare licensed under CC0 license - https://creativecommons.org/publicdomain/zero/1.0/\n\nImages in images/menu-icons subdirectories are licensed as described\nin 'LICENSES' files in their respective subdirectories.\n\nlib/enum.py is licensed under BSD license, as described in that file.\nlib/usb1.py and lib/libusb1.py are licensed under LGPL 2.1, as described in those files.\nlib/jsonencoder.py is licensed under PSFL, https://www.python.org/download/releases/2.7/license/\nscripts/gamecontrollerdb.txt is taken from Simple DirectMedia Layer library and licensed under zlib license.\nlib/xwrappers.py and lib/vdf.py are part of SC-Controller, licensed under GPL2 as rest of the project.\n\nEverything else is licensed under GPL2, as described in 'LICENCE' file.\n"
  },
  {
    "path": "AppImageBuilder.yml",
    "content": "version: 1\n\nscript:\n- |\n  if [ \"{{APPIMAGE_SOURCE}}\" != \"${TARGET_APPDIR}\" ]; then\n    mv \"{{APPIMAGE_SOURCE}}\" \"${TARGET_APPDIR}\"\n  fi\n  # Manual installation of squashfs-tools is required\n  # until https://github.com/AppImageCrafters/build-appimage/issues/5 is fixed\n  if ! command -v mksquashfs >/dev/null; then\n    apt-get update && apt-get install -y --no-install-recommends squashfs-tools\n  fi\n\nAppDir:\n  app_info:\n    id: org.ryochan7.sc-controller\n    name: sc-controller\n    version: \"{{APPIMAGE_VERSION}}\"\n    icon: sc-controller\n    exec: usr/bin/python3\n    exec_args: -c \"import os, sys; os.execvp('$APPDIR/usr/bin/scc', ['$APPDIR/usr/bin/scc'] + (sys.argv[1:] if len(sys.argv) > 1 else ['gui']))\" $@\n\n  after_bundle: |\n    set -eu\n\n    # appimage-builder expects .desktop file to start with appinfo-id\n    desktop=\"$(find \"${TARGET_APPDIR}/usr\" -name sc-controller.desktop)\"\n    sed -i \"s:Exec=.*:Exec=./usr/bin/scc gui:g\" \"${desktop}\"\n    ln -sr \"${desktop}\" \"${TARGET_APPDIR}/usr/share/applications/org.ryochan7.sc-controller.desktop\"\n\n    # appimage-builder expects utf-8 encoding when patching shebangs,\n    # but pygettext3 has iso-8859-1 encoding\n    find \"${TARGET_APPDIR}/usr/bin\" -name 'pygettext*' | while read -r file; do\n      encoding=\"ISO-8859-1\"\n      if file -bi \"${file}\" | grep -iq \"${encoding}\"; then\n        <\"${file}\" iconv -f \"${encoding}\" -t utf-8 -o \"${file}\"\n        sed -i -E '1,2 s|^(\\s*#.*coding[=:]\\s*)([[:alnum:].-]+)|\\1utf-8|g' \"${file}\"\n      fi\n    done\n\n  apt:\n    arch:\n      - \"{{APPIMAGE_APT_ARCH}}\"\n    sources:\n      - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ {{APPIMAGE_APT_DISTRO}} main universe\n        key_url: http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x{{APPIMAGE_APT_PUBKEY}}\n      - sourceline: deb [arch=amd64] http://archive.ubuntu.com/ubuntu/ {{APPIMAGE_APT_DISTRO}}-updates main universe\n      - sourceline: deb [arch=amd64] http://security.ubuntu.com/ubuntu/ {{APPIMAGE_APT_DISTRO}}-security main universe\n      - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ {{APPIMAGE_APT_DISTRO}} main universe\n      - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ {{APPIMAGE_APT_DISTRO}}-updates main universe\n      - sourceline: deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports/ {{APPIMAGE_APT_DISTRO}}-security main universe\n\n    include:\n      - gir1.2-rsvg-2.0\n      - libbluetooth3\n      - librsvg2-common\n      - python3-evdev\n      - python3-gi-cairo\n      - python3-pylibacl\n      - python3-vdf\n      - binutils         # required for detection of bluetooth library\n      - coreutils        # provides /usr/bin/env\n      - shared-mime-info # required for gui if host provides no MIME info, e.g. when XDG_DATA_DIRS is missing\n    exclude:\n      - gcc*            # development\n      - libgcc*         # development\n      - libstdc*        # development\n      - libtirpc*       # development\n      - libuuid*        # development\n      - libattr*        # filesystem\n      - libblkid*       # filesystem\n      - libmount*       # filesystem\n      - libbz*          # codec\n      - libjpeg*        # codec\n      - liblzma*        # codec\n      - libtiff*        # codec\n      - libxml*         # codec\n      - libwebp*        # codec\n      - media-types     # codec\n      - libpixman*      # X\n      - libxext*        # X\n      - libxrender*     # X\n      - libfontconfig*  # fonts\n      - libfreetype*    # fonts\n      - libfribidi*     # i18n\n      - libicu*         # i18n\n      - libgmp*         # arithmetics\n      - libmpdec*       # arithmetics\n      - libncurses*     # terminal\n      - libreadline*    # terminal\n      - readline*       # terminal\n      - libssl*         # security\n      - \"*crypt*\"       # security\n      - \"*krb*\"         # security\n      - libdb*          # database\n      - \"*sqlite*\"      # database\n      - libnsl*         # network\n\n\n  files:\n    exclude:\n      - usr/bin/*gold*              # alternative for ld\n      - usr/bin/*gp-display-html*   # since Ubuntu Lunatic, requires perl\n      - usr/lib/*/gconv             # unicode\n      - usr/lib/*/gdk-pixbuf-2.0/*/loaders/libpixbufloader-[!s]*.so # only svg is required\n      - usr/lib/*/glib-2.0\n      - usr/lib/python*/cgi.py\n      - usr/lib/python*/email\n      - usr/lib/python*/test\n      - usr/lib/python*/unittest\n      - usr/share/doc\n      - usr/share/glib-2.0\n      - usr/share/gtk-doc\n      - usr/share/icu\n      - usr/share/locale\n      - usr/share/man\n      - usr/share/python3/runtime.d\n      - usr/share/thumbnailers\n\n  runtime:\n    env:\n      # `usr/lib/python3.*/site-packages` is required in $PYTHONPATH,\n      # but the python version and hence the actual location is unknown here.\n      # Fortunately the site-packages directory is on the $PATH, so we add $PATH instead.\n      # It must precede an existing $PYTHONPATH to work.\n      PYTHONPATH: \"${APPDIR}/usr/lib/python3/dist-packages:${PATH}:${PYTHONPATH}\"\n      SCC_SHARED: \"${APPDIR}/usr/share/scc\"\n\nAppImage:\n  arch: \"{{APPIMAGE_ARCH}}\"\n  update-information: \"gh-releases-zsync|Ryochan7|sc-controller|latest|sc-controller-*-{{APPIMAGE_APT_DISTRO}}-{{APPIMAGE_ARCH}}.AppImage.zsync\"\n"
  },
  {
    "path": "Dockerfile",
    "content": "ARG BASE_OS=ubuntu\nARG BASE_CODENAME=jammy\nFROM $BASE_OS:$BASE_CODENAME AS build-stage\n\n# Download build dependencies\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n      gcc librsvg2-bin linux-headers-generic python3-dev python3-setuptools\n\n# Prepare working directory and target\nCOPY . /work\nWORKDIR /work\nARG TARGET=/build/usr\n\n# Build and install\nRUN python3 setup.py build --executable \"/usr/bin/env python3\" && \\\n    python3 setup.py install --single-version-externally-managed --home \"${TARGET}\" --record /dev/null\n\n# Provide input-event-codes.h as fallback for runtime systems without linux headers\nRUN cp -a \\\n      \"$(find /usr -type f -name input-event-codes.h -print -quit)\" \\\n      \"$(find \"${TARGET}\" -type f -name uinput.py -printf '%h\\n' -quit)\"\n\n# Create short name symlinks for static libraries\nRUN suffix=\".cpython-*-$(uname -m)-linux-gnu.so\" && \\\n    find \"${TARGET}\" -type f -path \"*/site-packages/*${suffix}\" \\\n    | while read -r path; do ln -sfr \"${path}\" \"${path%${suffix}}.so\"; done\n\n# Put AppStream metadata to required location according to https://wiki.debian.org/AppStream/Guidelines\nRUN metainfo=/build/usr/share/metainfo && \\\n    mkdir -p \"${metainfo}\" && \\\n    cp -a scripts/sc-controller.appdata.xml \"${metainfo}\"\n\n# Convert icon to png format (required for icons in .desktop file)\nRUN iconpath=\"${TARGET}/share/icons/hicolor/512x512/apps\" && \\\n    mkdir -p \"${iconpath}\" && \\\n    rsvg-convert --background-color none -o \"${iconpath}/sc-controller.png\" images/sc-controller.svg\n\n# Store build metadata\nARG TARGETOS TARGETARCH TARGETVARIANT\nRUN export \"TARGETMACHINE=$(uname -m)\" && printenv | grep ^TARGET >>/build/.build-metadata.env\n\n# Keep only files required for runtime\nFROM scratch AS export-stage\nCOPY --from=build-stage /build /\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.md",
    "content": "# SC Controller [![Build Status](https://travis-ci.org/kozec/sc-controller.svg?branch=master)](https://travis-ci.org/kozec/sc-controller)\n\n\nUser-mode driver, mapper and GTK3 based GUI for Steam Controller, DS4 and similar controllers.\n\n[![screenshot1](docs/screenshot1-tn.png?raw=true)](docs/screenshot1.png?raw=true)\n[![screenshot2](docs/screenshot2-tn.png?raw=true)](docs/screenshot2.png?raw=true)\n[![screenshot3](docs/screenshot3-tn.png?raw=true)](docs/screenshot3.png?raw=true)\n[![screenshot3](docs/screenshot4-tn.png?raw=true)](docs/screenshot4.png?raw=true)\n\n## Features\n- Allows to setup, configure and use Steam Controller(s) without ever launching Steam\n- Supports profiles switchable in GUI or with controller button\n- Stick, Pads and Gyroscope input\n- Haptic Feedback and in-game Rumble support\n- OSD, Menus, On-Screen Keyboard for desktop *and* in games.\n- Automatic profile switching based on active window.\n- Macros, button cycling, rapid fire, modeshift, mouse regions...\n- Emulates Xbox360 controller, mouse, trackball and keyboard.\n\nBased on [Standalone Steam Controller Driver](https://github.com/ynsta/steamcontroller) by [Ynsta](https://github.com/ynsta).\n\n## Like what I'm doing?\n\n[![Help me become filthy rich on Liberapay](https://img.shields.io/badge/Help%20me%20become%20filthy%20rich%20on-Liberapay-yellow.svg)](https://liberapay.com/kozec) <sup>or</sup> [![donate anything with PayPal](https://img.shields.io/badge/donate_anything_with-Paypal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=77DQD3L9K8RPU&lc=SK&item_name=kozec&item_number=scc&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted)\n\n## Packages\n\n - **Ubuntu (deb-based distros):** Found in [openSUSE Build Service](https://software.opensuse.org/download.html?project=home%3Akozec&package=sc-controller).\n - **Fedora, SUSE (rpm-based distros):** Found in [openSUSE Build Service](https://software.opensuse.org/download.html?project=home%3Akozec&package=sc-controller).\n - **Arch, Manjaro (arch-based distros):** Found in [AUR](https://aur.archlinux.org/packages/sc-controller-git/)\n - **Solus:** Search for `sc-controller` in Software Center or run `sudo eopkg it sc-controller` from a terminal.\n - **Exherbo:** Found in [hardware](https://git.exherbo.org/summer/packages/input/sc-controller)\n - **Void Linux:** Run `xbps-install -S sc-controller` in a terminal.\n\n\n## Building the package by yourself\n\n### Dependencies\n  - python 3, GTK 3.22 or newer and [PyGObject](https://live.gnome.org/PyGObject)\n  - [python-gi-cairo](https://packages.debian.org/sid/python-gi-cairo) and [gir1.2-rsvg-2.0](https://packages.debian.org/sid/gir1.2-rsvg-2.0) on debian based distros (included in PyGObject elsewhere)\n  - [setuptools](https://pypi.python.org/pypi/setuptools)\n  - [python-pylibacl](http://pylibacl.k1024.org/) is recommended\n  - [python-evdev](https://python-evdev.readthedocs.io/en/latest/) is strongly recommended\n  - [python-vdf](https://pypi.org/project/vdf/)\n  - [gtk-layer-shell](https://github.com/wmww/gtk-layer-shell) (Wayland only)\n\n### Installing\n  - Download and extract  [latest release](https://github.com/kozec/sc-controller/releases/latest)\n  - `python3 setup.py build`\n  - `python3 setup.py install`\n\n\n## Running with non distro-specific package          \n  - Download and extract [latest release](https://github.com/kozec/sc-controller/releases/latest)\n  - Navigate to extracted directory and execute `./run.sh`\n"
  },
  {
    "path": "TODO.md",
    "content": "List of (possibly) planned features in no particular order:\n\n- Multiple on-screen menus (and possibly keyboards) when using multiple controllers\n- Injecting emulated xbox controller into wine\n\nHard stuff:\n- Injecting emulated xbox controller into PlayOnLinux\n\nVery hard stuff:\n- Visual feedback in binding editor ( [what this guy says](https://www.reddit.com/r/linux_gaming/comments/5pcdmr/sc_controller_use_steam_controller_without_steam/dcqpvf4/) )\n\n**Done** stuff:\n- Multicontroller support\n- Configurable gamepad type (e.g. 4 axes and 16 buttons)\n- Steam Profile import\n- Radial Menu for the Joystick/Trackpad\n- Copy & paste\n- Cycling Buttons\n- Process monitor (or active window monitor) with switch\n- Mouse regions\n- Touch-Menu\n- Menu in OSD\n- OSD\n- double click\n- on-screen keyboard\n- Spining mouse wheel rotation\n- Haptic feedback support\n- Gyroscope input\n- Gamepad button as modifier (modeshift)\n- Macros\n- Turbo\n- Trigger settings\n- DPAD that acts only when clicked\n- 8-way DPAD\n- Selector for media keys"
  },
  {
    "path": "appimage-build.sh",
    "content": "#!/bin/bash\nAPP=\"sc-controller\"\nEXEC=\"scc\"\nLIB=\"lib\"\n\nEVDEV_VERSION=0.7.0\n[ x\"$BUILD_APPDIR\" == \"x\" ] && BUILD_APPDIR=$(pwd)/appimage\nPYTHON_VERSION=$(python3 -c 'import sys; version=sys.version_info[:3]; print(\"{0}.{1}\".format(*version))')\nSITE_PACKAGES_PATH=$(python3 -c \"import os,sys; print([p for p in sys.path if p.endswith('site-packages') and sys.prefix in p][0])\")\n\nif [ -z ${SITE_PACKAGES_PATH} ]; then\n  echo \"Could not determine global site-packages path. Exiting\";\n  exit 1;\nfi\n\nfunction download_dep() {\n\tNAME=$1\n\tURL=$2\n\tif [ -e ../../${NAME}.obstargz ] ; then\n\t\t# Special case for OBS\n\t\tcp ../../${NAME}.obstargz /tmp/${NAME}.tar.gz\n\telif [ -e ${NAME}.tar.gz ] ; then\n\t\tcp ${NAME}.tar.gz /tmp/${NAME}.tar.gz\n\telif [ -e /tmp/${NAME}.tar.gz ] ; then\n\t\techo \"/tmp/${NAME}.tar.gz already downloaded\"\n\telse\n\t\twget -c \"${URL}\" -O /tmp/${NAME}.tar.gz\n\tfi\n}\n\nfunction build_dep() {\n\tNAME=\"$1\"\n\tmkdir -p /tmp/${NAME}\n\tpushd /tmp/${NAME}\n\ttar --extract --strip-components=1 -f /tmp/${NAME}.tar.gz\n\tPYTHONPATH=${BUILD_APPDIR}/usr/lib/python${PYTHON_VERSION}/site-packages python3 \\\n\t\tsetup.py install --optimize=1 \\\n\t\t--prefix=\"/usr/\" --root=\"${BUILD_APPDIR}\"\n\tmkdir -p \"${BUILD_APPDIR}/usr/lib/python${PYTHON_VERSION}/site-packages/\"\n\tpython3 setup.py install --prefix=\"/usr/\" --root=\"${BUILD_APPDIR}\"\n\tpopd\n}\n\nfunction unpack_dep() {\n\tNAME=\"$1\"\n\tpushd ${BUILD_APPDIR}\n\ttar --extract --exclude=\"usr/include**\" --exclude=\"usr/lib/pkgconfig**\" \\\n\t\t\t--exclude=\"usr/lib/python2.7**\" -f /tmp/${NAME}.tar.gz\n\tpopd\n}\n\nset -ex\t\t# display commands, terminate after 1st failure\n\n# Download deps\ndownload_dep \"python-evdev-0.7.0\" \"https://github.com/gvalkov/python-evdev/archive/v0.7.0.tar.gz\"\ndownload_dep \"pylibacl-0.6.0\" \"https://github.com/iustin/pylibacl/releases/download/v0.6.0/pylibacl-0.6.0.tar.gz\"\ndownload_dep \"python-gobject-3.36.1\" \"https://archive.archlinux.org/packages/p/python-gobject/python-gobject-3.36.1-1-x86_64.pkg.tar.zst\"\ndownload_dep \"python-vdf-3.4\" \"https://github.com/ValvePython/vdf/archive/v3.4.tar.gz\"\ndownload_dep \"libpng-1.6.34\" \"https://archive.archlinux.org/packages/l/libpng/libpng-1.6.34-2-x86_64.pkg.tar.xz\"\ndownload_dep \"gdk-pixbuf-2.36.9\" \"https://archive.archlinux.org/packages/g/gdk-pixbuf2/gdk-pixbuf2-2.36.9-1-x86_64.pkg.tar.xz\"\ndownload_dep \"libcroco-0.6.13\" \"https://archive.archlinux.org/packages/l/libcroco/libcroco-0.6.13-1-x86_64.pkg.tar.xz\"\ndownload_dep \"libxml2-2.9.10\" \"https://archive.archlinux.org/packages/l/libxml2/libxml2-2.9.10-2-x86_64.pkg.tar.zst\"\ndownload_dep \"librsvg-2.48.7\" \"https://archive.archlinux.org/packages/l/librsvg/librsvg-2%3A2.48.7-1-x86_64.pkg.tar.zst\"\ndownload_dep \"icu-67.1\" \"https://archive.archlinux.org/packages/i/icu/icu-67.1-1-x86_64.pkg.tar.zst\"\ndownload_dep \"zlib-1:1.2.12\" \"https://archive.archlinux.org/packages/z/zlib/zlib-1%3A1.2.12-2-x86_64.pkg.tar.zst\"\ndownload_dep \"libffi-3.4.3\" \"https://archive.archlinux.org/packages/l/libffi/libffi-3.4.3-1-x86_64.pkg.tar.zst\"\n\n# Prepare & build deps\nexport PYTHONPATH=${BUILD_APPDIR}/usr/lib/python${PYTHON_VERSION}/site-packages/\nmkdir -p \"$PYTHONPATH\"\nif [[ $(grep ID_LIKE /etc/os-release) == *\"suse\"* ]] ; then\n\t# Special handling for OBS\n\tln -s lib64 ${BUILD_APPDIR}/usr/lib\n\texport PYTHONPATH=\"$PYTHONPATH\":${BUILD_APPDIR}/usr/lib64/python${PYTHON_VERSION}/site-packages/\n\tLIB=lib64\nfi\n\nbuild_dep \"python-evdev-0.7.0\"\nbuild_dep \"pylibacl-0.6.0\"\nbuild_dep \"python-vdf-3.4\"\nunpack_dep \"python-gobject-3.36.1\"\nunpack_dep \"libpng-1.6.34\"\nunpack_dep \"gdk-pixbuf-2.36.9\"\nunpack_dep \"libcroco-0.6.13\"\nunpack_dep \"libxml2-2.9.10\"\nunpack_dep \"librsvg-2.48.7\"\nunpack_dep \"icu-67.1\"\nunpack_dep \"zlib-1:1.2.12\"\nunpack_dep \"libffi-3.4.3\"\n\n# Remove uneeded files\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-ani.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-bmp.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-gif.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-icns.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-ico.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-jasper.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-jpeg.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-qtif.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-tga.so\"\nrm -f \"${BUILD_APPDIR}/usr/${LIB}/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-tiff.so\"\nrm -R \"${BUILD_APPDIR}/usr/lib/cmake\"\nrm -R \"${BUILD_APPDIR}/usr/share/doc\"\nrm -R \"${BUILD_APPDIR}/usr/share/gtk-doc\"\nrm -R \"${BUILD_APPDIR}/usr/share/locale\"\nrm -R \"${BUILD_APPDIR}/usr/share/man\"\nrm -R \"${BUILD_APPDIR}/usr/share/thumbnailers\"\nrm -R \"${BUILD_APPDIR}/usr/share/vala\"\nrm -R \"${BUILD_APPDIR}/usr/share/icu\"\n\n# Build important part. Need executable flag to place custom interpreter line.\n# Setuptools overrrides original #! line in scripts with /usr/bin/python.\n# Ubuntu 22.04 LTS does not provide an executable at /usr/bin/python\n# by default.\npython3 setup.py build --executable \"/usr/bin/env python3\"\n\n# Need to use single-version-externally-managed due to setuptools behavior\npython3 setup.py install --single-version-externally-managed --prefix ${BUILD_APPDIR}/usr --record /dev/null\n\n# Move udev stuff\nmv ${BUILD_APPDIR}/usr/lib/udev/rules.d/69-${APP}.rules ${BUILD_APPDIR}/\nrmdir ${BUILD_APPDIR}/usr/lib/udev/rules.d/\nrmdir ${BUILD_APPDIR}/usr/lib/udev/\nmkdir -p ${BUILD_APPDIR}/usr/${LIB}/python${PYTHON_VERSION}/site-packages/scc/\ncp \"/usr/include/linux/input-event-codes.h\" ${BUILD_APPDIR}/usr/${LIB}/python${PYTHON_VERSION}/site-packages/scc/\n\n# Move & patch desktop file\nmv ${BUILD_APPDIR}/usr/share/applications/${APP}.desktop ${BUILD_APPDIR}/\nsed -i \"s/Icon=.*/Icon=${APP}/g\" ${BUILD_APPDIR}/${APP}.desktop\nsed -i \"s/Exec=.*/Exec=.\\/usr\\/bin\\/scc gui/g\" ${BUILD_APPDIR}/${APP}.desktop\n\n# Convert icon\nconvert -background none ${BUILD_APPDIR}/usr/share/pixmaps/${APP}.svg ${BUILD_APPDIR}/${APP}.png\n\n# Copy appdata.xml\nmkdir -p ${BUILD_APPDIR}/usr/share/metainfo/\ncp scripts/${APP}.appdata.xml ${BUILD_APPDIR}/usr/share/metainfo/${APP}.appdata.xml\n\n# Make symlinks\nln -sfr ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libcemuhook.cpython-310-x86_64-linux-gnu.so ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libcemuhook.so\nln -sfr ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libhiddrv.cpython-310-x86_64-linux-gnu.so ${BUILD_APPDIR}${SITE_PACKAGES_PATH}//libhiddrv.so\nln -sfr ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libremotepad.cpython-310-x86_64-linux-gnu.so ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libremotepad.so\nln -sfr ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libsc_by_bt.cpython-310-x86_64-linux-gnu.so ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libsc_by_bt.so\nln -sfr ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libuinput.cpython-310-x86_64-linux-gnu.so ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/libuinput.so\nln -sfr ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/posix1e.cpython-310-x86_64-linux-gnu.so ${BUILD_APPDIR}${SITE_PACKAGES_PATH}/posix1e.so\n\n# Copy AppRun script\ncp scripts/appimage-AppRun.sh ${BUILD_APPDIR}/AppRun\nchmod +x ${BUILD_APPDIR}/AppRun\n\necho \"Run appimagetool -n ${BUILD_APPDIR} to finish prepared appimage\"\n"
  },
  {
    "path": "daemon.sh",
    "content": "#!/bin/bash\n\n# Ensure correct cwd\ncd \"$(dirname \"$0\")\"\n\n# Set PATH\nSCRIPTS=\"$(pwd)/scripts\"\nexport PATH=\"$SCRIPTS\":\"$PATH\"\nexport PYTHONPATH=\".\":\"$PYTHONPATH\"\nexport SCC_SHARED=\"$(pwd)\"\n\nif [ x\"$1\" == x\"lldb\" ] ; then\n\tshift\n\tlldb python3 -- 'scripts/scc-daemon' debug $@\nelse\n\tpython3 'scripts/scc-daemon' $@\nfi\n"
  },
  {
    "path": "default_menus/Default.menu",
    "content": "[{\n    \"separator\": true,\n    \"name\": \"Recent profiles\"\n}, {\n    \"generator\": \"recent\",\n    \"rows\": 3\n}, {\n    \"submenu\": \"Profiles.menu\",\n    \"icon\": \"system/profiles\",\n    \"name\": \"All Profiles\"\n}, {\n    \"separator\": true,\n    \"name\": \"Options\"\n}, {\n    \"submenu\": \".autoswitch.menu\",\n    \"icon\": \"system/autoswitch\",\n    \"name\": \"Autoswitch Options\"\n}, {\n    \"action\": \"turnoff()\", \n    \"id\": \"item4\", \n    \"icon\": \"system/turn-off\",\n    \"name\": \"Turn Controller OFF\", \n    \"osd\": true\n}, {\n    \"action\": \"keyboard()\", \n    \"id\": \"item5\", \n    \"icon\": \"system/keyboard\",\n    \"name\": \"Display Keyboard\"\n}]\n"
  },
  {
    "path": "default_menus/Profiles.menu",
    "content": "[{\n    \"separator\": true,\n    \"name\": \"All profiles\"\n}, {\n    \"generator\" : \"profiles\"\n}]\n"
  },
  {
    "path": "default_profiles/Desktop.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.KEY_ENTER)\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_ESC)\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.KEY_BACKSPACE)\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"CPADPRESS\": {\n            \"action\": \"button(Keys.BTN_LEFT)\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL)\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.KEY_LEFTALT)\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_LEFT)\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.KEY_SPACE)\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_TAB)\"\n        }\n    }, \n    \"cpad\": {\n        \"action\": \"mouse()\"\n    }, \n    \"gyro\": {},\n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"feedback(LEFT, 4096, 16, ball(XY(mouse(Rels.REL_HWHEEL, 1.0), mouse(Rels.REL_WHEEL, 1.0))))\"\n    }, \n    \"pad_right\": {\n        \"action\": \"smooth(8, 0.78, 2.0, feedback(RIGHT, 256, ball(mouse())))\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"button(Keys.BTN_RIGHT)\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"button(Keys.BTN_LEFT)\"\n    }, \n    \"version\": 1.4\n}\n"
  },
  {
    "path": "default_profiles/XBox Controller with High Precision Camera.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.BTN_EAST)\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.BTN_SELECT)\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.BTN_TL)\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.BTN_TR)\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.BTN_NORTH)\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBR)\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.BTN_START)\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"button(Keys.BTN_THUMBL)\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.BTN_NORTH)\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.BTN_WEST)\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"click(dpad(hatup(Axes.ABS_HAT0Y), hatdown(Axes.ABS_HAT0Y), hatleft(Axes.ABS_HAT0X), hatright(Axes.ABS_HAT0X)))\"\n    }, \n    \"pad_right\": {\n        \"action\": \"smooth(8, 0.78, feedback(RIGHT, 256, ball(mouse())))\"\n    }, \n    \"stick\": {\n        \"action\": \"sens(1.2, 1.2, XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y)))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"axis(Axes.ABS_Z)\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"axis(Axes.ABS_RZ)\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "default_profiles/XBox Controller.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.BTN_EAST)\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.BTN_SELECT)\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.BTN_TL)\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.BTN_TR)\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.BTN_NORTH)\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBR)\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.BTN_START)\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"button(Keys.BTN_THUMBL)\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.BTN_NORTH)\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.BTN_WEST)\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {\n    \t\"action\": \"cemuhook\"\n    }, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"click(dpad(hatup(Axes.ABS_HAT0Y), hatdown(Axes.ABS_HAT0Y), hatleft(Axes.ABS_HAT0X), hatright(Axes.ABS_HAT0X)))\"\n    }, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, XY(axis(Axes.ABS_RX), raxis(Axes.ABS_RY)))\"\n    }, \n    \"stick\": {\n        \"action\": \"sens(1.2, 1.2, XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y)))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"axis(Axes.ABS_Z)\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"axis(Axes.ABS_RZ)\"\n    }, \n    \"version\": 1.4\n}\n"
  },
  {
    "path": "docs/actions.md",
    "content": "### In this document\n- [Custom Action page](#examples1)\n- [List of all known actions](#actions)\n- [Macros and operators](#macros)\n- [Profile file examples](#examples2)\n\n# <a name=\"examples1\"></a>Custom Action page examples\n\n- To do two or more things at once, type `action() and action()`\n- To do two or more things in sequence, type `action() ; action()`\n- Typing second action on new line is same thing as using `and`\n\n#### Press button repeadedly, rapid fire mode\n`repeat(button(BTN_A))` (see [repeat](#repeat), [button](#button))\n\n#### Press Alt+F4\n`button(KEY_LEFTALT) and button(KEY_F4)`\n\n#### Press multiple buttons in sequence\n`button(BTN_A) ; button(BTN_X); button(BTN_B)`\n\n#### Press button and hold it for set delay\n`press(BTN_A) ; sleep(0.5); release(BTN_B)` (see [press](#press), [sleep](#sleep), [release](#release))\n\n\n# <a name=\"actions\"></a>Actions\n\n#### <a name=\"button\"></a> button(button1 [, button2 = None ])\n- For button, simply maps real button to emulated\n- For stick or pad, 'button1' is pressed when stick or finger on pad is moved\n  to up or left and 'button2' when to down or right.\n  Using [dpad](#dpad) may be better for such situations.\n- For trigger, when trigger is pressed, but until it clicks, 'button2' is\n  pressed. When trigger clicks 'button2' is released and replaced by 'button1'.\n  If only 'button1' is set, trigger acts as big button.\n  \n  Note that 'button2' is always optional.\n\n\n#### <a name=\"mouse\"></a> mouse(axis)  \n\n- For stick, lets cursor or mouse wheel to be controlled by stick tilt.\n- For pad, acts as trackpad - sliding finger over pad moves the mouse.\n  If set to *REL_WHEEL* or *REL_HWHEEL*, emulates finger scroll.\n  You can use `ball(mouse)` to emulate trackball.\n- For gyroscope, controls mouse with changes in controller pitch and roll/yaw.\n  Axis parameter should be either YAW or ROLL (constants) and decides which\n  gyroscope axis controls X mouse axis.\n- For button, pressing button maps to single movement over mouse axis or\n  single step on scroll wheel.\n\n\n#### <a name=\"mouseabs\"></a> mouseabs(axis)\n\n- For stick, lets cursor or mouse wheel to be controlled by stick tilt.\n- For pad, distance from center of pad controls speed of mouse movement\n- For gyroscope, please, use gyroabs action.\n\n\n#### <a name=\"trackpad\"></a> trackpad(axis)\nMerged with [mouse](#mouse), does same thing.\n\n\n#### <a name=\"axis\"></a> axis(id [, min = -32767, max = 32767 ])\n- For button, pressing button maps to moving axis full way to 'max'.\neleasing button returns emulated axis back to 'min'.\n- For stick or pad, simply maps real axis to emulated\n- For trigger, maps trigger position to to emulated axis. Note that default\n  trigger position is not in middle, but in minimal possible value.\n\n\n#### <a name=\"dpad\"></a> dpad([diagonal_range,] up, down, left, right)\nEmulates dpad. Touchpad is divided into 8 triangular parts. When the user\ntouches the touchpad, action is executed depending on finger position.\n\n'diagonal_range' is specified in degrees (1 to 89). If not set, all parts are\nsized equally, otherwise, diagonal parts are taking specified portion of pad\nand rest is assigned to up/left/right/down portions.\n\nAvailable only for pads and sticks.\n\n\n#### <a name=\"dpad8\"></a> dpad8([diagonal_range,] up, down, left, right, upleft, upright, downleft, downright)\nSame as dpad, with more directions.\n\n\n#### <a name=\"ring\"></a> ring([radius=0.5], inner, outer)\nDefines outer and inner ring bindings. When distance of finger from center of\npad is smaller than 'radius', 'inner' action is activated, otherwise, 'outer'\ntakes place.\n\nUnlike [dpad](#dpad), which executes actions as if they were bound to buttons,\nring works more like defining two actions on same pad with non-overlapping\ndeadzones.\n\n\n#### <a name=\"area\"></a> area(x1, y1, x2, y2), <a name=\"winarea\"></a> winarea(x1, y1, x2, y2)\nCreates 1:1 mapping between finger position on pad and mouse position in\nspecified screen area. Coordinates are in pixels with (0,0) on top,left corner.\nNegative number can be used to count from other side of screen.\n\n`winarea` does same thing but with position relative to current window instead of entire screen.\n\n\n#### <a name=\"relarea\"></a> relarea(x1, y1, x2, y2), <a name=\"relwinarea\"></a> relwinarea(x1, y1, x2, y2)\nCreates 1:1 mapping between finger position on pad and mouse position in\nspecified screen area. Coordinates are fractions of screen width and height,\n(0,0) is top,left and (1,1) bottom,right corner of screen.\n\n`relwinarea` does same thing but with position relative to current window instead of entire screen.\n\n\n#### <a name=\"trigger\"></a> trigger(press_level, [release_level, ] action)\nMaps action to be executed as by button press when trigger is pressed through\n'press_level'. Level goes from 0 to 255, where 255 is level after physical\ntrigger clicks.\nThen, optionally, if trigger is pressed through 'release_level', action is\n\"released\". If release_level is not set, action will be released only after\ntrigger value moves back beyond press_level.\n\nIt's possible to map multiple actions on different trigger levels using [and](#and).\n\nExamples:\n\nHold right mouse button while trigger is being pressed, press left button when\ntrigger clicks. Right button is released only when trigger is fully released.\n```\ntrigger(64, 255, button(BTN_RIGHT)) and trigger(255, button(BTN_LEFT))\n```\n\nControl left virtual trigger while trigger is being pressed and press left button\njust before trigger clicks. If trigger clicks, press enter key and play feedback.\n```\n\ttrigger(64, 255, axis(ABS_Z))\nand\n\ttrigger(240, 254, button(BTN_LEFT))\nand\n\ttrigger(255, feedback(LEFT, button(KEY_ENTER)))\n```\n\n#### <a name=\"hipfire\"></a> hipfire([partialpress_level, ][fullpress_level, ] partialpress_action, fullpress_action [, mode][, delay])\nMaps two different actions to be executed when trigger is inside a defined range and meet predefined conditions.\nBasically, the \"partialpress_action\" will be activated if the trigger is pressed passed the \"partialpress_level\" and it stays inside the range between this level and the \"fullpress_level\" until the \"delay\" ends, otherwise, the \"fullpress_action\" will be activated ALONE if the \"fullpress_level\" is reached before the \"delay\" ends.\n\nThe partial and full levels goes from 0 to 255, and the values 50 and 254 are used for \"partialpress_level\" and \"fullpress_level\", respectively, if none is passed.\n\nThe \"mode\" can be defined as described below and the \"NORMAL\" one is used if none is  passed.\n\nModes available:\n\n - NORMAL - if trigger is pressed beyond the \"partialpress_level\" and the timeout is reached, the \"partialpress_action\" is executed. If the \"partialpress_action\" was pressed it will only be released after the trigger return back beyond the \"partialpress_level\". The \"fullpress_action\" will be executed every time the \"fullpress_level\" is reached, but if this level is reached before the timeout the \"partialpres_action\" will not be triggered until releasing the trigger.\n - EXCLUSIVE - Acts similar to the previous mode, but the \"fullpress_action\" is only triggered if the \"partialpres_action\" was not triggered. Meaning it will only activate if the \"fullpress_level\" is reached before the timeout ends.\n - SENSIBLE - Acts similar to NORMAL, but after the \"partialpress_action\" is activated, releasing the trigger a little, will deactivate the action allowing it to be activated again more faster without needing to release the trigger back beyond the \"partialpress_level\". \n\nThe \"delay\" is time window used to determine if the \"partialpress_action\" should or not be activated. \n\nExamples:\n\nHold right mouse button while trigger is being softly pressed and press left mouse button when trigger click, but will bypass the right mouse button and only press the left mouse button if the trigger is pressed very fast to the click.\n```\nhipfire(50, 254, button(BTN_RIGHT),button(BTN_LEFT), NORMAL, 0.20)\n```\n\nPress A if the trigger is pressed slowly and not reaches the click or press B if the trigger is pressed fast and reached the click, and will execute only one of this two actions.\n\n```\nhipfire(50, 254, button(KEY_A),button(KEY_B), EXCLUSIVE, 0.15)\n```\n\n#### <a name=\"gyro\"></a> gyro(axis1 [, axis2 [, axis3]])\nMaps *changes* in gyroscope pitch, yaw and roll movement into movements of gamepad stick.\nCan be used to map gyroscope to camera when camera can be controlled only with analog stick.\n\n\n#### <a name=\"gyroabs\"></a> gyroabs(axis1 [, axis2 [, axis3]])\nMaps absolute gyroscope pitch, yaw and roll movement into movements of mouse\nor gamepad stick.\nCan be used to map gyroscope to movement stick or to use controller as racing wheel.\n\n\n#### <a name=\"resetgyro\"></a> resetgyro()\nResets gyroscope offsets so current orientation is treated as neutral.\n\n\n#### <a name=\"cemuhook\"></a> cemuhook()\nWhen set to gyro, outputs gyroscope data in way compatibile with Cemu, Citra and\nother applications using CemuHookUDP motion provider protocol.\n\n\n#### <a name=\"tilt\"></a> gyro(front_down, front_up, tilt_left, tilt_right)\nMaps tilting of gamepad into actions. When gamepad is tilt to one of for supported\nsides, assigned action is executed as if by button press and then \"released\" after\ngamepad is balanced again.\n\n\n#### <a name=\"trackball\"></a> trackball()\nSplit to [ball](#ball) modifier and [mouse](#mouse) action.\n\nTyping `trackball` works as alias for `ball(mouse())`\n\n\n#### <a name=\"XY\"></a> XY(xaction, yaction)\nProvides way to assign two different actions to two stick or pad axes.\nThis is automatically handled by GUI, so user usually doesn't need\nto write it directly.\n\n\n#### <a name=\"relXY\"></a> relXY(xaction, yaction)\nWorks same as [XY](#XY), but treats position where pad is touched as \"center\"\nof pad.\n\n\n#### <a name=\"press\"></a> press(button)\nPresses button and leaves it pressed.\n\n\n#### <a name=\"release\"></a> release(button)\nReleases pressed button.\n\n\n#### <a name=\"tap\"></a> tap(button, number=1)\nPresses button for a short while, 'number' times.\n\nIf 'number' is greater than 1 (when double-tap is performed), tapped button\nis kept press as long as physical button that started tap is pressed. For single\ntap, virtual button is released right away.\n\nIf virtual button is already pressed before tapping, it is released first and\nrestored after tap, resulting in sequence of \"release - press - release - press\"\n\n\n#### <a name=\"profile\"></a> profile(name)\nLoads another profile\n\n\n#### <a name=\"shell\"></a> shell(command)\nExecutes command on background\n\n\n#### <a name=\"turnoff\"></a> turnoff()\nTurns controller off\n\n\n#### <a name=\"restart\"></a> restart()\nRestarts scc-daemon. Don't use unless you have good reason to.\n\n\n#### <a name=\"led\"></a> led(brightness)\nSets brightness of controller led. 'Brightness' is percent in 0 to 100 range.\n\n\n#### <a name=\"osd\"></a> osd([timeout=5, [size=3]], text)\nDisplays message in OSD.\n\n'timeout' sets for how many seconds should message stay visible. Value of 0 has\nspecial meaning and leaves message displayed indefinitely, until profile is\nchanged or [clearosd](#clearosd) action is used.\n\n'size' sets size of font on message. Only three options are supported right now,\n3 for \"default size\", 2 for \"smalller\" and 1 for \"small\".\n\n\n\n#### <a name=\"clearosd\"></a> clearosd()\nClears all windows from OSD layer. Cancels all menus, clears all messages,\nhides on screen keyboard.\n\nDoes _not_ clear OSD windows created using command line tools.\n\n\n#### <a name=\"menu\"></a> menu(menu [, confirm_button=A [, cancel_button=B [, show_with_release=False]]]])\nDisplays OSD menu.\n'confirm_button' and 'cancel_button' sets which gamepad button should be used to\nconfirm/cancel menu. Additionaly, 'confirm_button' can be set to SAME (constant),\nin which case menu will be closed and selected item choosen when button used to\ndisplay menu is released.\n\nIf 'show_with_release' is set to true, menu is displayed only after button\nis released.\n\n'menu' can be either id of menu defined in same profile file or filename\nrelative to `~/.config/scc/menus` or `/usr/share/scc/default_menus/`, whichever\nexists, in that order.\n\n\n#### <a name=\"hmenu\"></a> hmenu(menu [, confirm_button=A [, cancel_button=B [, show_with_release=False]]]])\nSame as `menu`, but packed in one row.\n\n\n#### <a name=\"gridmenu\"></a> gridmenu(menu [, confirm_button=A [, cancel_button=B [, show_with_release=False]]]])\nSame as `menu`, but displays items in grid.\n\n\n#### <a name=\"radialmenu\"></a> radialmenu(menu [, confirm_button=A [, cancel_button=B [, show_with_release=False]]]])\nSame as `menu`, but displays items in radial menu.\n\n\n#### <a name=\"quickmenu\"></a> quickmenu(menu)\nSpecial kind of menu controled by buttons instead of stick. Every item has\nassigned button and user selects it by pressing that button.\n\nFast to use, but is limited to 6 items at most.\n\n\n#### <a name=\"dialog\"></a> dialog([ confirm_button=A [, cancel_button=B ], ] text, action1, [action2... actionN])\nDisplays OSD dialog. Dialog works similary to horizontal menu and displays\ntext message above list of options.\n\n\n#### <a name=\"keyboard\"></a> keyboard()\nDisplays on-screen keyboard\n\n# Modifiers:\n\n#### <a name=\"click\"></a> click(action)\nCreates action action that occurs only if pad or stick is pressed.\nFor example, `click(dpad(...))` set to pad will create dpad that activates\nbuttons only when pressed.\n\n#### <a name=\"pressed\"></a> pressed(action)\nCreates action that occurs for brief moment when button is pressed.\nFor example, `pressed(button(A))` will press and instantly release virtual\nA button whenever physical button is pressed.\n\n#### <a name=\"released\"></a> released(action)\nCreates action that occurs for brief moment when button is released.\n\n#### <a name=\"touched\"></a> pressed(action)\nCreates action that occurs for brief moment when finger touches pad.\n\n#### <a name=\"untouched\"></a> released(action)\nCreates action that occurs for brief moment when pad is released.\n\n#### <a name=\"mode\"></a> mode(button1, action1, [button2, action2... buttonN, actionN] [, default] )\nDefines mode shifting. If physical buttonX is pressed, actionX is executed.\nOptional default action is executed if none from specified buttons is pressed.\n\n\n#### <a name=\"gestures\"></a> gestures([precision=0,] gesture1, action1, [gesture2, action2... gestureN, actionN] )\nIf set to left or right pad, enables gesture recognition. If GestureX\nis drawn, actionX is executed.\n\nIf 'precision' is set to 1.0, gesture has to be exact. Otherwise,\ngestures resembling input with given precision are compared and\none that matches it most is used. At precision of 0.0, all gestures are considered.\n\n<a name=\"gesture_format\"></a>Gestures are encoded in string and it's\nrecommended to use GUI to record them. Nevertheless, format is simple:\n- Each stroke in one of four directions is stored as single character.\n- Characters are uppercase `U`, `D`, `L`, `R` for up, down, left, right\n- Default stroke length is 1/3 of pad size.\n- For stroke with twice of that length, characters is repeated twice\n- Three times for stroke through entire pad, or even more for longer.\n- If string starts with lowercase `i`, stroke length is ignored.\n\n\n#### <a name=\"doubleclick\"></a> doubleclick(doubleclick_action [, normal_action [, timeout ]])\nExecutes action if user double-clicks button.\nOptional normal_action parameter specifies action that is executed when user\nclick button only once. Optional time arguments modifies maximum delay in \ndoubleclick and in effect sets delay before normal action is executed.\n\n#### <a name=\"hold\"></a> hold(hold_action [, normal_action [, timeout ]])\nExecutes action if user holds button for longer time.\nOptional normal_action parameter specifies action that is executed when user\nclick button shortly. Optional time arguments modifies how long \"longer time\" is.\n\nHold and doubleclick can be combined together by writing\n`hold([time,] hold_action, doubleclick(doubleclick_action, normal_action))`\n\n\n#### <a name=\"sens\"></a> sens(x_axis [, y_axis [, z_axis]], action)\nModifies sensitivity of physical stick or pad.\n\n\n#### <a name=\"rotate\"></a> rotate(angle, action)\nRotates input pad or stick input by given angle.\n\n\n#### <a name=\"feedback\"></a> feedback(side, [amplitude=256 [, frequency=4 [, period=100 [, count=1 ]]]], action)\nEnables haptic feedback for specified action, if action supports it.\nSide has to be one of LEFT, RIGHT or BOTH. All remaining numbers can be anything\nfrom 1 to 32767, but note that setting count to large number will start long\nrunning feedback that you may not be able to stop.\n\n'frequency' is used only when emulating touchpad and describes how many pixels\nshould mouse travel between two feedback ticks.\n\n\n#### <a name=\"ball\"></a> ball([friction=10.0, [mass=80.0, ]] action)\nEnables trackball mode. Moving finger over pad will keep repeating same action\nwith decreasing speed, based on set mass and friction, until virtual\n'spinning ball' stops moving.\n\n\n#### <a name=\"circular\"></a> circular(action)\nDesigned to controls scroll wheel by scrolling finger around pad.\nCan be used with any axis. For example,\n\n`circular(axis(Axes.ABS_X))`\n\nturns touchpad into small raing wheel.\n\n\n#### <a name=\"circularabs\"></a> circular(action)\nWorks as to `circular`, but instead of counting with finger movements,\ntranslates exact position on dpad to axis value.\n\n\n#### <a name=\"deadzone\"></a> deadzone([mode,] lower, [upper, ] action)\nEnables deadzone on trigger, pad or stick.\nMode defaults to 'CUT' and can be one of:\n\n - CUT     - if value is out of deadzone range, output value is zero\n - ROUND   - for values bellow deadzone range, output value is zero. For values\nabove range, output value is maximum allowed.\n - LINEAR  - input value is scaled, so entire output range is covered by\nrange of deadzone.\n - MINIMUM - any non-zero input value is scaled so entire input range is mapped\nto range of deadzone. Zero on input is mapped to zero on output, so there is\narea over which output \"jumps\" when stick is tilted.\n\n\n#### <a name=\"smooth\"></a> smooth([buffer=8, [multiplier=0.7, [filter=2, ]]] action)\nEnables input smoothing. Position is computed as weighed average of last X\ninput positions with highest weight given to most recent position. If 'filter'\nis above zero, movements bellow that value are ignored.\n\n\n#### <a name=\"osd\"></a> osd([timeout=5], action)\nEnables on screen display for action. In most cases just displays action\ndescription in OSD and executes it normally.\nWorks only if executed by pressing physical button or with `dpad`. Otherwise\njust executes child action.\n\n\n#### <a name=\"position\"></a> position(x, y, action)\nSpecifies menu position on screen. X is position from left, Y from top. To\nspecify position from right or bottom, use negative values.\n\n\n#### <a name=\"name\"></a> name(name, action)\nAllow inline setting of action name\n\n\n# Shortcuts:\n#### <a name=\"raxis\"></a> raxis(id)\nShortcut for `axis(id, 32767, -32767)`, that is call to axis with min/max values\nreversed. Effectively inverted axis mapping.\n\n\n#### <a name=\"hatup\"></a> hatup(id)\nShortcut for `axis(id, 0, 32767)`, emulates moving hat up or pressing 'up'\nbutton on dpad.\n\n\n#### <a name=\"hatdown\"></a> hatdown(id)\nShortcut for `axis(id, 0, -32767)`, emulates moving hat down or pressing 'down'\nbutton on dpad.\n\n\n#### <a name=\"hatleft\"></a> hatleft(id), <a name=\"hatright\"></a> hatright(id)\nSame thing as hatup/hatdown, as vertical hat movement and left/right dpad\nbuttons are same events on another axis\n\n\n# <a name=\"macros\"></a>Macros and operators\n\n#### <a name=\"and\"></a> and - executing actions at once\nIt is possible to join two (or more) actions with `and` keyword (or newline) to have them executed together.\n- `button(KEY_LEFTALT) and button(KEY_F4)` presses Alt+F4\n\n\n#### <a name=\"semicolon\"></a> semicolon - sequence (macro)\nWhen `;` is placed between actions, they are executed as sequence.\n- `hatup(ABS_Y); hatup(ABS_Y); button(BTN_B); button(BTN_A)` presses 'UP UP B A' on gamepad, as fast as possible\n- `button(KEY_A); button(KEY_B); button(KEY_C)` types 'abc'.\n\n\n#### <a name=\"type\"></a> type('text')\nSpecial type of macro where keys to press are specified as string.\nBasically, writing `type(\"iddqd\")` is same thing as `button(KEY_I) ; button(KEY_D) ;\nbutton(KEY_D); button(KEY_Q); button(KEY_D)`, just much shorter.\n\n\n#### <a name=\"sleep\"></a> sleep(x)\nTo insert pause between macro actions, use sleep() action.\n- `button(KEY_A); button(KEY_B); sleep(1.0); button(KEY_C)` types 'ab', waits 1s and types 'c'\n\n\n#### <a name=\"repeat\"></a> repeat(action)\nTurbo / rapid fire mode. Repeats macro (or even single action) until physical button is released. Macro is always played to end, even if button is released before macro is finished.\n- `repeat(button(BTN_X))` deals with \"mash X to not die\" events in some games.\n\n\n#### <a name=\"cycle\"></a> cycle(action1, action2...)\nExecutes different action every time when button is pressed (action1 upon first press, action2 with second, etc.)\nWorks only on buttons.\n\n\n# <a name=\"examples2\"></a>Examples for profile file\nEmulate key presses based on stick position\n```\n\"stick\" : {\n\t\"X\"\t\t: { \"action\" : \"pad(KEY_A, KEY_D)\" },\n\t\"Y\"\t\t: { \"action\" : \"key(KEY_W, KEY_S)\" },\n```\n\n\nEmulate left/right stick movement with X and B buttons\n```\n\"buttons\" : {\n\t\"B\"      : { \"action\" : \"axis(ABS_X, 0, 32767)\" },\n\t\"X\"      : { \"action\" : \"axis(ABS_X, 0, -32767)\" },\n```\n\nEmulate dpad on left touchpad, but act only when dpad is pressed\n```\n\"left_pad\" : {\n\t\"action\" : \"click( dpad('hatup(ABS_HAT0Y)', 'hatdown(ABS_HAT0Y)', 'hatleft(ABS_HAT0X)', 'hatright(ABS_HAT0X)' ) )\"\n}\n```\n\nEmulate button A when left trigger is half-pressed and button B when\nit is pressed fully\n```\n\"triggers\" : {\n\t\"LEFT\"  : { \"action\" : \"pad(BTN_A, BTN_B)\" },\n```\n"
  },
  {
    "path": "docs/menu-file.md",
    "content": "SC-Controller menu file specification\r\n----------------------------------------\r\n\r\nMenu file contains json-encoded list with menu items (actions), submenus,\nseparators and menu generators.\r\n\r\n### Menu Items\r\n\nEvery menu item is defined by action, in same way  as action would be defined\r\n[in profile file](profile-file.md#Action_definition) with one additional\r\n`id` key. `id` specifies action ID and can be anything, but each menu item\r\nshould have unique ID.\r\n\r\n`name` key is still optional, but highly recommended as used as menu item title\r\ndisplayed on screen. If `name` is not specified, title is auto-generated.\r\n\r\nExample:\r\n\r\n\t[{\r\n\t  \"id\": \"item1\", \r\n\t  \"action\": \"profile('Desktop')\", \r\n\t  \"name\": \"Switch to Desktop profile\", \r\n\t}, {\r\n\t  \"id\": \"item2\", \r\n\t  \"action\": \"turnoff()\", \r\n\t  \"name\": \"Turn controller OFF\", \r\n\t}]\r\n\r\nspecifies menu with two items.\n\r\n### Submenus\r\n\r\nSubmenu is reference to another menu file (submenu cannot be defined in same\r\nfile or profile file). When selected, another menu is loaded and drawn over\r\noriginal menu.\r\nSubmenu is dict with `submenu` key, value of key is filename relative to\r\n`~/.config/scc/menus` or `/usr/share/scc/default_menus/`, whichever exists, in\r\nthat order.\r\n`name` key may be defined.\r\n\r\nExample:\r\n\r\n\t[{\r\n\t  \"submenu\": \"profiles.menu\", \r\n\t  \"name\": \"All Profiles\"\r\n\t}]\r\n\r\nspecifies menu with sumbmenu called \"All Profiles\" defined in *profiles.menu*\r\n\r\n### Separators\r\n\r\nSeparator is empty space that splits menu into two or more logical blocks.\r\nName, if set, is displayed in different way from menu items. Separator is\r\ndefined by dict with `separator` key set to True.\r\n\r\n### Menu Generators\r\n\r\nGenerator is something that generates menu items automatically. It is defined\r\nby dict with `generator` key, where value is type of generator to use.\r\n\r\nExample:\r\n\r\n\t[{\r\n\t  \"generator\": \"profiles\"\r\n\t}, {\r\n\t  \"id\": \"item2\", \r\n\t  \"action\": \"turnoff()\", \r\n\t  \"name\": \"Turn controller OFF\", \r\n\t}]\r\n\r\nspecifies menu with list of all profiles, followed by one normal menu item.\r\n\r\n\r\n### Available generators\r\n\r\n#### `profiles`\r\nGenerates menu item for each available profile. Selecting item will switch to\r\nrepresented profile.\r\n\r\n#### `recent`\r\nGenerates menu item for *X* recently selected profiles. *X* is 5 by default and\r\ncan be set with additional `rows` key.\r\n"
  },
  {
    "path": "docs/profile-file.md",
    "content": "SC-Controller profile file specification\n----------------------------------------\n\nProfile file contains json-encoded dictonary with specific keys. Missing keys are substituted with defaults, unknown keys are ignored. See [Desktop.sccprofile](../default_profiles/Desktop.sccprofile) for example.\n\nRoot dictonary has to contain following keys:\n- `buttons`\t\t\t- contains subkey for controller buttons. See [buttons](#buttons).\n- `pad_left`\t\t- sets action executed when finger is moved on left touchpad.\n- `pad_right`\t\t- ... when finger is moved on right touchpad.\n- `stick`\t\t\t- ... when stick angle is changed.\n- `trigger_left`\t- ... when left trigger value is changed.\n- `trigger_right`\t- ... when right trigger value is changed.\n- `gyro`\t\t\t- ... when gyroscope reading changes. Gyroscope in is activated only if this key is set to something else than `NoAction`\n- `menus`\t\t\t- stores menus saved in profile. See [menus](#menus).\n- `version`\t\t\t- profile file version. Current version is _1_. See If not pressent, _0_ is assumed. If profile file version is lower than expected, automatic conversion may happen. This conversion is in-memory only, but changing and saving such profile in GUI will save converted data.\n\nSee [actions.md](actions.md) file for list of possible actions.\n\n\n## <A name=\"Action_definition\"></a>Action definition\nAction definition is dictionary containing `action` key and optional `name` key. Value assigned to `action` describes action to be executed.\n\n`action` key can describe entire action, but for better readability, it is also possible to specify additional properties using [additional keys](#Additional_keys).\n\nFor example,\n\n\t{\n\t  \"trigger_left\": {\n\t    \"action\": \"axis(Axes.ABS_Z)\",\n\t    \"name\": \"Aim\",\n\t}}\n\nassigns `axis` action with *Axes.ABS_Z* parameter to left trigger.\n\n## <a name=\"Additional_keys\"></a>Additional keys in action definition\n\n#### `X` and `Y`\nTurns action into `XYAction`, allowing to specify different action for each pad\nor stick axis. If either of keys is specified, `action` key is ignored.\n\nExample:\n\n\t\"stick\" : {\n\t  \"X\": { \"action\": \"axis(Axes.ABS_RX)\" },\n\t  \"Y\": { \"action\": \"raxis(Axes.ABS_RY)\" }\n\t},\n\n\nis same as\n`\"stick\" : { \"action\" : \"XY(axis(Axes.ABS_RX), raxis(Axes.ABS_RY))\" }`\n\n\n#### `levels`\nTurns action into `TriggerAction`, allowing to specify lower and upper trigger\nlevels among which is action executed.\n\nExample:\n\n\t\"trigger_left\": {\n\t  \"action\": \"button(BTN_LEFT)\", \n\t  \"levels\": [127, 255]\n\t}, \n\nSets action that presses left mouse button, but only if trigger\nis roughly half-pressed.\n\n\n#### `dpad`\nTurns action into `DPadAction`, allowing to assign different action for each\nside of pad or stick alignment.\n\nExample:\n\n\t\"dpad\" : [\n\t  { \"action\": \"button(Keys.KEY_UP)\" },\n\t  { \"action\": \"button(Keys.KEY_DOWN)\" },\n\t  { \"action\": \"button(Keys.KEY_LEFT)\" },\n\t  { \"action\": \"button(Keys.KEY_RIGHT)\" }\n\t],\n\n\n#### `ring`\nDefines outer and inner ring bindings. Expects keys with 'inner' and 'outer'\nactions and 'radius' as float value, but all keys are optional.\n\nExample:\n\n\t\"pad_left\": {\n\t  \"ring\": {\n\t    \"inner\": { \"action\": \"XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y))\" },\n\t    \"outer\": { \"action\": \"XY(axis(Axes.ABS_RX), raxis(Axes.ABS_RY))\" }, \n\t    \"radius\": 0.4\n\t  }\n\t}, \n\ndefines inner ring binding controlling left stick and outer ring right stick\nof emulated gamepad.\n\n#### `tilt`\nTurns action into `TiltAction`, allowing to assign different action for tilting\ndpad. Works pretty-much as `dpad` on gyro.\n\nExample:\n\n\t\"tilt\" : [\n\t  { \"action\": \"button(Keys.KEY_UP)\" },\n\t  { \"action\": \"button(Keys.KEY_DOWN)\" },\n\t  { \"action\": \"button(Keys.KEY_LEFT)\" },\n\t  { \"action\": \"button(Keys.KEY_RIGHT)\" }\n\t],\n\n#### `deadzone`\nSpecifies deadzone. Allows for `lower` and `upper` subkeys defaulting to\n*0* and *32767* and `mode` subkey defaulting to 'CUT'.\nSee see [deadzone modifier](actions.md#deadzone) for list of modes.\n\nExample:\n\n\t\"trigger_left\": {\n\t\t\"mode\" : \"linear\",\n \t  \"action\": \"axis(Axes.ABS_Z)\",\n \t  \"deadzone\": {\n\t    \"lower\": 100,\n\t    \"upper\": 200\n  \t}},\n\n\n#### `sensitivity`\nSpecifies input sensitivity. Value is list with one or two values for sensitivity\nover X and Y axis (or one value for sensitivity of trigger.)\nDefault sensitivity is 1.0\n\nExample:\n\n\t\"stick\" : {\n\t  \"action\": \"trackball()\",\n\t  \"sensitivity\": [2.0, 0.5]\n\t},\n\ndoubles sensitivity over X and halves over Y axis.\n\n\n#### `rotate`\nRotates input pad or stick input by given angle.\n\nExample:\n\n\t\"stick\" : {\n\t  \"action\": \"trackball()\",\n\t  \"rotate\": 15\n\t},\n\n\n\n#### `feedback`\nEnables haptic feedback for action. Value is list with one to three values\nspecifying feedback position (*'LEFT'*, *'RIGHT'* or *'BOTH'*),\namplitude and frequency.\n\nExample:\n\n\t\"pad_left\": {\n\t  \"action\": \"trackball()\",\n\t  \"feedback\": [\"LEFT\", 512.0, 5.0]\n\t},\n\nspecifies feedback with amplitude of 512 (default vlaue)\nand frequency of 5 generated by left motor.\n\n\n#### `smooth`\nEnables input smoothing (see [smooth modifier](actions.md#smooth))\n\nExample:\n\n\t\"pad_left\": {\n\t  \"action\": \"trackball()\",\n\t  \"smooth\": [ 8, 0.7, 2.0 ]\n\t},\n\nenables smoothing with buffer of 8 and modifier set to 0.7.\n\n\n#### `osd`\nIf set to True, enables OSD for action.\n\nExample:\n\n\t\"X\": {\n\t  \"action\": \"button(Keys.BTN_EAST)\", \n\t  \"osd\": true\n\t}, \n\nenables OSD feedback for X button.\n\n#### `click`\nIf set to True, enables 'click' modifier, making action executed only when\npad or stick is pressed.\n\nExample:\n\n\t\"pad_left\": {\n\t  \"action\": \"mouse()\",\n\t  \"click\": True\n\t},\n\n\n#### `ball`\nIf set to value, enables trackball mode. Value is list with zero to two values\nspecifying friction and mass of virtual 'spinning ball'.\nSee [ball modifier](actions.md#ball) for more info.\n\nExample:\n\n\t\"pad_left\": {\n\t  \"action\": \"mouse()\",\n\t  \"ball\": [ 10.0 ]\n\t},\n\n\n#### `circular`\nDesigned to controls scroll wheel by scrolling finger around pad, but can\nbe used with any axis.\n\nExample:\n\n\t\"pad_left\": {\n\t  \"action\": \"mouse(Rels.REL_WHEEL)\",\n\t  \"circular\": true\n\t},\n\n\n#### `circularabs`\nWorks as to `circular`, but instead of counting with finger movements,\ntranslates exact position on dpad to axis value.\n\nExample:\n\n\t\"pad_left\": {\n\t  \"action\": \"circularabs(Rels.REL_WHEEL)\",\n\t  \"circular\": true\n\t},\n\n\n#### `modes`\nDefines mode shifting (see [mode modifier](actions.md#mode)).\n\nValue is dict with physical key names (A, B, X, Y...) as keys and actions\nfor each mode as values. Action on same level as `mode` is used as default\naction.\n\nExample:\n\n\t\"modes\": {\n\t  \"A\": { \"action\": \"mouse()\" },\n\t  \"B\": { \"action\": \"XY( axis(Axes.ABS_X), raxis(Axes.ABS_Y) )\" }\n\t},\n\ndefines pad or stick that controls mouse while button A is pressed\nand left virtual stick while button B is pressed.\n\n\n#### `gestures`\nEnables gesture cognition on pad (see [gestures modifier](actions.md#gestures)).\n\nValue is dict with encoded gestures (see [description in actions.md](actions.md#gesture_format)) and actions\nfor each gesture as values.\n\nExample:\n\n\t\"gestures\": {\n\t  \"UD\": { \"action\": \"button(Key.R)\" }\n\t},\n\nenables gesture recognition with single gesture activated when user does short stroke up followed by short stroke down.\n\n#### `doubleclick`\nDefines action that is executed when user double-clicks with button.\nOptional `time` key can be used on same level as `doubleclick` to modify\ndouble-click time.\n\nExample:\n\n\t\"buttons\": {\n\t  \"A\": {\n\t    \"action\": \"button(KEY_X)\",\n\t    \"doubleclick\": { \"action\": \"button(KEY_Z)\" },\n\t\t\"time\": 5\n\t  }\n\t}\n\ndefines button that emulates pressing X key when pressed normally and\npressing Z key when doubleclicked.\n\n\n#### `hold`\nDefines action that is executed when user holds button for short time.\nOptional `time` key can be used on same level as `hold` to modify\ndouble-click time.\n\nExample:\n\n\t\"buttons\": {\n\t  \"A\": {\n\t    \"action\": \"button(KEY_X)\",\n\t    \"hold\": { \"action\": \"button(KEY_Z)\" },\n\t\t\"time\": 5\n\t  }\n\t}\n\ndefines button that emulates pressing X key when pressed normally and\npressing Z key when held for 5 seconds.\n\n\n## <a name=\"buttons\"></a>Buttons\n`buttons` is dictionary with keys for each gamepad button.\nPossible keys are:\n\n- `X`, `Y`, `A` and `B` for colored buttons\n- `C` for Steam button in center\n- `SELECT` and `START` for small \"( &lt; )\" and \"( &gt; )\" buttons\n- `LB` and `RB` for left and right bumper\n- `LPAD`, `RPAD` and `STICK` for presing pads or stick.\n\nAll keys are optional. Value for each key is [action definition](#Action_definition)\n\nExample:\n\n\t\"buttons\": {\n\t  \"A\":    { \"action\": \"button(Keys.BTN_WEST)\",  }, \n\t  \"B\":    { \"action\": \"osd('Hello world!')\" }, \n\t  \"BACK\": { \"action\": \"button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_A)\" }, \n\t}\n\n\n## <a name=\"menus\"></a>Menus\n`menus` is dictionary with menus stored along with profile. Keys are IDs of\nmenus; Menu ID can contain any characters but dots (\".\") and slashes (\"/\").\n\nValue for each key is same as root list in [menu file](menu-file.md)\n"
  },
  {
    "path": "docs/protocol.md",
    "content": "SCCDaemon Protocol specification\n--------------------------------\n\nTo control running daemon instance, unix socket in user directory is used.\nControlling protocol uses case-sensitive messages terminated by newline. Message type and message arguments are delimited by `:`.\n\nWhen new connection is accepted, daemon sends some info:\n\n```\nSCCDaemon\nVersion: 0.2.6\nPID: 123456\nCurrent profile: filename.sccprofile\nReady.\n```\n\nConnection is then held until client side closes it.\n\n### Messages sent by daemon:\n\n#### `Controller Count: n`\nInforms about total number of connected controllers.\nAlways sent after `Controller:` messages\n\n#### `Controller: controller_id type flags config_file`\nProvides info about controller 'n'.\n- `controller_id` is unique string identifier of controller (should stay same at\nleast until daemon exits) and doesn't contains spaces.\n- `type` is string identifier (without spaces) of driver.\n- `flags` describes controller features, such as having central touchpad.\nSee ControllerFlags definition in scc/constants.py for more info.\n- `config_file` is None or file name of json-encoded file that can GUI use\nto get additional data about controller (background image, button images, etc)\nFile name may be absolute path or just name of file in /usr/share/scc\n\nThis message is repeated for every connected controller and followed by\n`Controller Count:` message. It is automatically sent to every client when\nnumber of connected controllers changes. It is also sent automatically to\nevery new client.\n\n#### `Controller profile: controller_id filename.sccprofile`\nSent to every client when profile file for any controller is loaded and used.\nAlso sent automatically to every new client.\n\n#### `Current profile: filename.sccprofile`\nSimilar to `Controller profile:`, sent to every client when profile file for\nfirst controller is loaded and used. Also sent automatically to every new client.\n\nUnlike `Controller profile:`, this message is sent even if there is no\ncontroller connected.\n\n#### `Event: source values`\nSent to client that requested locking of source (that is button, pad or axis).\n\nList of possible events:\n- `Event: B 1` - Sent when button is pressed. *B* is button, is one of *SCButtons.\\** constants.\n- `Event: B 0` - Sent when button is released. *B* is button one of *SCButtons.\\** constants.\n- `Event: STICK x y` - Sent when stick position is changed. *x* and *y* are new values.\n- `Event: LEFT x y` - Sent when finger on left pad is moved. *x* and *y* is new position.\n- `Event: RIGHT x y` - Sent when finger on right pad is moved. *x* and *y* is new position.\n\n#### `Error: message`\nSent to every client when error is detected. May be sent repeatedly to indicate\nmultiple errors.\n\nAfter all error conditions are cleared, `Ready.` is sent to indicate that emulation works again.\n\n#### `Fail: text`\nIndicates error as response to client's request.\n\n#### `Gesture: side gesturestring`\nSent to client that requested gesture to be detected.\n\n#### `OK.`\nIndicates sucess as response to client's request.\n\n### `OSD: tool param1 param2...`\nSend to scc-osd-daemon when osd-related action is requested.\n*tool* can be *'message'*, *'menu'*, *'hmenu'*, *'gridmenu'*,*'radialmenu'* or *'gesture'*\n*params* are same as command-line arguments for scc-osd-* script with that name.\n\n#### `PID: xyz`\nReports PID of *scc-daemon* instance. Automatically sent when connection is accepted.\n\n#### `Ready.`\nAutomatically sent when connection is accepted to indicate that there is no error and daemon is working as expected.\n\n#### `Reconfigured.`\nSent to all clients when daemon receives `Reconfigure.` message.\n\n#### `SCCDaemon`\nJust identification message, automatically sent when connection is accepted.\nCan be either ignored or used to check if remote side really is *scc-daemon*.\n\n#### `State: ....`\nSent to client as response to `State.` message. String after colon describes\ncurrent state of controller (such as pressed buttons and stick position...)\nand is device-specific.\n\n#### `Version: x.y.z`\nIdentifies daemon version. Automatically sent when connection is accepted.\n\n## Commands sent from client\n\n#### `Controller: controller_id`\nBy default, all messages sent from client are related to first connected\ncontroller. This message changes which controller are following messages meant\nfor.\n\nIf controller with specified controller_id is known, daemon responds with `OK.`\nOtherwise, `Fail: no such controller` error message is sent.\n\n#### `Controller.`\nRestores default state after controller is chosen.\nDaemon responds with `OK.`\n\n#### `Gesture: side up_angle`\nRequests gesture to be detected on one of pads. 'side' can be LEFT or RIGHT.\n'up_angle' is angle in radians and sets how much should be gesture input\nrotated.\n\nDaemon always responds with `OK.` unless request cannot be parsed.\nThen, when gesture detection is completed, daemon sends\n`Gesture: side detectedgesture` message. If gesture detection fails for any\nreason, sent gesture is empty.\n\n#### `Led: brightness`\nSets brightness of controller led. 'Brightness' is percent in 0 to 100 range.\nDaemon responds with `OK.`, unless 'brightness' cannot be parsed, in which case\n`Fail: ...` with error message is sent.\n\n#### `Lock: button1 button2...`\nLocks physical button, axis or pad. Events from locked sources are not processed normally, but sent to client that initiated lock.\n\nOnly one client can have one source locked at one time. Second attempt to lock already locked source will fail and `Fail: cannot lock <button>` will be sent as response. Locking is done only if all requested sources are free and in such case, daemon responds with `OK.`\n\nWhile source is locked, daemon keeps sending `Event: ...` messages every time when button is pressed, released, axis moved, etc...\n\nUnlocking is done automatically when client is disconnected, or using `Unlock.` message.\n\n#### `Observe: button1 button2...`\nEnables observing on physical button, axis or pad. Works like Lock, but events from observed sources are processed normally and to client at same time.\n\nAny number of clients can observe same source, so upon this requests, daemon always responds with `OK.`, as long as observing is enabled in configuration.\nWhile source is observed, daemon keeps sending `Event: ...` messages every time when button is pressed, released, axis moved, etc...\n\nUnlocking is done automatically when client is disconnected, or using `Unlock.` message.\n\n#### `Replace: button actionstring`\nTemporally replaces action set on physical button, axis or pad. This works in\nsame way as lock, so action is restored when client requesting change disconnects\nor call `Unlock.`\n\nIf requested button (axis, pad) is already locked, daemon will respond with\n`Fail: cannot lock <button>`. If action string (which can contain spaces)\ncannot be parsed, daemon responds with `Fail: failed to parse: <more info>`.\nIf everything went well, daemon respnds with `OK.`\n\n#### `Feedback: position amplitude`\nAsks daemon to generate feedback effect. Position can be one of 'LEFT', 'RIGHT' or 'BOTH' and\namplitude is integer in range 0 to 32767 and controls power of generated effect.\n\nDaemon responds with `OK.`\n\n#### `OSD: text to display`\nAsks daemon to display OSD message. No escaping or quoting is needed, everything after colon is displayed\nas text.\n\nIf OSD cannot be used (for example because daemon runs without X server), daemon responds with `Fail: ....` message.\nOtherwise daemon responds with `OK.`. Note that doesn't necessary mean that OSD is visible to user, only\nthat scc-daemon managed to send request to scc-osd-daemon.\n\n#### `Profile: filename.sccprofile`\nAsks daemon to load another profile. No escaping or quoting is needed, everything after colon is used as filename. Additional spaces and tabs are stripped.\n\nIf profile is sucessfully loaded, daemon responds with `OK.` to client that initiated loading and sends `Current profile: ...` message to all clients.\n\nIf loading fails, daemon responds with `Fail: ....` message where error with entire backtrace is sent. Backtrace is escaped to fit it on single line.\n\n#### `Reconfigure.`\nAsks daemon to reload configuration file (`~/.config/scc/config.json`).\nDaemon reloads and reapplies all controller configs and sends `Reconfigured.`\nmessage to all connected clients, what causes them to reload configuration\nfile as well.\nDaemon responds with `OK.`\n\n#### `Register: value`\nSend by scc-osd-daemon and scc-autoswitch-daemon to register their client connections.\nWhen sent with same value with two or more clients, daemon will automatically close former connection\nbefore registering new one.\nscc-osd-daemon sends `Register: osd`\nscc-autoswitch-daemon `Register: autoswitch`\nDaemon responds with `OK.`\n\n#### `Rescan.`\nAsks daemon to rescan for new devices. Drivers may re-read its configuration if needed.\nDaemon responds with `OK.`\n\n#### `Restart.`\nRestarts daemon. This has same effect as calling \"scc-daemon restart\", as that's exactly what\ngets called. All clients are disconnected immediately, so there is no response.\n\n#### `Selected: menu_id item_id`\nSend by scc-osd-daemon when user chooses item from displayed menu.\nIf menu_id or item_id contains spaces or quotes, it should be escaped.\nDaemon responds with `OK.`\n\n#### `State.`\nAsks daemon to sent current state of controller. Format of response is device-specific,\nbut should be useful enough for single-purpose script or debugging.\n\nIf observing is not enabled in configuration, daemon responds with `Fail: Sniffing disabled.`\nIf there is no active controller, daemon responds with `Fail: no controller connected`. \nOtherwise, daemon responds with `State: ...` message.\n\n#### `Gestured: gesture_string`\nSend by scc-osd-daemon, when user draws gesture. Sent only after requested\nby `OSD: gesture`. If user gesture cannot be recognized or user cancels it,\n'3|' (valid gesture string with no meaning) is reported.\nDaemon responds with `OK.`\n\n#### `Turnoff.`\nTurns off all controllers.\nDaemon responds with `OK.`\n\n#### `Unlock.`\nUnlocks everything locked with `Lock...` and `Observe...` messages sent by same client.\nIt is not possible to unlock only one input or only one type of lock.\n\nThis operation cannot fail (and does nothing if there is nothing to unlock), so daemon always responds with `OK.`\n"
  },
  {
    "path": "gamecontrollerdb.txt",
    "content": "# Game Controller DB for SDL in 2.0.9 format\n# Source: https://github.com/gabomdq/SDL_GameControllerDB\n\n# Linux\n03000000c82d00000090000011010000,8BitDo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00001038000000010000,8Bitdo FC30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00005106000000010000,8BitDo M30,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001590000011010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00006528000000010000,8BitDo N30 Pro 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000310000011010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux,\n05000000c82d00008010000000010000,8BitDo NES30,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b9,righttrigger:b8,start:b11,x:b3,y:b4,platform:Linux,\n03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000060000000010000,8BitDo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000061000000010000,8Bitdo SF30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d000021ab000010010000,8BitDo SFC30,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n030000003512000012ab000010010000,8Bitdo SFC30 GamePad,a:b2,b:b1,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b0,platform:Linux,\n05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000160000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000160000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000161000000000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00001290000011010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00006228000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n03000000c82d00000260000011010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000c82d00000261000000010000,8BitDo SN30 Pro+,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000202800000900000000010000,8BitDo SNES30 Gamepad,a:b1,b:b0,back:b10,dpdown:b122,dpleft:b119,dpright:b120,dpup:b117,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n030000005e0400008e02000020010000,8BitDo Wireless Adapter (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c82d00000031000011010000,8BitDo Wireless Adapter (DInput),a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000c82d00001890000011010000,8BitDo Zero 2,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,\n050000005e040000e002000030110000,8BitDo Zero 2 (XInput),a:b0,b:b1,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,start:b7,x:b2,y:b3,platform:Linux,\n05000000c82d00003032000000010000,8BitDo Zero 2,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,\n05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,\n05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,\n03000000c01100000355000011010000,ACRUX USB GAME PAD,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Linux,\n030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00003901000020060000,Afterglow Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00003901000000430000,Afterglow Prismatic Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00003901000013020000,Afterglow Prismatic Wired Controller 048-007-NA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000007c1800000006000010010000,Alienware Dual Compatible Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Linux,\n05000000491900000204000021000000,Amazon Fire Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b17,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,misc1:b12,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000790000003018000011010000,Arcade Fightstick F300,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,\n05000000050b00000045000040000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,\n03000000120c00000500000010010000,AxisPad,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b11,x:b0,y:b1,platform:Linux,\n03000000c62400001b89000011010000,BDA MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000d62000002a79000011010000,BDA PS4 Fightpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000c21100000791000011010000,Be1 GC101 Controller 1.03 mode,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000c31100000791000011010000,Be1 GC101 GAMEPAD 1.03 mode,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005e0400008e02000003030000,Be1 GC101 Xbox 360 Controller mode,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,\n03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000000b0400003365000000010000,Competition Pro,a:b0,b:b1,back:b2,leftx:a0,lefty:a1,start:b3,platform:Linux,\n03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,\n03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,\n03000000790000001100000010010000,Data Frog SNES controller,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,\n03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,\n030000004f04000004b3000010010000,Dual Power 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,\n03000000bc2000000055000011010000,GameSir G3w,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000008f0e00000800000010010000,Gasia Co. Ltd PS(R) Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000451300000010000010010000,Genius Maxfire Grandias 12,a:b0,b:b1,x:b2,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,\n030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n0300000079000000d418000000010000,GPD Win 2 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000007d0400000540000000010000,Gravis Eliminator GamePad Pro,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,\n030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000f0250000c383000010010000,GT VX2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,\n03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000632500002605000010010000,HJD-X,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux,\n030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f0000c100000011010000,HORI CO. LTD. HORIPAD S,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00008500000010010000,HORI Fighting Commander,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00008600000002010000,Hori Fighting Commander,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000000d0f00009200000011010000,Hori Pokken Tournament DX Pro Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f0000aa00000011010000,HORI Real Arcade Pro,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000000d0f0000d800000072056800,HORI Real Arcade Pro S,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n030000000d0f00001600000000010000,Hori Real Arcade Pro.EX-SE (Xbox 360),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f0000ee00000011010000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,\n03000000242e00008816000001010000,Hyperkin X91,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,\n050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000d80400008200000003000000,IMS PCU#0 Gamepad Interface,a:b1,b:b0,back:b4,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,start:b5,x:b3,y:b2,platform:Linux,\n03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux,\n0500000049190000020400001b010000,Ipega PG-9069 - Bluetooth Gamepad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b161,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000632500007505000011010000,Ipega PG-9099 - Bluetooth Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,\n03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000300f00000b01000010010000,Jess Tech GGE909 PC Recoil Pad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n030000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux,\n050000007e0500000620000001000000,Joy-Con (L),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b13,leftshoulder:b4,leftstick:b10,rightshoulder:b5,start:b8,x:b2,y:b3,platform:Linux,\n030000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux,\n050000007e0500000720000001000000,Joy-Con (R),+leftx:h0.2,+lefty:h0.4,-leftx:h0.8,-lefty:h0.1,a:b0,b:b1,back:b12,leftshoulder:b4,leftstick:b11,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Linux,\n03000000242f00002d00000011010000,JYS Wireless Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000242f00008a00000011010000,JYS Wireless Adapter,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,\n030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d040000d1ca000000000000,Logitech ChillStream,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d0400001ec2000019200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d0400000ac2000010010000,Logitech Inc. WingMan RumblePad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b2,rightx:a3,righty:a4,x:b3,y:b4,platform:Linux,\n030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,\n050000004d4f435554452d3035305800,M54-PC,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Linux,\n05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,\n03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008433000011010000,Mad Catz FightStick TE S+ (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700008483000011010000,Mad Catz FightStick TE S+ (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000120c00000500000000010000,Manta Dualshock 2,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,\n03000000790000004318000010010000,Mayflash GameCube Controller Adapter,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,\n03000000242f00007300000011010000,Mayflash Magic NS,a:b1,b:b4,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b0,y:b3,platform:Linux,\n0300000079000000d218000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000d620000010a7000011010000,Mayflash Magic NS,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n0300000025090000e803000001010000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:a5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,\n03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n030000005e0400000e00000000010000,Microsoft SideWinder,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,\n030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000050b000003090000,Microsoft X-Box One Elite 2 pad,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005e040000e302000003020000,Microsoft X-Box One Elite pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000dd02000003020000,Microsoft X-Box One pad (Firmware 2015),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n030000005e040000000b000008040000,Microsoft Xbox One Elite 2 pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000ea02000008040000,Microsoft Xbox One S pad - Wired,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c62400001a53000000010000,Mini PE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,\n05000000d6200000e589000001000000,Moga 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n05000000d62000007162000001000000,Moga Pro 2 HID,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,\n03000000c62400002b89000011010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c62400002a89000000010000,MOGA XP5-A Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b22,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000c62400001a89000000010000,MOGA XP5-X Plus,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000250900006688000000010000,MP-8866 Super Dual Box,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,\n030000006b140000010c000010010000,NACON GC-400ES,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0f00000900000010010000,Natec Genesis P44,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,\n060000007e0500000820000000000000,Nintendo Combined Joy-Cons (joycond),a:b0,b:b1,back:b9,dpdown:b15,dpleft:b16,dpright:b17,dpup:b14,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n030000007e0500003703000000016800,Nintendo GameCube Controller,a:b0,b:b2,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1~,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3~,start:b8,x:b1,y:b3,platform:Linux,\n03000000790000004618000010010000,Nintendo GameCube Controller Adapter,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a5~,righty:a2~,start:b9,x:b0,y:b3,platform:Linux,\n050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n050000007e0500000920000001800000,Nintendo Switch Pro Controller (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n030000007e0500000920000011810000,Nintendo Switch Pro Controller Wired (joycond),a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b11,leftshoulder:b5,leftstick:b12,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b13,righttrigger:b8,rightx:a2,righty:a3,start:b10,x:b3,y:b2,platform:Linux,\n050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,\n05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,\n03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000550900001472000011010000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,\n05000000550900001472000001000000,NVIDIA Controller v01.04,a:b0,b:b1,back:b14,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,platform:Linux,\n03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n19000000010000000100000001010000,odroidgo2_joypad,a:b1,b:b0,dpdown:b7,dpleft:b8,dpright:b9,dpup:b6,guide:b10,leftshoulder:b4,leftstick:b12,lefttrigger:b11,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b13,righttrigger:b14,start:b15,x:b2,y:b3,platform:Linux,\n19000000010000000200000011000000,odroidgo2_joypad_v11,a:b1,b:b0,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b12,leftshoulder:b4,leftstick:b14,lefttrigger:b13,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b15,righttrigger:b16,start:b17,x:b2,y:b3,platform:Linux,\n030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,\n05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,\n05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,\n03000000830500005020000010010000,Padix Co. Ltd. Rockfire PSX/USB Bridge,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b2,y:b3,platform:Linux,\n03000000790000001c18000011010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000006f0e0000b802000001010000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000b802000013020000,PDP AFTERGLOW Wired Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00008001000011010000,PDP CO. LTD. Faceoff Wired Pro Controller for Nintendo Switch,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00003101000000010000,PDP EA Sports Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e0000c802000012010000,PDP Kingdom Hearts Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00008701000011010000,PDP Rock Candy Wired Controller for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000006f0e00000901000011010000,PDP Versus Fighting Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e0000a802000023020000,PDP Wired Controller for Xbox One,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000006f0e00008501000011010000,PDP Wired Fight Pad Pro for Nintendo Switch,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n05000000491900000204000000000000,PG-9118,x:b76,a:b73,b:b74,y:b77,back:b83,start:b84,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b79,lefttrigger:b81,rightshoulder:b80,righttrigger:b82,leftstick:b86,rightstick:b87,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux,\n0500000049190000030400001b010000,PG-9099,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000004c050000da0c000011010000,Playstation Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000c62400000053000000010000,PowerA,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c62400003a54000001010000,PowerA 1428124-01,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d62000006dca000011010000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000c62400001a58000001010000,PowerA Xbox One Cabled,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006d040000d2ca000011010000,Precision Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000006f0e00001402000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000008f0e00000300000010010000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n050000004c0500006802000000800000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n05000000504c415953544154494f4e00,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n060000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,\n030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n03000000c01100000140000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000c405000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n050000004c050000cc09000001800000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,\n030000004c050000e60c000011010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n050000004c050000e60c000000010000,PS5 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,\n030000009b2800003200000001010000,Raphnet Technologies GC/N64 to USB v3.4,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,\n030000009b2800006000000001010000,Raphnet Technologies GC/N64 to USB v3.6,a:b0,b:b7,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,rightx:a3,righty:a4,start:b3,x:b1,y:b8,platform:Linux,\n030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,\n030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000008916000000fd000024010000,Razer Onza Tournament Edition,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000321500000204000011010000,Razer Panthera (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n03000000321500000104000011010000,Razer Panthera (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000321500000810000011010000,Razer Panthera Evo Arcade Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000321500000507000000010000,Razer Raiju Mobile,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b21,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000321500000011000011010000,Razer Raion Fightpad for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000008916000000fe000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c6240000045d000024010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n0300000081170000990a000001010000,Retronic Adapter,a:b0,leftx:a0,lefty:a1,platform:Linux,\n0300000000f000000300000000010000,RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,\n030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000006b140000130d000011010000,Revolution Pro Controller 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00001f01000000010000,Rock Candy,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000006f0e00001e01000011010000,Rock Candy PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00004601000001010000,Rock Candy Xbox One Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n03000000a30600001005000000010000,Saitek P150,a:b0,b:b1,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b7,lefttrigger:b6,rightshoulder:b2,righttrigger:b5,x:b3,y:b4,platform:Linux,\n03000000a30600000701000000010000,Saitek P220,a:b2,b:b3,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,lefttrigger:b7,rightshoulder:b4,righttrigger:b5,x:b0,y:b1,platform:Linux,\n03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b0,y:b1,platform:Linux,\n03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,\n03000000300f00001201000010010000,Saitek P380,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,\n03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,\n03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,\n03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n03000000a306000020f6000011010000,Saitek PS2700 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,\n03000000d81d00000e00000010010000,Savior,a:b0,b:b1,back:b8,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b11,righttrigger:b3,start:b9,x:b4,y:b5,platform:Linux,\n03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux,\n03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000632500007505000010010000,SHANWAN PS3/PC Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000bc2000000055000010010000,ShanWan PS3/PC Wired GamePad,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005f140000c501000010010000,SHANWAN Trust Gamepad,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Linux,\n03000000632500002305000010010000,ShanWan USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000341a00000908000010010000,SL-6566,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n050000004c050000e60c000000810000,Sony DualSense Wireless,a:b0,b:b1,x:b3,y:b2,back:b8,guide:b10,start:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,\n03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,\n030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000d11800000094000011010000,Stadia Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de2800004211000011010000,Steam Controller,a:b2,b:b3,back:b10,dpdown:b18,dpleft:b19,dpright:b20,dpup:b17,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b5,platform:Linux,\n03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,\n03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000381000003014000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000381000003114000075010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n05000000110100001914000009010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n03000000ad1b000038f0000090040000,Street Fighter IV FightStick TE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000003b07000004a1000000010000,Suncom SFX Plus for USB,a:b0,b:b2,back:b7,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:b5,start:b8,x:b1,y:b3,platform:Linux,\n03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,\n0300000000f00000f100000000010000,Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,\n03000000457500002211000010010000,SZMY-POWER CO. LTD. GAMEPAD,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n030000008f0e00000d31000010010000,SZMY-POWER CO. LTD. GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000008f0e00001431000010010000,SZMY-POWER CO. LTD. PS3 gamepad,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,\n030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004f0400000ed0000011010000,ThrustMaster eSwap PRO Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000b50700000399000000010000,Thrustmaster Firestorm Digital 2,a:b2,b:b4,back:b11,leftshoulder:b6,leftstick:b10,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b8,rightstick:b0,righttrigger:b9,start:b1,x:b3,y:b5,platform:Linux,\n030000004f04000003b3000010010000,Thrustmaster Firestorm Dual Analog 2,a:b0,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b9,rightx:a2,righty:a3,x:b1,y:b3,platform:Linux,\n030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,\n030000004f04000026b3000002040000,Thrustmaster Gamepad GP XID,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c6240000025b000002020000,Thrustmaster GPX Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000004f04000007d0000000010000,Thrustmaster T Mini Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,\n030000004f04000012b3000010010000,Thrustmaster vibrating gamepad,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,\n03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,\n03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,\n030000005e0400008e02000070050000,Torid,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000c01100000591000011010000,Torid,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,\n03000000790000000600000007010000,USB gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,\n03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,\n030000006f0e00000302000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n030000006f0e00000702000011010000,Victrix Pro Fight Stick for PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,\n05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,\n03000000791d00000103000010010000,Wii Classic Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n050000000d0f0000f600000001000000,Wireless HORIPAD Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,\n030000005e040000a102000014010000,Xbox 360 Wireless Receiver (XBOX),a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000d102000002010000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000fd02000030110000,Xbox One Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000050b000002090000,Xbox One Elite Series 2,a:b0,b:b1,back:b136,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005e040000ea02000000000000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005e040000ea02000001030000,Xbox One Wireless Controller (Model 1708),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000120b000001050000,Xbox Series Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n030000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000001050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n050000005e040000130b000005050000,Xbox Series Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,\n030000005e0400008e02000000010000,xbox360 Wireless EasySMX,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,\n03000000ac0500005b05000010010000,Xiaoji Gamesir-G3w,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,\n05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,\n03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,\nxinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,\n03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000120c0000101e000011010000,ZEROPLUS P4 Wired Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,\n03000000c0160000dc27000001010000,OnyxSoft Dual JoyDivision,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b6,leftshoulder:b4,rightshoulder:b5,dpup:-a1,dpdown:+a1,dpleft:-a0,dpright:+a0,\n"
  },
  {
    "path": "generate-icons.py",
    "content": "#!/usr/bin/env python2\n# Used to generate some icons\n# Requires inkscape and imagemagick pacages\n\nimport os, subprocess, colorsys\nfrom xml.etree import ElementTree as ET\n\nICODIR = \"./images/\"\t\t\t\t\t# Directory with icons\nCICONS = \"./images/controller-icons/\"\t# Directory controller-icons\nRECOLORS = {\t\t\t\t\t\t\t# Defines set of hue shifts for controller-icons\n\t# \"0\" : 0.0,\t# Green - original\n\t\"1\" : 0.3,\t\t# Blue\n\t\"2\" : 0.7,\t\t# Red\n\t\"3\" : 0.9,\t\t# Yellow\n\t\"4\" : 0.2,\t\t# Cyan\n\t\"5\" : 0.8,\t\t# Orange\n\t\"6\" : 0.5,\t\t# Purple\n}\n\n\n# Generate svg state icons\nfor size in (24, 256):\n\tfor state in ('alive', 'dead', 'error', 'unknown'):\n\t\tprint(\"scc-statusicon-%s.png\" % (state,))\n\t\tsubprocess.call([\n\t\t\t\"inkscape\",\n\t\t\t\"%s/scc-statusicon-%s.svg\" % (ICODIR, state),\n\t\t\t\"--export-area-page\",\n\t\t\t\"--export-png=%s/%sx%s/status/scc-%s.png\" % (ICODIR, size, size, state),\n\t\t\t\"--export-width=%s\" % (size,),\n\t\t\t\"--export-height=%s\" % (size,) ])\n\n\ndef html_to_rgb(html):\n\t\"\"\" Converts #rrggbbaa or #rrggbb to r, g, b,a in (0,1) ranges \"\"\"\n\thtml = html.strip(\"#\")\n\tif len(html) == 6:\n\t\thtml = html + \"ff\"\n\telif html == \"none\":\n\t\treturn 0, 0, 0, 0\n\telif len(html) != 8:\n\t\traise ValueError(\"Needs RRGGBB(AA) format, got '%s'\" % (html, ))\n\treturn tuple(( float(int(html[i:i+2],16)) / 255.0 for i in xrange(0, len(html), 2) ))\n\n\ndef rgb_to_html(r,g,b):\n\t\"\"\" Convets rgb back to html color code \"\"\"\n\treturn \"#\" + \"\".join(( \"%02x\" % int(x * 255) for x in (r,g,b) ))\n\n\ndef recolor(tree, add):\n\t\"\"\" Recursive part of recolor_strokes and recolor_background \"\"\"\n\tif 'id' in tree.attrib and \"overlay\" in tree.attrib['id']:\n\t\treturn\n\tfor child in tree:\n\t\tif 'style' in child.attrib:\n\t\t\tstyles = { a : b\n\t\t\t\tfor (a, b) in (\n\t\t\t\t\tx.split(\":\", 1)\n\t\t\t\t\tfor x in child.attrib['style'].split(';')\n\t\t\t\t\tif \":\" in x\n\t\t\t\t)}\n\t\t\tif \"fill\" in styles or \"stroke\" in styles:\n\t\t\t\tfor key in (\"fill\", \"stroke\"):\n\t\t\t\t\tif key in styles:\n\t\t\t\t\t\t# Convert color to HSV\n\t\t\t\t\t\tr,g,b,a = html_to_rgb(styles[key])\n\t\t\t\t\t\th,s,v = colorsys.rgb_to_hsv(r,g,b)\n\t\t\t\t\t\t# Shift hue\n\t\t\t\t\t\th += add\n\t\t\t\t\t\twhile h > 1.0 : h -= 1.0\n\t\t\t\t\t\t# Convert it back\n\t\t\t\t\t\tr,g,b = colorsys.hsv_to_rgb(h,s,v)\n\t\t\t\t\t\t# Store\n\t\t\t\t\t\tstyles[key] = rgb_to_html(r,g,b)\n\t\t\t\tchild.attrib[\"style\"] = \";\".join(( \":\".join((x,styles[x])) for x in styles ))\n\t\trecolor(child, add)\n\n# Generate different colors for controller icons\nET.register_namespace(\"\",\"http://www.w3.org/2000/svg\")\nfor tp in (\"sc\", \"scbt\", \"fake\", \"ds4\", \"hid\", \"rpad\"):\n\t# Read svg and parse it\n\tdata = file(\"%s/%s-0.svg\" % (CICONS, tp), \"r\").read()\n\t# Create recolored images\n\tfor key in RECOLORS:\n\t\ttree = ET.fromstring(data)\n\t\t# Walk recursively and recolor everything that has color\n\t\trecolor(tree, RECOLORS[key])\n\t\t\n\t\tout = \"%s/%s-%s.svg\" % (CICONS, tp, key)\n\t\tfile(out, \"w\").write(ET.tostring(tree))\n\t\tprint(out)\n"
  },
  {
    "path": "generate_svg.py",
    "content": "#!/usr/bin/env python2\nfrom scc.tools import _\n\nfrom scc.actions import Action, DPadAction, XYAction, MouseAction\nfrom scc.modifiers import ModeModifier, DoubleclickModifier\nfrom scc.special_actions import MenuAction\nfrom scc.parser import TalkingActionParser\nfrom scc.constants import SCButtons\nfrom scc.profile import Profile\nfrom scc.tools import nameof\nfrom scc.uinput import Rels\nfrom scc.gui.svg_widget import SVGEditor\nfrom scc.lib import IntEnum\nimport os\n\n\nclass Align(IntEnum):\n\tTOP  =    1 << 0\n\tBOTTOM =  1 << 1\n\tLEFT =    1 << 2\n\tRIGHT =   1 << 3\n\n\ndef find_image(name):\n\t# TODO: This\n\tfilename = \"images/\" + name + \".svg\"\n\tif os.path.exists(filename):\n\t\treturn filename\n\treturn None\n\n\nclass Line(object):\n\t\n\tdef __init__(self, icon, text):\n\t\tself.icons = [ icon ]\n\t\tself.text = text\n\t\n\t\n\tdef get_size(self, gen):\n\t\t# TODO: This\n\t\treturn gen.char_width * len(self.text), gen.line_height\n\t\n\t\n\tdef add_icon(self, icon):\n\t\tself.icons.append(icon)\n\t\treturn self\n\t\n\t\n\tdef to_string(self):\n\t\treturn \"%-10s: %s\" % (\",\".join([ x for x in self.icons if x ]), self.text)\n\n\nclass LineCollection(object):\n\t\"\"\" Allows calling add_icon on multiple lines at once \"\"\"\n\t\n\tdef __init__(self, *lines):\n\t\tself.lines = lines\n\t\n\t\n\tdef add_icon(self, icon):\n\t\tfor line in self.lines:\n\t\t\tline.add_icon(icon)\n\t\treturn self\n\n\nclass Box(object):\n\tPADDING = 5\n\tSPACING = 2\n\tMIN_WIDTH = 100\n\tMIN_HEIGHT = 50\n\t\n\tdef __init__(self, anchor_x, anchor_y, align, name,\n\t\t\tmin_width = MIN_WIDTH, min_height = MIN_HEIGHT):\n\t\tself.name = name\n\t\tself.lines = []\n\t\tself.anchor = anchor_x, anchor_y\n\t\tself.align = align\n\t\tself.min_height = min_height\n\t\tself.x, self.y = 0, 0\n\t\tself.min_width = min_width\n\t\tself.min_height = min_height\n\t\n\t\n\tdef to_string(self):\n\t\treturn \"--- %s ---\\n%s\\n\" % (\n\t\t\tself.name,\n\t\t\t\"\\n\".join([ x.to_string() for x in self.lines ])\n\t\t)\n\t\n\t\n\tdef add(self, icon, context, action):\n\t\tif not action: return LineCollection()\n\t\tif isinstance(action, ModeModifier):\n\t\t\tlines = [ self.add(icon, context, action.default) ]\n\t\t\tfor x in action.mods:\n\t\t\t\tlines.append( self.add(nameof(x), context, action.mods[x])\n\t\t\t\t\t\t.add_icon(icon) )\n\t\t\treturn LineCollection(*lines)\n\t\telif isinstance(action, DoubleclickModifier):\n\t\t\tlines = []\n\t\t\tif action.normalaction:\n\t\t\t\tlines.append( self.add(icon, context, action.normalaction) )\n\t\t\tif action.action:\n\t\t\t\tlines.append( self.add(\"DOUBLECLICK\", context, action.action)\n\t\t\t\t\t\t.add_icon(icon) )\n\t\t\tif action.holdaction:\n\t\t\t\tlines.append( self.add(\"HOLD\", context, action.holdaction)\n\t\t\t\t\t\t.add_icon(icon) )\n\t\t\treturn LineCollection(*lines)\n\t\t\n\t\taction = action.strip()\n\t\tif isinstance(action, MenuAction):\n\t\t\tif self.name == \"bcs\" and action.menu_id == \"Default.menu\":\n\t\t\t\t# Special case, this action is expected in every profile,\n\t\t\t\t# so there is no need to draw it here\n\t\t\t\treturn LineCollection()\n\t\telif isinstance(action, DPadAction):\n\t\t\treturn LineCollection(\n\t\t\t\tself.add(\"DPAD_UP\",    Action.AC_BUTTON, action.actions[0]),\n\t\t\t\tself.add(\"DPAD_DOWN\",  Action.AC_BUTTON, action.actions[1]),\n\t\t\t\tself.add(\"DPAD_LEFT\",  Action.AC_BUTTON, action.actions[2]),\n\t\t\t\tself.add(\"DPAD_RIGHT\", Action.AC_BUTTON, action.actions[3])\n\t\t\t)\n\t\telif isinstance(action, XYAction):\n\t\t\tif isinstance(action.x, MouseAction) and isinstance(action.y, MouseAction):\n\t\t\t\tif action.x.get_axis() in (Rels.REL_HWHEEL, Rels.REL_WHEEL):\n\t\t\t\t\t# Special case, pad bound to wheel\n\t\t\t\t\tline = Line(icon, _(\"Mouse Wheel\"))\n\t\t\t\t\tself.lines.append(line)\n\t\t\t\t\treturn line\t\n\t\t\treturn LineCollection(\n\t\t\t\tself.add(\"AXISX\",  Action.AC_BUTTON, action.x),\n\t\t\t\tself.add(\"AXISY\",  Action.AC_BUTTON, action.y)\n\t\t\t)\n\t\tline = Line(icon, action.describe(context))\n\t\tself.lines.append(line)\n\t\treturn line\n\t\n\t\n\tdef calculate(self, gen):\n\t\tself.width, self.height = self.min_width, 2 * self.PADDING\n\t\tself.icount = 0\n\t\tfor line in self.lines:\n\t\t\tlw, lh = line.get_size(gen)\n\t\t\tself.width, self.height = max(self.width, lw), self.height + lh + self.SPACING\n\t\t\tself.icount = max(self.icount, len(line.icons))\n\t\tself.width += 2 * self.PADDING + self.icount * (gen.line_height + self.SPACING)\n\t\tself.height = max(self.height, self.min_height)\n\t\t\n\t\tanchor_x, anchor_y = self.anchor\n\t\tif (self.align & Align.TOP) != 0:\n\t\t\tself.y = anchor_y\n\t\telif (self.align & Align.BOTTOM) != 0:\n\t\t\tself.y = gen.full_height - self.height - anchor_y\n\t\telse:\n\t\t\tself.y = (gen.full_height - self.height) / 2\n\t\t\n\t\tif (self.align & Align.LEFT) != 0:\n\t\t\tself.x = anchor_x\n\t\telif (self.align & Align.RIGHT) != 0:\n\t\t\tself.x = gen.full_width - self.width - anchor_x\n\t\telse:\n\t\t\tself.x = (gen.full_width - self.width) / 2\n\t\n\t\n\tdef place(self, gen, root):\n\t\te = SVGEditor.add_element(root, \"rect\",\n\t\t\tstyle = \"opacity:1;fill-opacity:0.0;stroke-width:2.0;\",\n\t\t\tfill=\"#000000\",\n\t\t\tstroke=\"#06a400\",\n\t\t\tid = \"box_%s\" % (self.name,),\n\t\t\twidth = self.width, height = self.height,\n\t\t\tx = self.x, y = self.y,\n\t\t)\n\t\t\n\t\ty = self.y + self.PADDING\n\t\tfor line in self.lines:\n\t\t\th = gen.line_height\n\t\t\tx = self.x + self.PADDING\n\t\t\tfor icon in line.icons:\n\t\t\t\timage = find_image(icon)\n\t\t\t\tif image:\n\t\t\t\t\tSVGEditor.add_element(root, \"image\", x = x, y = y,\n\t\t\t\t\t\tstyle = \"filter:url(#filterInvert)\",\n\t\t\t\t\t\twidth = h, height = h, href = image)\n\t\t\t\tx += h + self.SPACING\n\t\t\tx = self.x + self.PADDING + self.icount * (h + self.SPACING)\n\t\t\ty += h\n\t\t\ttxt = SVGEditor.add_element(root, \"text\", x = x, y = y,\n\t\t\t\tstyle = gen.label_template.attrib['style']\n\t\t\t)\n\t\t\tSVGEditor.set_text(txt, line.text)\n\t\t\ty += self.SPACING\n\t\n\t\n\tdef place_marker(self, gen, root):\n\t\tx1, y1 = self.x, self.y\n\t\tx2, y2 = x1 + self.width, y1 + self.height\n\t\tif self.align & (Align.LEFT | Align.RIGHT) == 0:\n\t\t\tedges = [ [ x2, y2 ], [ x1, y2 ] ]\n\t\telif self.align & Align.BOTTOM == Align.BOTTOM:\n\t\t\tif self.align & Align.LEFT != 0:\n\t\t\t\tedges = [ [ x2, y2 ], [ x1, y1 ] ]\n\t\t\telif self.align & Align.RIGHT != 0:\n\t\t\t\tedges = [ [ x2, y1 ], [ x1, y2 ] ]\n\t\telif self.align & Align.TOP == Align.TOP:\n\t\t\tif self.align & Align.LEFT != 0:\n\t\t\t\tedges = [ [ x2, y1 ], [ x2, y2 ] ]\n\t\t\telif self.align & Align.RIGHT != 0:\n\t\t\t\tedges = [ [ x1, y1 ], [ x1, y2 ] ]\n\t\telse:\n\t\t\tif self.align & Align.LEFT != 0:\n\t\t\t\tedges = [ [ x2, y1 ], [ x2, y2 ] ]\n\t\t\telif self.align & Align.RIGHT != 0:\n\t\t\t\tedges = [ [ x1, y1 ], [ x2, y2 ] ]\n\t\t\n\t\ttargets = SVGEditor.get_element(root, \"markers_%s\" % (self.name,))\n\t\tif targets is None:\n\t\t\treturn\n\t\ti = 0\n\t\tfor target in targets:\n\t\t\ttx, ty = float(target.attrib[\"cx\"]), float(target.attrib[\"cy\"])\n\t\t\ttry:\n\t\t\t\tedges[i] += [ tx, ty ]\n\t\t\t\ti += 1\n\t\t\texcept IndexError:\n\t\t\t\tbreak\n\t\tedges = [ i for i in edges if len(i) == 4]\n\t\t\n\t\tfor x1, y1, x2, y2 in edges:\n\t\t\te = SVGEditor.add_element(root, \"line\",\n\t\t\t\tstyle = \"opacity:1;stroke:#06a400;stroke-width:0.5;\",\n\t\t\t\t# id = \"box_%s_line0\" % (self.name,),\n\t\t\t\tx1 = x1, y1 = y1, x2 = x2, y2 = y2\n\t\t\t)\n\n\n\nclass Generator(object):\n\tPADDING = 10\n\t\n\tdef __init__(self):\n\t\tsvg = SVGEditor(file(\"images/binding-display.svg\").read())\n\t\tbackground = SVGEditor.get_element(svg, \"background\")\n\t\tself.label_template = SVGEditor.get_element(svg, \"label_template\")\n\t\tself.line_height = int(float(self.label_template.attrib.get(\"height\") or 8))\n\t\tself.char_width = int(float(self.label_template.attrib.get(\"width\") or 8))\n\t\tself.full_width = int(float(background.attrib.get(\"width\") or 800))\n\t\tself.full_height = int(float(background.attrib.get(\"height\") or 800))\n\t\t\n\t\tprofile = Profile(TalkingActionParser()).load(\"test.sccprofile\")\n\t\tboxes = []\n\t\t\n\t\t\n\t\tbox_bcs = Box(0, self.PADDING, Align.TOP, \"bcs\")\n\t\tbox_bcs.add(\"BACK\", Action.AC_BUTTON, profile.buttons.get(SCButtons.BACK))\n\t\tbox_bcs.add(\"C\", Action.AC_BUTTON, profile.buttons.get(SCButtons.C))\n\t\tbox_bcs.add(\"START\", Action.AC_BUTTON, profile.buttons.get(SCButtons.START))\n\t\tboxes.append(box_bcs)\n\t\t\n\t\t\n\t\tbox_left = Box(self.PADDING, self.PADDING, Align.LEFT | Align.TOP, \"left\",\n\t\t\tmin_height = self.full_height * 0.5,\n\t\t\tmin_width = self.full_width * 0.2)\n\t\tbox_left.add(\"LEFT\", Action.AC_TRIGGER, profile.triggers.get(profile.LEFT))\n\t\tbox_left.add(\"LB\", Action.AC_BUTTON, profile.buttons.get(SCButtons.LB))\n\t\tbox_left.add(\"LGRIP\", Action.AC_BUTTON, profile.buttons.get(SCButtons.LGRIP))\n\t\tbox_left.add(\"LPAD\", Action.AC_PAD, profile.pads.get(profile.LEFT))\n\t\tboxes.append(box_left)\n\t\t\n\t\t\n\t\tbox_right = Box(self.PADDING, self.PADDING, Align.RIGHT | Align.TOP, \"right\",\n\t\t\tmin_height = self.full_height * 0.5,\n\t\t\tmin_width = self.full_width * 0.2)\n\t\tbox_right.add(\"RIGHT\", Action.AC_TRIGGER, profile.triggers.get(profile.RIGHT))\n\t\tbox_right.add(\"RB\", Action.AC_BUTTON, profile.buttons.get(SCButtons.RB))\n\t\tbox_right.add(\"RGRIP\", Action.AC_BUTTON, profile.buttons.get(SCButtons.RGRIP))\n\t\tbox_right.add(\"RPAD\", Action.AC_PAD, profile.pads.get(profile.RIGHT))\n\t\tboxes.append(box_right)\n\t\t\n\t\t\n\t\tbox_abxy = Box(4 * self.PADDING, self.PADDING, Align.RIGHT | Align.BOTTOM, \"abxy\")\n\t\tbox_abxy.add(\"A\", Action.AC_BUTTON, profile.buttons.get(SCButtons.A))\n\t\tbox_abxy.add(\"B\", Action.AC_BUTTON, profile.buttons.get(SCButtons.B))\n\t\tbox_abxy.add(\"X\", Action.AC_BUTTON, profile.buttons.get(SCButtons.X))\n\t\tbox_abxy.add(\"Y\", Action.AC_BUTTON, profile.buttons.get(SCButtons.Y))\n\t\tboxes.append(box_abxy)\n\t\t\n\t\t\n\t\tbox_stick = Box(4 * self.PADDING, self.PADDING, Align.LEFT | Align.BOTTOM, \"stick\")\n\t\tbox_stick.add(\"STICK\", Action.AC_STICK, profile.stick)\n\t\tboxes.append(box_stick)\n\t\t\n\t\t\n\t\tw = int(float(background.attrib.get(\"width\") or 800))\n\t\th = int(float(background.attrib.get(\"height\") or 800))\n\t\t\n\t\troot = SVGEditor.get_element(svg, \"root\")\n\t\tfor b in boxes:\n\t\t\tb.calculate(self)\n\t\t\n\t\t# Set ABXY and Stick size & position\n\t\tbox_abxy.height = box_stick.height = self.full_height * 0.25\n\t\tbox_abxy.width = box_stick.width = self.full_width * 0.3\n\t\tbox_abxy.y = self.full_height - self.PADDING - box_abxy.height\n\t\tbox_stick.y = self.full_height - self.PADDING - box_stick.height\n\t\tbox_abxy.x = self.full_width - self.PADDING - box_abxy.width\n\t\t\n\t\tself.equal_width(box_left, box_right)\n\t\tself.equal_height(box_left, box_right)\n\t\t\n\t\tfor b in boxes:\n\t\t\tb.place_marker(self, root)\n\t\tfor b in boxes:\n\t\t\tb.place(self, root)\n\t\t\n\t\tfile(\"out.svg\", \"w\").write(svg.to_string())\n\t\n\t\n\t\n\tdef equal_width(self, *boxes):\n\t\t\"\"\" Sets width of all passed boxes to width of widest box \"\"\"\n\t\twidth = 0\n\t\tfor b in boxes: width = max(width, b.width)\n\t\tfor b in boxes:\n\t\t\tb.width = width\n\t\t\tif b.align & Align.RIGHT:\n\t\t\t\tb.x = self.full_width - b.width - self.PADDING\n\t\n\t\n\tdef equal_height(self, *boxes):\n\t\t\"\"\" Sets height of all passed boxes to height of tallest box \"\"\"\n\t\theight = 0\n\t\tfor b in boxes: height = max(height, b.height)\n\t\tfor b in boxes:\n\t\t\tb.height = height\n\n\nGenerator()\n"
  },
  {
    "path": "glade/about.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkDialog\" id=\"Dialog\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"title\" translatable=\"yes\">About SC-Controller</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"icon\">../images/sc-controller-small.svg</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"response\" handler=\"on_dialog_response\" swapped=\"no\"/>\n    <child internal-child=\"vbox\">\n      <object class=\"GtkBox\" id=\"vb1\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">2</property>\n        <child internal-child=\"action_area\">\n          <object class=\"GtkButtonBox\" id=\"vb2\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"layout_style\">end</property>\n            <child>\n              <object class=\"GtkButton\" id=\"btnClose\">\n                <property name=\"label\">gtk-close</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"use_stock\">True</property>\n                <signal name=\"clicked\" handler=\"on_dialog_response\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">False</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vb3\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">50</property>\n            <property name=\"margin_right\">50</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"margin_bottom\">10</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkImage\" id=\"image1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"pixbuf\">../images/sc-controller-small.svg</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">&lt;span size=\"large\"&gt;&lt;b&gt;SC-Controller&lt;/b&gt;&lt;/span&gt;</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"justify\">center</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblVersion\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"label\" translatable=\"yes\">(version)</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">20</property>\n                <property name=\"label\" translatable=\"yes\">User-mode driver and GTK3 based GUI for Steam Controller.</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"justify\">center</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_bottom\">25</property>\n                <property name=\"label\" translatable=\"yes\">&lt;b&gt;Code&amp;amp;UI © 2016 Kozec&lt;/b&gt;\n\nSource code available on &lt;a href=\"https://github.com/kozec/sc-controller\"&gt;GitHub&lt;/a&gt;\n\nI'd like to express my biggest thanks\nfor all my &lt;b&gt;&lt;a href=\"https://www.patreon.com/kozec\"&gt;Patreon&lt;/a&gt;&lt;/b&gt; supporters, with special mention of\n\n&lt;b&gt;guido haag&lt;/b&gt;\n&lt;b&gt;Orivej Desh&lt;/b&gt;\n&lt;b&gt;G. Wilson&lt;/b&gt;\n&lt;b&gt;Eric Duhamel&lt;/b&gt;\n&lt;b&gt;Enric Morales&lt;/b&gt;\n&lt;b&gt;Joshua M&lt;/b&gt;\n&lt;b&gt;Lutris&lt;/b&gt;\n&lt;b&gt;Taowa&lt;/b&gt;\nand\n&lt;b&gt;ambidot&lt;/b&gt;\n</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"justify\">center</property>\n                <property name=\"yalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">4</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"label\" translatable=\"yes\">&lt;small&gt;This program comes with absolutely no warranty.\nSee the &lt;a href=\"http://www.gnu.org/licenses/old-licenses/gpl-2.0.html\"&gt;GNU General Public License, version 2&lt;/a&gt; for details.&lt;/small&gt;</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"justify\">center</property>\n                <property name=\"yalign\">1</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">5</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child>\n      <placeholder/>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/action_editor.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjDeadzoneLower\">\n    <property name=\"upper\">32766</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjDeadzoneUpper\">\n    <property name=\"lower\">1</property>\n    <property name=\"upper\">32767</property>\n    <property name=\"value\">32767</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjFAmplitude\">\n    <property name=\"lower\">16</property>\n    <property name=\"upper\">32767</property>\n    <property name=\"value\">512</property>\n    <property name=\"step_increment\">32</property>\n    <property name=\"page_increment\">128</property>\n    <property name=\"page_size\">128</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjFFrequency\">\n    <property name=\"lower\">1</property>\n    <property name=\"upper\">100</property>\n    <property name=\"value\">4</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjFPeriod\">\n    <property name=\"lower\">16</property>\n    <property name=\"upper\">32767</property>\n    <property name=\"value\">1024</property>\n    <property name=\"step_increment\">32</property>\n    <property name=\"page_increment\">128</property>\n    <property name=\"page_size\">128</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjFriction\">\n    <property name=\"upper\">6</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjRotation\">\n    <property name=\"lower\">-180</property>\n    <property name=\"upper\">180</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSensitivityX\">\n    <property name=\"upper\">10</property>\n    <property name=\"value\">1</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">1</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSensitivityY\">\n    <property name=\"upper\">10</property>\n    <property name=\"value\">1</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">1</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSensitivityZ\">\n    <property name=\"upper\">10</property>\n    <property name=\"value\">1</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">1</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSmoothFilter\">\n    <property name=\"upper\">100</property>\n    <property name=\"value\">2</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSmoothLevel\">\n    <property name=\"lower\">2</property>\n    <property name=\"upper\">20</property>\n    <property name=\"value\">8</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">3</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSmoothWeight\">\n    <property name=\"lower\">0.10000000000000001</property>\n    <property name=\"upper\">1</property>\n    <property name=\"value\">0.75</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">0.29999999999999999</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstDPADType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Simple</col>\n        <col id=\"1\">dpad</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">8-Way</col>\n        <col id=\"1\">dpad8</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstDeadzoneMode\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Cut Down</col>\n        <col id=\"1\">CUT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Round Up</col>\n        <col id=\"1\">ROUND</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Linear</col>\n        <col id=\"1\">LINEAR</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Minimum Output Value</col>\n        <col id=\"1\">MINIMUM</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstFeedbackSide\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left</col>\n        <col id=\"1\">LEFT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right</col>\n        <col id=\"1\">RIGHT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Both</col>\n        <col id=\"1\">BOTH</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">650</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"on_Dialog_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_Dialog_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkGrid\" id=\"grEditor\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <child>\n          <object class=\"GtkEntry\" id=\"entName\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">2</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"activates_default\">True</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkStack\" id=\"stActionModes\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">20</property>\n            <property name=\"transition_type\">slide-right</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">3</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbActionStr\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">20</property>\n            <child>\n              <object class=\"GtkEntry\" id=\"entAction\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"editable\">False</property>\n                <property name=\"activates_default\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">6</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbActionButtons\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_top\">5</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"spacing\">4</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblActionType\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"label\" translatable=\"yes\">Action Type</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblName\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">50</property>\n            <property name=\"margin_bottom\">2</property>\n            <property name=\"label\" translatable=\"yes\">Action Name</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkExpander\" id=\"exMore\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_top\">5</property>\n            <property name=\"margin_bottom\">5</property>\n            <signal name=\"activate\" handler=\"on_exMore_activate\" swapped=\"no\"/>\n            <child>\n              <placeholder/>\n            </child>\n            <child type=\"label\">\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">More...</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">4</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvMore\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"transition_type\">slide-up</property>\n            <child>\n              <object class=\"GtkNotebook\" id=\"ntbMore\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"tab_pos\">left</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grRotation\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">5</property>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbOSD\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"xalign\">0</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label5\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Display OSD</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblRotationHeader\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_top\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Input Rotation</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblRotation\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Angle</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclRotation\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjRotation</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearRotation\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"margin_left\">5</property>\n                        <signal name=\"clicked\" handler=\"on_btClearRotation_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image9\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbRequireClick\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"xalign\">0</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label4\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Require Click</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbPreview\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"xalign\">0</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"on_cbPreview_toggled\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label2\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Preview Changes Immediately</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblFriction\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"margin_right\">5</property>\n                        <property name=\"label\" translatable=\"yes\">Friction</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">6</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclFriction\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjFriction</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"format-value\" handler=\"on_sclFriction_format_value\" swapped=\"no\"/>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">6</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearFriction\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"margin_left\">5</property>\n                        <signal name=\"clicked\" handler=\"on_btClearFriction_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image13\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">6</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbBallMode\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_top\">10</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblBallMode\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Trackball Mode</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">5</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"label49\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Basics</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"tab_fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grSensitivity\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">5</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSensitivityHeader\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Sensitivity</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSensX\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">X</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclSensX\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjSensitivityX</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearSensX\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearSens_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image1\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSensY\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Y</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclSensY\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjSensitivityY</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearSensY\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearSens_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image2\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSensZ\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Roll</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclSensZ\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjSensitivityZ</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearSensZ\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearSens_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image3\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbSensInvertX\">\n                        <property name=\"label\" translatable=\"yes\">Invert X axis</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">4</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbSensInvertY\">\n                        <property name=\"label\" translatable=\"yes\">Invert Y axis</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">5</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbSensInvertZ\">\n                        <property name=\"label\" translatable=\"yes\">Invert Roll</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"no_show_all\">True</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">6</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"label48\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Sensitivity</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">1</property>\n                    <property name=\"tab_fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grFeedback\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"row_spacing\">2</property>\n                    <property name=\"column_spacing\">10</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblFPeriod\">\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"no_show_all\">True</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Period</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbFeedbackSide\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"model\">lstFeedbackSide</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"nowthishastohaveidtoo\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                        <property name=\"width\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearFFrequency\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearFeedback_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image4\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearFAmplitude\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearFeedback_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image5\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclFPeriod\">\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"no_show_all\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjFPeriod</property>\n                        <property name=\"round_digits\">0</property>\n                        <property name=\"digits\">0</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearFPeriod\">\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"no_show_all\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearFeedback_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image6\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclFFrequency\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjFFrequency</property>\n                        <property name=\"inverted\">True</property>\n                        <property name=\"round_digits\">0</property>\n                        <property name=\"digits\">0</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"format-value\" handler=\"on_sclFFrequency_format_value\" swapped=\"no\"/>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclFAmplitude\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjFAmplitude</property>\n                        <property name=\"round_digits\">0</property>\n                        <property name=\"digits\">0</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblFFrequency\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Frequency</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblFAmplitude\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Strength</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblFeedbackSide\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Side</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbFeedback\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"xalign\">0</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label3\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Feedback Enabled</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"lblFeedbackOptions\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Feedback</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">2</property>\n                    <property name=\"tab_fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grDeadzone\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"row_spacing\">2</property>\n                    <property name=\"column_spacing\">10</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDZUpper\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Upper</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDZLower\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Lower</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclDZUpper\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjDeadzoneUpper</property>\n                        <property name=\"round_digits\">0</property>\n                        <property name=\"digits\">0</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclDZLower\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjDeadzoneLower</property>\n                        <property name=\"round_digits\">0</property>\n                        <property name=\"digits\">0</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearDZUpper\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearDeadzone_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image8\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearDZLower\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearDeadzone_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image7\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbDeadzone\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"xalign\">0</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label6\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Deadzone Enabled</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDeadzoneMode\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Mode</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbDeadzoneMode\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"model\">lstDeadzoneMode</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"nowthishastohaveidtoo1\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">3</property>\n                        <property name=\"width\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">3</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"label51\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Deadzone</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">3</property>\n                    <property name=\"tab_fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grSmoothing\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"row_spacing\">2</property>\n                    <property name=\"column_spacing\">10</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSmoothFilter\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Filter</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSmoothWeight\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Weight</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSmoothLevel\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Level</property>\n                        <property name=\"ellipsize\">end</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclSmoothFilter\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjSmoothFilter</property>\n                        <property name=\"round_digits\">0</property>\n                        <property name=\"digits\">0</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclSmoothWeight\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjSmoothWeight</property>\n                        <property name=\"round_digits\">2</property>\n                        <property name=\"digits\">2</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclSmoothLevel\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjSmoothLevel</property>\n                        <property name=\"round_digits\">0</property>\n                        <property name=\"digits\">0</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"value-changed\" handler=\"update_modifiers\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearSmoothFilter\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearSmoothing_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image12\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearSmoothWeight\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearSmoothing_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image11\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearSmoothLevel\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btClearSmoothing_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image10\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbSmoothing\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"xalign\">0</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"update_modifiers\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label7\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Smoothing Enabled</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">4</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"label42\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Smoothing</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">4</property>\n                    <property name=\"tab_fill\">False</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">5</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">50</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"margin_bottom\">7</property>\n            <property name=\"label\">(Widget)</property>\n            <property name=\"xalign\">0</property>\n            <property name=\"yalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">7</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btModeshift\">\n            <property name=\"label\" translatable=\"yes\">Mode Shift</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"clicked\" handler=\"on_btModeshift_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btMacro\">\n            <property name=\"label\" translatable=\"yes\">Macro</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"clicked\" handler=\"on_btMacro_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btInnerRing\">\n            <property name=\"label\" translatable=\"yes\">Ring Bindings</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"clicked\" handler=\"on_btInnerRing_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btOK\">\n            <property name=\"label\">gtk-ok</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btOK_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btClear\">\n            <property name=\"label\">gtk-clear</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btClear_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">6</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOutputMode\">\n    <columns>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">XY( axis(Axes.ABS_X), raxis(Axes.ABS_Y) )</col>\n        <col id=\"1\" translatable=\"yes\">Left Stick</col>\n        <col id=\"2\" translatable=\"yes\">lstick</col>\n      </row>\n      <row>\n        <col id=\"0\">XY( axis(Axes.ABS_RX), raxis(Axes.ABS_RY) )</col>\n        <col id=\"1\" translatable=\"yes\">Right Stick</col>\n        <col id=\"2\" translatable=\"yes\">rstick</col>\n      </row>\n      <row>\n        <col id=\"0\">XY( axis(Axes.ABS_HAT0X), raxis(Axes.ABS_HAT0Y) )</col>\n        <col id=\"1\" translatable=\"yes\">DPad</col>\n        <col id=\"2\" translatable=\"yes\">dpad</col>\n      </row>\n      <row>\n        <col id=\"0\">XY( mouse(Rels.REL_HWHEEL, sensitivity), mouse(Rels.REL_WHEEL, sensitivity) )</col>\n        <col id=\"1\" translatable=\"yes\">Mouse Wheel</col>\n        <col id=\"2\" translatable=\"yes\">wheel</col>\n      </row>\n      <row>\n        <col id=\"0\">trackpad(sensitivity)</col>\n        <col id=\"1\" translatable=\"yes\">Trackpad (Mouse)</col>\n        <col id=\"2\" translatable=\"yes\">trackpad</col>\n      </row>\n      <row>\n        <col id=\"0\">trackball(sensitivity)</col>\n        <col id=\"1\" translatable=\"yes\">Trackball (Mouse)</col>\n        <col id=\"2\" translatable=\"yes\">trackball</col>\n      </row>\n    </data>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/axis.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkGrid\" id=\"axis\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"label9\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">5</property>\n        <property name=\"margin_bottom\">5</property>\n        <property name=\"label\" translatable=\"yes\">click on axis or trigger</property>\n        <property name=\"xalign\">1</property>\n        <attributes>\n          <attribute name=\"style\" value=\"italic\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">1</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <placeholder/>\n    </child>\n    <child>\n      <placeholder/>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/axis_action.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjAreaX1\">\n    <property name=\"upper\">9999</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjAreaX2\">\n    <property name=\"upper\">9999</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjAreaY1\">\n    <property name=\"upper\">9999</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjAreaY2\">\n    <property name=\"upper\">9999</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjFriction\">\n    <property name=\"upper\">6</property>\n    <property name=\"value\">4</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSensitivity\">\n    <property name=\"lower\">0.01</property>\n    <property name=\"upper\">10.01</property>\n    <property name=\"value\">1</property>\n    <property name=\"step_increment\">0.01</property>\n    <property name=\"page_increment\">0.10000000000000001</property>\n    <property name=\"page_size\">0.01</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstAreaType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Screen Size</col>\n        <col id=\"1\">screensize</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Active Window Size</col>\n        <col id=\"1\">windowsize</col>\n      </row>\n      <row>\n        <col id=\"0\">-</col>\n        <col id=\"1\">-</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Top-Left Corner of Screen</col>\n        <col id=\"1\">screen-00</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Top-Right Corner of Screen</col>\n        <col id=\"1\">screen-01</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Bottom-Left Corner of Screen</col>\n        <col id=\"1\">screen-10</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Bottom-Right Corner of Screen</col>\n        <col id=\"1\">screen-11</col>\n      </row>\n      <row>\n        <col id=\"0\">-</col>\n        <col id=\"1\">-</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Top-Left Corner of Active Window</col>\n        <col id=\"1\">window-00</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Top-Right Corner of Active Window</col>\n        <col id=\"1\">window-01</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Bottom-Left Corner of Active Window</col>\n        <col id=\"1\">window-10</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Relative to Bottom-Right Corner of Active Window</col>\n        <col id=\"1\">window-11</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMouseOutput\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Mouse</col>\n        <col id=\"1\">mouse</col>\n        <col id=\"2\">mouse()</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Mouse-like Joystick on LEFT Stick</col>\n        <col id=\"1\">left</col>\n        <col id=\"2\">XY(axis(Axes.ABS_X),axis(Axes.ABS_Y))</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Mouse-like Joystick on RIGHT Stick</col>\n        <col id=\"1\">right</col>\n        <col id=\"2\">XY(axis(Axes.ABS_RX),axis(Axes.ABS_RY))</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOutputMode\">\n    <columns>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">None</col>\n        <col id=\"1\" translatable=\"yes\">None</col>\n        <col id=\"2\">none</col>\n      </row>\n      <row>\n        <col id=\"0\">XY( axis(Axes.ABS_X), raxis(Axes.ABS_Y) )</col>\n        <col id=\"1\" translatable=\"yes\">Left Stick</col>\n        <col id=\"2\">lstick</col>\n      </row>\n      <row>\n        <col id=\"0\">XY( axis(Axes.ABS_RX), raxis(Axes.ABS_RY) )</col>\n        <col id=\"1\" translatable=\"yes\">Right Stick</col>\n        <col id=\"2\">rstick</col>\n      </row>\n      <row>\n        <col id=\"0\">button(Keys.BTN_GAMEPAD)</col>\n        <col id=\"1\" translatable=\"yes\">Single Button</col>\n        <col id=\"2\">button</col>\n      </row>\n      <row>\n        <col id=\"0\">circular(Rels.REL_WHEEL)</col>\n        <col id=\"1\" translatable=\"yes\">Circular</col>\n        <col id=\"2\">circular</col>\n      </row>\n      <row>\n        <col id=\"0\">relXY( axis(Axes.ABS_RX), raxis(Axes.ABS_RY) )</col>\n        <col id=\"1\" translatable=\"yes\">Joystick Camera</col>\n        <col id=\"2\">rstick_rel</col>\n      </row>\n      <row>\n        <col id=\"0\">ball(XY( mouse(Rels.REL_HWHEEL), mouse(Rels.REL_WHEEL) ))</col>\n        <col id=\"1\" translatable=\"yes\">Mouse Wheel</col>\n        <col id=\"2\">wheel_pad</col>\n      </row>\n      <row>\n        <col id=\"0\">XY( mouse(Rels.REL_HWHEEL), mouse(Rels.REL_WHEEL) )</col>\n        <col id=\"1\" translatable=\"yes\">Mouse Wheel</col>\n        <col id=\"2\">wheel_stick</col>\n      </row>\n      <row>\n        <col id=\"0\">area(0, 0, 100, 100)</col>\n        <col id=\"1\" translatable=\"yes\">Mouse Region</col>\n        <col id=\"2\">area</col>\n      </row>\n      <row>\n        <col id=\"0\">mouse()</col>\n        <col id=\"1\" translatable=\"yes\">Mouse</col>\n        <col id=\"2\">mouse_stick</col>\n      </row>\n      <row>\n        <col id=\"0\">XY(mouseabs(Rels.REL_X), mouseabs(Rels.REL_Y))</col>\n        <col id=\"1\" translatable=\"yes\">Mouse (Emulate Stick)</col>\n        <col id=\"2\">mouse_pad</col>\n      </row>\n      <row>\n        <col id=\"0\">mouse()</col>\n        <col id=\"1\" translatable=\"yes\">Mouse (Trackpad or Trackball)</col>\n        <col id=\"2\">mouse</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkBox\" id=\"axis_action\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"label5\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"label\" translatable=\"yes\">Output</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbAxisOutput\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">20</property>\n        <property name=\"margin_right\">20</property>\n        <property name=\"model\">lstOutputMode</property>\n        <property name=\"active\">1</property>\n        <signal name=\"changed\" handler=\"on_cbAxisOutput_changed\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"text\"/>\n          <attributes>\n            <attribute name=\"text\">1</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkStack\" id=\"stActionData\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"transition_type\">crossfade</property>\n        <child>\n          <object class=\"GtkFixed\" id=\"nothing\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n          </object>\n          <packing>\n            <property name=\"name\">page4</property>\n            <property name=\"title\" translatable=\"yes\">page4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbButton\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label91\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"label\" translatable=\"yes\">Button</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btSingleButton\">\n                <property name=\"label\" translatable=\"yes\">A</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">150</property>\n                <property name=\"margin_right\">150</property>\n                <signal name=\"clicked\" handler=\"on_btSingleButton_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page21</property>\n            <property name=\"title\" translatable=\"yes\">page21</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grArea\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblArea\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Region Options</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label_n1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"label\" translatable=\"yes\">Type</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbAreaType\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"model\">lstAreaType</property>\n                <property name=\"active\">1</property>\n                <signal name=\"changed\" handler=\"on_area_options_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"text1\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label_n2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"label\" translatable=\"yes\">Coordinates</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkBox\" id=\"vbAreaOptions\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"homogeneous\">True</property>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbAreaClickEnabled\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">False</property>\n                    <property name=\"draw_indicator\">True</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label_n3\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Pressing the Pad Clicks</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbAreaOSDEnabled\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">False</property>\n                    <property name=\"draw_indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbAreaOSDEnabled_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label_n4\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Display Area While Pad is Being Touched</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grCoordinates\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label_n6\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"label\" translatable=\"yes\">X1</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSpinButton\" id=\"sbAreaX1\">\n                    <property name=\"width_request\">150</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"text\" translatable=\"yes\">0</property>\n                    <property name=\"adjustment\">adjAreaX1</property>\n                    <signal name=\"focus-out-event\" handler=\"on_sbArea_focus_out_event\" swapped=\"no\"/>\n                    <signal name=\"output\" handler=\"on_sbArea_output\" swapped=\"no\"/>\n                    <signal name=\"value-changed\" handler=\"on_sbArea_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label_n9\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"label\" translatable=\"yes\">X2</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSpinButton\" id=\"sbAreaX2\">\n                    <property name=\"width_request\">150</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"text\" translatable=\"yes\">0</property>\n                    <property name=\"adjustment\">adjAreaX2</property>\n                    <signal name=\"focus-out-event\" handler=\"on_sbArea_focus_out_event\" swapped=\"no\"/>\n                    <signal name=\"output\" handler=\"on_sbArea_output\" swapped=\"no\"/>\n                    <signal name=\"value-changed\" handler=\"on_sbArea_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">3</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label_n10\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"label\" translatable=\"yes\">Y1</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">4</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSpinButton\" id=\"sbAreaY1\">\n                    <property name=\"width_request\">150</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"text\" translatable=\"yes\">0</property>\n                    <property name=\"adjustment\">adjAreaY1</property>\n                    <signal name=\"focus-out-event\" handler=\"on_sbArea_focus_out_event\" swapped=\"no\"/>\n                    <signal name=\"output\" handler=\"on_sbArea_output\" swapped=\"no\"/>\n                    <signal name=\"value-changed\" handler=\"on_sbArea_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">5</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label_n15\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"label\" translatable=\"yes\">Y2</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">6</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSpinButton\" id=\"sbAreaY2\">\n                    <property name=\"width_request\">150</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">10</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"text\" translatable=\"yes\">0</property>\n                    <property name=\"adjustment\">adjAreaY2</property>\n                    <signal name=\"focus-out-event\" handler=\"on_sbArea_focus_out_event\" swapped=\"no\"/>\n                    <signal name=\"output\" handler=\"on_sbArea_output\" swapped=\"no\"/>\n                    <signal name=\"value-changed\" handler=\"on_sbArea_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">7</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page0</property>\n            <property name=\"title\" translatable=\"yes\">page0</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbMose\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"label\" translatable=\"yes\">Output As</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbMouseOutput\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <property name=\"model\">lstMouseOutput</property>\n                <property name=\"active\">0</property>\n                <signal name=\"changed\" handler=\"on_mouse_options_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"text2\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblSetupTrackball\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"label\" translatable=\"yes\">&lt;a href=\"advanced://0#cbBallMode\"&gt;Setup or disable Trackball Mode&lt;/a&gt;</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"xalign\">0</property>\n                <signal name=\"activate-link\" handler=\"on_lblSetupTrackball_activate_link\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page1</property>\n            <property name=\"title\" translatable=\"yes\">page1</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grCircular\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"hexpand\">False</property>\n            <child>\n              <object class=\"GtkButton\" id=\"btClearCircularAxis\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">100</property>\n                <signal name=\"clicked\" handler=\"on_btClearCircularAxis_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkImage\" id=\"image1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"stock\">gtk-clear</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btClearCircularButtons\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">100</property>\n                <signal name=\"clicked\" handler=\"on_btClearCircularButtons_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkImage\" id=\"image2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"stock\">gtk-clear</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"hexpand\">False</property>\n                <property name=\"label\" translatable=\"yes\">Output Axis</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"hexpand\">False</property>\n                <property name=\"label\" translatable=\"yes\">Output Buttons</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btCircularAxis\">\n                <property name=\"label\" translatable=\"yes\">Mouse Wheel</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">100</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btCircularAxis_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btCircularButton0\">\n                <property name=\"label\" translatable=\"yes\">(not set)</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">100</property>\n                <property name=\"margin_right\">5</property>\n                <signal name=\"clicked\" handler=\"on_btCircularButton_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btCircularButton1\">\n                <property name=\"label\" translatable=\"yes\">(not set)</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">5</property>\n                <signal name=\"clicked\" handler=\"on_btCircularButton_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">grCircular</property>\n            <property name=\"title\" translatable=\"yes\">page2</property>\n            <property name=\"position\">4</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">2</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/buttons.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkBox\" id=\"buttons\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <placeholder/>\n    </child>\n    <child>\n      <object class=\"GtkBox\" id=\"vbWeirdSetOfBoxes1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbRandomBox48\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbRepeat\">\n                <property name=\"label\" translatable=\"yes\">Repeat</property>\n                <property name=\"width_request\">150</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"valign\">end</property>\n                <property name=\"xalign\">0</property>\n                <property name=\"draw_indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_cbRepeat_toggled\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"pack_type\">end</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbToggle\">\n                <property name=\"label\" translatable=\"yes\">Toggle Button</property>\n                <property name=\"width_request\">150</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"valign\">end</property>\n                <property name=\"xalign\">0</property>\n                <property name=\"draw_indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_cbToggle_toggled\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"pack_type\">end</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbWeirdSetOfBoxes2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblClickAnyButton\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"label\" translatable=\"yes\">click any button or...</property>\n                <attributes>\n                  <attribute name=\"style\" value=\"italic\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkBox\" id=\"vbWeirdSetOfBoxes3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkButton\" id=\"btnGrabKey\">\n                    <property name=\"label\" translatable=\"yes\">Grab a key</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btnGrabKey_clicked\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btnGrabAnother\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"halign\">start</property>\n                    <property name=\"margin_left\">5</property>\n                    <signal name=\"clicked\" handler=\"on_btnGrabAnother_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"imgPlus\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-add</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblPad\">\n            <property name=\"width_request\">150</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"pack_type\">end</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/custom.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.19.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkTextBuffer\" id=\"tbCustomAction\">\n    <signal name=\"changed\" handler=\"on_tbCustomAction_changed\" swapped=\"no\"/>\n  </object>\n  <object class=\"GtkBox\" id=\"custom\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"label4\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Custom Action</property>\n        <property name=\"max_width_chars\">4</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkScrolledWindow\" id=\"scrolledwindow1\">\n        <property name=\"height_request\">150</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"margin_top\">5</property>\n        <property name=\"shadow_type\">in</property>\n        <child>\n          <object class=\"GtkTextView\" id=\"txCustomAction\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"pixels_above_lines\">5</property>\n            <property name=\"pixels_below_lines\">5</property>\n            <property name=\"left_margin\">5</property>\n            <property name=\"right_margin\">5</property>\n            <property name=\"buffer\">tbCustomAction</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">True</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/dpad.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjDiagonalRange\">\n    <property name=\"lower\">1</property>\n    <property name=\"upper\">89</property>\n    <property name=\"value\">45</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjMenuPosX\">\n    <property name=\"upper\">4096</property>\n    <property name=\"value\">10</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjMenuPosY\">\n    <property name=\"upper\">4096</property>\n    <property name=\"value\">10</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjMenuSize\">\n    <property name=\"upper\">100</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">2</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstButton\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name button -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">(Controller Default)</col>\n        <col id=\"1\">DEFAULT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">A</col>\n        <col id=\"1\">A</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">B</col>\n        <col id=\"1\">B</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">X</col>\n        <col id=\"1\">X</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Y</col>\n        <col id=\"1\">Y</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Back (select)</col>\n        <col id=\"1\">BACK</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Center</col>\n        <col id=\"1\">C</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Start</col>\n        <col id=\"1\">START</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Grip</col>\n        <col id=\"1\">LGRIP</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Grip</col>\n        <col id=\"1\">RGRIP</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Bumper</col>\n        <col id=\"1\">LB</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Bumper</col>\n        <col id=\"1\">RB</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Trigger</col>\n        <col id=\"1\">LT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Trigger</col>\n        <col id=\"1\">RT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Stick Press</col>\n        <col id=\"1\">STICKPRESS</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Pad Press</col>\n        <col id=\"1\">LPAD</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Pad Press</col>\n        <col id=\"1\">RPAD</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenu\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenuPosX\">\n    <columns>\n      <!-- column-name id -->\n      <column type=\"gint\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">1</col>\n        <col id=\"1\" translatable=\"yes\">From Left</col>\n      </row>\n      <row>\n        <col id=\"0\">-1</col>\n        <col id=\"1\" translatable=\"yes\">From Right</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenuPosY\">\n    <columns>\n      <!-- column-name id -->\n      <column type=\"gint\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">1</col>\n        <col id=\"1\" translatable=\"yes\">From Top</col>\n      </row>\n      <row>\n        <col id=\"0\">-1</col>\n        <col id=\"1\" translatable=\"yes\">From Bottom</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenuType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">List</col>\n        <col id=\"1\">menu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Grid</col>\n        <col id=\"1\">gridmenu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Radial</col>\n        <col id=\"1\">radialmenu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Horizontal Menu</col>\n        <col id=\"1\">hmenu</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOutType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Simple DPAD</col>\n        <col id=\"1\">dpad</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">8-Way DPAD</col>\n        <col id=\"1\">dpad8</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">WSAD (DPAD)</col>\n        <col id=\"1\">wsad</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Arrows (DPAD)</col>\n        <col id=\"1\">arrows</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Emulated DPAD</col>\n        <col id=\"1\">actual_dpad</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Menu</col>\n        <col id=\"1\">menu</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkGrid\" id=\"dpad\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkStack\" id=\"stActionData\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"vexpand\">True</property>\n        <property name=\"transition_type\">crossfade</property>\n        <child>\n          <object class=\"GtkGrid\" id=\"grDPAD\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"row_spacing\">5</property>\n            <property name=\"column_spacing\">5</property>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD2\">\n                <property name=\"name\">2</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkBox\" id=\"box1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image2\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-go-back</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkSeparator\" id=\"separator3\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDPAD2\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">(...)</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD1\">\n                <property name=\"name\">1</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkBox\" id=\"box2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image3\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-go-down</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkSeparator\" id=\"separator4\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDPAD1\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">(...)</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD0\">\n                <property name=\"name\">0</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkBox\" id=\"box6\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image4\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-go-up</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkSeparator\" id=\"separator5\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDPAD0\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">(...)</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD3\">\n                <property name=\"name\">3</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkBox\" id=\"box7\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDPAD3\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">(...)</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkSeparator\" id=\"separator6\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image5\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-go-forward</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD4\">\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n                <property name=\"name\">4</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD5\">\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n                <property name=\"name\">5</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD6\">\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n                <property name=\"name\">6</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btDPAD7\">\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n                <property name=\"name\">7</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_btDPAD_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkBox\" id=\"vbDiagonalRange\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">5</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblDiagonalRange\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Diagonal Range</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScale\" id=\"sclDiagonalRange\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_left\">10</property>\n                    <property name=\"margin_right\">10</property>\n                    <property name=\"adjustment\">adjDiagonalRange</property>\n                    <property name=\"round_digits\">1</property>\n                    <property name=\"digits\">0</property>\n                    <property name=\"value_pos\">right</property>\n                    <signal name=\"format-value\" handler=\"on_sclDiagonalRange_format_value\" swapped=\"no\"/>\n                    <signal name=\"value-changed\" handler=\"on_sclDiagonalRange_value_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">True</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearDiagonalRange\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btClearDiagonalRange_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image10\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page0</property>\n            <property name=\"title\" translatable=\"yes\">page0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grMenu\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_top\">40</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"label\" translatable=\"yes\">Menu</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbMenus\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_right\">5</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"model\">lstMenu</property>\n                <signal name=\"button-press-event\" handler=\"on_cbMenus_button_press_event\" swapped=\"no\"/>\n                <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"cellrenderertext3\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btEditMenu\">\n                <property name=\"label\">gtk-edit</property>\n                <property name=\"visible\">True</property>\n                <property name=\"sensitive\">False</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"use_stock\">True</property>\n                <signal name=\"clicked\" handler=\"on_btEditMenu_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"label\" translatable=\"yes\">Menu Type</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbMenuType\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"model\">lstMenuType</property>\n                <property name=\"active\">0</property>\n                <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"cellrenderertext2\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvMenuSize\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbMenuSize\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblMenuSize\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_top\">10</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"label\" translatable=\"yes\">Items per row</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkSpinButton\" id=\"spMenuSize\">\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"no_show_all\">True</property>\n                        <property name=\"adjustment\">adjMenuSize</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <signal name=\"output\" handler=\"on_spMenuSize_format_value\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclMenuSize\">\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"no_show_all\">True</property>\n                        <property name=\"adjustment\">adjMenuSize</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                        <signal name=\"format-value\" handler=\"on_sclMenuSize_format_value\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">4</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkExpander\" id=\"exMenuControl\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_top\">10</property>\n                <signal name=\"activate\" handler=\"on_exMenuControl_activate\" swapped=\"no\"/>\n                <child>\n                  <placeholder/>\n                </child>\n                <child type=\"label\">\n                  <object class=\"GtkLabel\" id=\"label67\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Control Options...</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">5</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvMenuControl\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grMenuControl\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">20</property>\n                    <property name=\"margin_right\">20</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblConfirmWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_right\">20</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"label\" translatable=\"yes\">Confirm Button</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblCancelWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_right\">20</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"label\" translatable=\"yes\">Cancel Button</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbConfirmWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"model\">lstButton</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"cellrenderertext6\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbCancelWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"model\">lstButton</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"cellrenderertext7\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbMenuAutoConfirm\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <signal name=\"toggled\" handler=\"prevent_confirm_cancel_nonsense\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblMenuAutoConfirm\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Confirm selection when pad is released</property>\n                            <property name=\"xalign\">0</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                        <property name=\"width\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbMenuAutoCancel\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <signal name=\"toggled\" handler=\"prevent_confirm_cancel_nonsense\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblMenuAutoCancel\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Cancel menu when pad is released</property>\n                            <property name=\"xalign\">0</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">4</property>\n                        <property name=\"width\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbMenuConfirmWithClick\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <signal name=\"toggled\" handler=\"prevent_confirm_cancel_nonsense\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblMenuConfirmWithClick\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Confirm selection by clicking the pad</property>\n                            <property name=\"xalign\">0</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                        <property name=\"width\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">6</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkExpander\" id=\"exMenuPosition\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_top\">10</property>\n                <signal name=\"activate\" handler=\"on_exMenuPosition_activate\" swapped=\"no\"/>\n                <child>\n                  <placeholder/>\n                </child>\n                <child type=\"label\">\n                  <object class=\"GtkLabel\" id=\"label8\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Menu Position...</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">7</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvMenuPosition\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grid7\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">20</property>\n                    <property name=\"margin_right\">20</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <child>\n                      <object class=\"GtkSpinButton\" id=\"spMenuPosX\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjMenuPosX</property>\n                        <signal name=\"value-changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkSpinButton\" id=\"spMenuPosY\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjMenuPosY</property>\n                        <signal name=\"value-changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbMenuPosX\">\n                        <property name=\"width_request\">200</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">20</property>\n                        <property name=\"model\">lstMenuPosX</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"crMenuPosX\"/>\n                          <attributes>\n                            <attribute name=\"text\">1</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbMenuPosY\">\n                        <property name=\"width_request\">200</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">20</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"model\">lstMenuPosY</property>\n                        <property name=\"active\">1</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"crMenuPosY\"/>\n                          <attributes>\n                            <attribute name=\"text\">1</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">8</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page1</property>\n            <property name=\"title\" translatable=\"yes\">page1</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">1</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label12\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_right\">20</property>\n        <property name=\"label\" translatable=\"yes\">Output Type</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbActionType\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"model\">lstOutType</property>\n        <property name=\"active\">0</property>\n        <signal name=\"changed\" handler=\"on_cbActionType_changed\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"cellrenderertext1\"/>\n          <attributes>\n            <attribute name=\"text\">0</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuMenu\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuNew\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_New Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuNew_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuCopy\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Copy Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuCopy_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuRename\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Rename Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuRename_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuDelete\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Delete Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuDelete_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/first_page.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkGrid\" id=\"first_page\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"lblMarkup\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">20</property>\n        <property name=\"label\" translatable=\"yes\">(markup goes here)</property>\n        <property name=\"use_markup\">True</property>\n        <property name=\"track_visited_links\">False</property>\n        <property name=\"xalign\">0</property>\n        <property name=\"yalign\">0</property>\n        <signal name=\"activate-link\" handler=\"on_lblMarkup_activate_link\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/gesture.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjPrecision\">\n    <property name=\"upper\">1</property>\n    <property name=\"value\">1</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">0.10000000000000001</property>\n  </object>\n  <object class=\"GtkImage\" id=\"imgApply\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"stock\">gtk-apply</property>\n  </object>\n  <object class=\"GtkImage\" id=\"imgChangeGesture\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"stock\">gtk-edit</property>\n  </object>\n  <object class=\"GtkGrid\" id=\"gesture_editor\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkEntry\" id=\"txGesture\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"editable\">False</property>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btChangeGesture\">\n        <property name=\"label\" translatable=\"yes\">_Change</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"image\">imgChangeGesture</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"clicked\" handler=\"on_btChangeGesture_clicked\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkCheckButton\" id=\"cbIgnoreStroke\">\n        <property name=\"label\" translatable=\"yes\">Ignore Stroke _Length</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">False</property>\n        <property name=\"margin_top\">5</property>\n        <property name=\"use_underline\">True</property>\n        <property name=\"draw_indicator\">True</property>\n        <signal name=\"toggled\" handler=\"on_cbIgnoreStroke_toggled\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">1</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n  </object>\n  <object class=\"GtkImage\" id=\"imgStartOver\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"stock\">gtk-refresh</property>\n  </object>\n  <object class=\"GtkWindow\" id=\"gesture_grabber\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">gesture_grabber</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"modal\">True</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"type_hint\">dialog</property>\n    <property name=\"skip_taskbar_hint\">True</property>\n    <property name=\"skip_pager_hint\">True</property>\n    <property name=\"urgency_hint\">True</property>\n    <child>\n      <object class=\"GtkGrid\" id=\"grid2\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"vexpand\">True</property>\n        <child>\n          <object class=\"GtkEntry\" id=\"txGestureGrab\">\n            <property name=\"width_request\">400</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_top\">20</property>\n            <property name=\"margin_bottom\">20</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"editable\">False</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvGestureGrab\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkBox\" id=\"box4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblGestureStatus\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btnStartGestureOver\">\n                    <property name=\"label\" translatable=\"yes\">Start _Over (Y)</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"image\">imgStartOver</property>\n                    <property name=\"use_underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btnConfirmGesutre\">\n                    <property name=\"label\" translatable=\"yes\">_Use (A)</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"margin_left\">10</property>\n                    <property name=\"image\">imgApply</property>\n                    <property name=\"use_underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"hb1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <placeholder/>\n        </child>\n        <child type=\"title\">\n          <object class=\"GtkLabel\" id=\"lblGestureGrabberTitle\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Draw gesture on LEFT pad...</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstGestures\">\n    <columns>\n      <!-- column-name gesture -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n      <!-- column-name actionobj -->\n      <column type=\"GObject\"/>\n    </columns>\n  </object>\n  <object class=\"GtkBox\" id=\"gesture\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <object class=\"GtkBox\" id=\"vbPrecision\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_bottom\">5</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblPrecision\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Matching Precision</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkScale\" id=\"sclPrecision\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"adjustment\">adjPrecision</property>\n            <property name=\"round_digits\">2</property>\n            <property name=\"value_pos\">right</property>\n            <signal name=\"format-value\" handler=\"on_sclPrecision_format_value\" swapped=\"no\"/>\n            <signal name=\"value-changed\" handler=\"on_sclPrecision_value_changed\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btClearTolerance\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"clicked\" handler=\"on_btClearTolerance_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkImage\" id=\"imgBtClearTolerance\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-clear</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"lblGestureMessage\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"no_show_all\">True</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"label\" translatable=\"yes\">message</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"semibold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkScrolledWindow\" id=\"scGestures\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"shadow_type\">in</property>\n        <child>\n          <object class=\"GtkTreeView\" id=\"tvGestures\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"model\">lstGestures</property>\n            <signal name=\"cursor-changed\" handler=\"on_tvGestures_cursor_changed\" swapped=\"no\"/>\n            <signal name=\"row-activated\" handler=\"on_btEditAction_clicked\" swapped=\"no\"/>\n            <child internal-child=\"selection\">\n              <object class=\"GtkTreeSelection\" id=\"tvGesturesSelection\"/>\n            </child>\n            <child>\n              <object class=\"GtkTreeViewColumn\" id=\"tvcGesture\">\n                <property name=\"title\" translatable=\"yes\">Gesture</property>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"crGesture\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n              </object>\n            </child>\n            <child>\n              <object class=\"GtkTreeViewColumn\" id=\"tvcAction\">\n                <property name=\"title\" translatable=\"yes\">Action</property>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"crAction\"/>\n                  <attributes>\n                    <attribute name=\"text\">1</attribute>\n                  </attributes>\n                </child>\n              </object>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">True</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkToolbar\" id=\"tbGestures\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <child>\n          <object class=\"GtkToolButton\" id=\"btEditAction\">\n            <property name=\"visible\">True</property>\n            <property name=\"sensitive\">False</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"is_important\">True</property>\n            <property name=\"label\" translatable=\"yes\">Edit</property>\n            <property name=\"use_underline\">True</property>\n            <property name=\"stock_id\">gtk-edit</property>\n            <signal name=\"clicked\" handler=\"on_btEditAction_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"homogeneous\">True</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkSeparatorToolItem\" id=\"separator45\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"draw\">False</property>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"homogeneous\">False</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkToolButton\" id=\"btRemove\">\n            <property name=\"visible\">True</property>\n            <property name=\"sensitive\">False</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">_Remove</property>\n            <property name=\"use_underline\">True</property>\n            <property name=\"stock_id\">gtk-remove</property>\n            <signal name=\"clicked\" handler=\"on_btRemove_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"homogeneous\">False</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkToolButton\" id=\"btAdd\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">_Add</property>\n            <property name=\"use_underline\">True</property>\n            <property name=\"stock_id\">gtk-add</property>\n            <signal name=\"clicked\" handler=\"on_btAdd_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"homogeneous\">False</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">3</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/gyro.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjSoftLevel\">\n    <property name=\"lower\">0.01</property>\n    <property name=\"upper\">1</property>\n    <property name=\"value\">0.69999999999999996</property>\n    <property name=\"step_increment\">0.01</property>\n    <property name=\"page_increment\">0.01</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstGyroButton\">\n    <columns>\n      <!-- column-name value -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkBox\" id=\"gyro\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"margin_left\">30</property>\n    <property name=\"margin_right\">30</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <object class=\"GtkGrid\" id=\"whatthefuckubuntu\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"row_spacing\">2</property>\n        <property name=\"column_spacing\">10</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btPitch\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_select_axis\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkBox\" id=\"box8\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label6\">\n                    <property name=\"width_request\">100</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Pitch</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSeparator\" id=\"separator7\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblPitch\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">(...)</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">True</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btYaw\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_select_axis\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkBox\" id=\"box9\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label8\">\n                    <property name=\"width_request\">100</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Yaw</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSeparator\" id=\"separator8\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblYaw\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">(...)</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">True</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btRoll\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_select_axis\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkBox\" id=\"box1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label1\">\n                    <property name=\"width_request\">100</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Roll</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSeparator\" id=\"separator1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblRoll\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">(...)</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">True</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkCheckButton\" id=\"cbPitchAbs\">\n            <property name=\"label\" translatable=\"yes\">Absolute</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">False</property>\n            <property name=\"draw_indicator\">True</property>\n            <signal name=\"toggled\" handler=\"on_abs_changed\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkCheckButton\" id=\"cbYawAbs\">\n            <property name=\"label\" translatable=\"yes\">Absolute</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">False</property>\n            <property name=\"draw_indicator\">True</property>\n            <signal name=\"toggled\" handler=\"on_abs_changed\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkCheckButton\" id=\"cbRollAbs\">\n            <property name=\"label\" translatable=\"yes\">Absolute</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">False</property>\n            <property name=\"draw_indicator\">True</property>\n            <signal name=\"toggled\" handler=\"on_abs_changed\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkBox\" id=\"ihaveid\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">20</property>\n        <property name=\"spacing\">10</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblGyroEnable\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Gyro Enable Button</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkComboBox\" id=\"cbGyroButton\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"model\">lstGyroButton</property>\n            <property name=\"active\">1</property>\n            <signal name=\"changed\" handler=\"send\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkCellRendererText\" id=\"text2\"/>\n              <attributes>\n                <attribute name=\"text\">1</attribute>\n              </attributes>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkRevealer\" id=\"rvSoftLevel\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">5</property>\n        <property name=\"margin_bottom\">10</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbSoftLevel\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblSoftLevel\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"label\" translatable=\"yes\">Trigger Pull Level</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScale\" id=\"sclSoftLevel\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_right\">20</property>\n                <property name=\"adjustment\">adjSoftLevel</property>\n                <property name=\"round_digits\">2</property>\n                <property name=\"value_pos\">right</property>\n                <signal name=\"format-value\" handler=\"on_sclSoftLevel_format_value\" swapped=\"no\"/>\n                <signal name=\"value-changed\" handler=\"send\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkCheckButton\" id=\"cbInvertGyro\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">False</property>\n        <property name=\"draw_indicator\">True</property>\n        <signal name=\"toggled\" handler=\"on_cbInvertGyro_toggled\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkLabel\" id=\"label9414\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Button disables Gyro</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">3</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/gyro_action.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjSoftLevel\">\n    <property name=\"lower\">0.01</property>\n    <property name=\"upper\">1</property>\n    <property name=\"value\">0.69999999999999996</property>\n    <property name=\"step_increment\">0.01</property>\n    <property name=\"page_increment\">0.01</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstGyroButton\">\n    <columns>\n      <!-- column-name value -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOutputMode\">\n    <columns>\n      <!-- column-name value -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">None</col>\n        <col id=\"1\" translatable=\"yes\">None</col>\n        <col id=\"2\">none</col>\n      </row>\n      <row>\n        <col id=\"0\">mouse([YAW|ROLL])</col>\n        <col id=\"1\" translatable=\"yes\">Mouse (Desktop)</col>\n        <col id=\"2\">mouse</col>\n      </row>\n      <row>\n        <col id=\"0\">mouse([YAW|ROLL])</col>\n        <col id=\"1\" translatable=\"yes\">Mouse (Camera)</col>\n        <col id=\"2\">mouse_cam</col>\n      </row>\n      <row>\n        <col id=\"0\">gyroabs(Rels.REL_Y, [Rels.REL_X|None, Rels.REL_X])</col>\n        <col id=\"1\" translatable=\"yes\">Mouse (Emulate Stick)</col>\n        <col id=\"2\">mouse_stick</col>\n      </row>\n      <row>\n        <col id=\"0\">gyro(ABS_RY, [ABS_RX|None, ABS_RX])</col>\n        <col id=\"1\" translatable=\"yes\">Right Stick (Camera)</col>\n        <col id=\"2\">right</col>\n      </row>\n      <row>\n        <col id=\"0\">gyroabs(ABS_RY, [ABS_RX|None, ABS_RX])</col>\n        <col id=\"1\" translatable=\"yes\">Right Stick (Absolute)</col>\n        <col id=\"2\">right_abs</col>\n      </row>\n      <row>\n        <col id=\"0\">gyro(ABS_Y, [ABS_X|None, ABS_X])</col>\n        <col id=\"1\" translatable=\"yes\">Left Stick (Camera)</col>\n        <col id=\"2\">left</col>\n      </row>\n      <row>\n        <col id=\"0\">gyroabs(ABS_Y, [ABS_X|None, ABS_X])</col>\n        <col id=\"1\" translatable=\"yes\">Left Stick (Absolute)</col>\n        <col id=\"2\">left_abs</col>\n      </row>\n      <row>\n        <col id=\"0\">cemuhook</col>\n        <col id=\"1\" translatable=\"yes\">CemuHook motion provider</col>\n        <col id=\"2\">cemuhook</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstYawRoll\">\n    <columns>\n      <!-- column-name value -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">YAW</col>\n        <col id=\"1\" translatable=\"yes\">Yaw</col>\n      </row>\n      <row>\n        <col id=\"0\">ROLL</col>\n        <col id=\"1\" translatable=\"yes\">Roll</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkGrid\" id=\"gyro_action\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"margin_left\">25</property>\n    <property name=\"margin_right\">30</property>\n    <property name=\"row_spacing\">3</property>\n    <property name=\"column_spacing\">10</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"lblYawRoll\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">5</property>\n        <property name=\"margin_right\">20</property>\n        <property name=\"label\" translatable=\"yes\">X Axis Controlled by</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">1</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbMode\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"model\">lstOutputMode</property>\n        <property name=\"active\">1</property>\n        <signal name=\"changed\" handler=\"send\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"text\"/>\n          <attributes>\n            <attribute name=\"text\">1</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label5\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">5</property>\n        <property name=\"margin_right\">20</property>\n        <property name=\"label\" translatable=\"yes\">Output Mode</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbYawRoll\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"model\">lstYawRoll</property>\n        <property name=\"active\">0</property>\n        <signal name=\"changed\" handler=\"send\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"text1\"/>\n          <attributes>\n            <attribute name=\"text\">1</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">1</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbGyroButton\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">20</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"model\">lstGyroButton</property>\n        <property name=\"active\">1</property>\n        <signal name=\"changed\" handler=\"send\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"text2\"/>\n          <attributes>\n            <attribute name=\"text\">1</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">3</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"lblGyroEnable\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">5</property>\n        <property name=\"margin_top\">20</property>\n        <property name=\"label\" translatable=\"yes\">Gyro Enable Button</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">3</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkCheckButton\" id=\"cbInvertY\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">False</property>\n        <property name=\"draw_indicator\">True</property>\n        <signal name=\"toggled\" handler=\"cbInvertY_toggled_cb\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkLabel\" id=\"label3\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Invert Y Axis</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">2</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkCheckButton\" id=\"cbInvertGyro\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">False</property>\n        <property name=\"draw_indicator\">True</property>\n        <signal name=\"toggled\" handler=\"on_cbInvertGyro_toggled\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkLabel\" id=\"label4\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Button disables Gyro</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">5</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkRevealer\" id=\"rvSoftLevel\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">5</property>\n        <property name=\"margin_bottom\">10</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbSoftLevel\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblSoftLevel\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"label\" translatable=\"yes\">Trigger Pull Level</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScale\" id=\"sclSoftLevel\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_right\">20</property>\n                <property name=\"adjustment\">adjSoftLevel</property>\n                <property name=\"round_digits\">2</property>\n                <property name=\"value_pos\">right</property>\n                <signal name=\"format-value\" handler=\"on_sclSoftLevel_format_value\" swapped=\"no\"/>\n                <signal name=\"value-changed\" handler=\"send\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">4</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/menu_only.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkListStore\" id=\"lstMenu\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkBox\" id=\"menu_only\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <object class=\"GtkGrid\" id=\"grMenu\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">20</property>\n        <property name=\"margin_right\">20</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"label4\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_bottom\">10</property>\n            <property name=\"label\" translatable=\"yes\">Menu</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkComboBox\" id=\"cbMenus\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"model\">lstMenu</property>\n            <signal name=\"button-press-event\" handler=\"on_cbMenus_button_press_event\" swapped=\"no\"/>\n            <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkCellRendererText\" id=\"cellrenderertext3\"/>\n              <attributes>\n                <attribute name=\"text\">0</attribute>\n              </attributes>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btEditMenu\">\n            <property name=\"label\">gtk-edit</property>\n            <property name=\"visible\">True</property>\n            <property name=\"sensitive\">False</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btEditMenu_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuMenu\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuNew\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_New Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuNew_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuCopy\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Copy Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuCopy_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuRename\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Rename Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuRename_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuDelete\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Delete Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuDelete_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/osk_action.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkListStore\" id=\"lstActionType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">None</col>\n        <col id=\"1\" translatable=\"yes\">None</col>\n      </row>\n      <row>\n        <col id=\"0\">OSK.close()</col>\n        <col id=\"1\" translatable=\"yes\">Hide Keyboard</col>\n      </row>\n      <row>\n        <col id=\"0\">OSK.cursor(LEFT)</col>\n        <col id=\"1\" translatable=\"yes\">Move LEFT Cursor</col>\n      </row>\n      <row>\n        <col id=\"0\">OSK.cursor(RIGHT)</col>\n        <col id=\"1\" translatable=\"yes\">Move RIGHT Cursor</col>\n      </row>\n      <row>\n        <col id=\"0\">OSK.press(LEFT)</col>\n        <col id=\"1\" translatable=\"yes\">Press Key Under LEFT Cursor</col>\n      </row>\n      <row>\n        <col id=\"0\">OSK.press(RIGHT)</col>\n        <col id=\"1\" translatable=\"yes\">Press Key Under RIGHT Cursor</col>\n      </row>\n      <row>\n        <col id=\"0\">OSK.move()</col>\n        <col id=\"1\" translatable=\"yes\">Move Keyboard</col>\n      </row>\n      <row>\n        <col id=\"0\">button(Keys.BTN_LEFT)</col>\n        <col id=\"1\" translatable=\"yes\">Press LEFT mouse button</col>\n      </row>\n      <row>\n        <col id=\"0\">button(Keys.BTN_RIGHT)</col>\n        <col id=\"1\" translatable=\"yes\">Press RIGHT mouse button</col>\n      </row>\n      <row>\n        <col id=\"0\">OSK.move()</col>\n        <col id=\"1\" translatable=\"yes\">Move Keyboard</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkBox\" id=\"osk_action\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"label5\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"label\" translatable=\"yes\">Action</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbActionType\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"model\">lstActionType</property>\n        <property name=\"active\">0</property>\n        <signal name=\"changed\" handler=\"on_cbActionType_changed\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"text\"/>\n          <attributes>\n            <attribute name=\"text\">1</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/per_axis.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.19.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkBox\" id=\"per_axis\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"margin_left\">50</property>\n    <property name=\"margin_right\">50</property>\n    <property name=\"margin_top\">20</property>\n    <property name=\"orientation\">vertical</property>\n    <property name=\"spacing\">10</property>\n    <child>\n      <object class=\"GtkButton\" id=\"btAxisX\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"hexpand\">True</property>\n        <signal name=\"clicked\" handler=\"on_btAxisX_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkBox\" id=\"box8\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label6\">\n                <property name=\"width_request\">200</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">X Axis</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkSeparator\" id=\"separator7\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblAxisX\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btAxisY\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"hexpand\">True</property>\n        <signal name=\"clicked\" handler=\"on_btAxisY_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkBox\" id=\"box9\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label8\">\n                <property name=\"width_request\">200</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Y Axis</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkSeparator\" id=\"separator8\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblAxisY\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/recent_list.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjNumOfProfiles\">\n    <property name=\"lower\">2</property>\n    <property name=\"upper\">10</property>\n    <property name=\"value\">2</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkGrid\" id=\"recent_list\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"margin_left\">20</property>\n    <property name=\"margin_right\">20</property>\n    <property name=\"margin_top\">10</property>\n    <property name=\"margin_bottom\">10</property>\n    <property name=\"hexpand\">True</property>\n    <property name=\"vexpand\">True</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"lbl67\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"label\" translatable=\"yes\">Number of Profiles to Display</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkSpinButton\" id=\"sclNumOfProfiles\">\n        <property name=\"width_request\">150</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"adjustment\">adjNumOfProfiles</property>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/special_action.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjMenuPosX\">\n    <property name=\"upper\">4096</property>\n    <property name=\"value\">10</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjMenuPosY\">\n    <property name=\"upper\">4096</property>\n    <property name=\"value\">10</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjOSDTimeout\">\n    <property name=\"lower\">0.10000000000000001</property>\n    <property name=\"upper\">60.100000000000001</property>\n    <property name=\"value\">1</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">1</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstActionType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">none</col>\n        <col id=\"1\" translatable=\"yes\">None</col>\n      </row>\n      <row>\n        <col id=\"0\">turnoff</col>\n        <col id=\"1\" translatable=\"yes\">Turn Off Controller</col>\n      </row>\n      <row>\n        <col id=\"0\">profile</col>\n        <col id=\"1\" translatable=\"yes\">Change Profile</col>\n      </row>\n      <row>\n        <col id=\"0\">menu</col>\n        <col id=\"1\" translatable=\"yes\">Display Menu</col>\n      </row>\n      <row>\n        <col id=\"0\">keyboard</col>\n        <col id=\"1\" translatable=\"yes\">Display On-Screen Keyboard</col>\n      </row>\n      <row>\n        <col id=\"0\">osd</col>\n        <col id=\"1\" translatable=\"yes\">Show OSD Message</col>\n      </row>\n      <row>\n        <col id=\"0\">clearosd</col>\n        <col id=\"1\" translatable=\"yes\">Hide all OSD Menus and Messages</col>\n      </row>\n      <row>\n        <col id=\"0\">resetgyro</col>\n        <col id=\"1\" translatable=\"yes\">Recenter gyro sensor</col>\n      </row>\n      <row>\n        <col id=\"0\">shell</col>\n        <col id=\"1\" translatable=\"yes\">Execute Command</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstButton\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name button -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">(Controller Default)</col>\n        <col id=\"1\">DEFAULT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">A</col>\n        <col id=\"1\">A</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">B</col>\n        <col id=\"1\">B</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">X</col>\n        <col id=\"1\">X</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Y</col>\n        <col id=\"1\">Y</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Back (select)</col>\n        <col id=\"1\">BACK</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Center</col>\n        <col id=\"1\">C</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Start</col>\n        <col id=\"1\">START</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Grip</col>\n        <col id=\"1\">LGRIP</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Grip</col>\n        <col id=\"1\">RGRIP</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Bumper</col>\n        <col id=\"1\">LB</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Bumper</col>\n        <col id=\"1\">RB</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Trigger</col>\n        <col id=\"1\">LT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Trigger</col>\n        <col id=\"1\">RT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Stick Press</col>\n        <col id=\"1\">STICKPRESS</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Pad Press</col>\n        <col id=\"1\">LPAD</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Pad Press</col>\n        <col id=\"1\">RPAD</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstControlWith\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name stickorpad -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">(Controller Default)</col>\n        <col id=\"1\">DEFAULT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Stick</col>\n        <col id=\"1\">STICK</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Pad</col>\n        <col id=\"1\">LEFT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Pad</col>\n        <col id=\"1\">RIGHT</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenu\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenuPosX\">\n    <columns>\n      <!-- column-name id -->\n      <column type=\"gint\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">1</col>\n        <col id=\"1\" translatable=\"yes\">From Left</col>\n      </row>\n      <row>\n        <col id=\"0\">-1</col>\n        <col id=\"1\" translatable=\"yes\">From Right</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenuPosY\">\n    <columns>\n      <!-- column-name id -->\n      <column type=\"gint\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">1</col>\n        <col id=\"1\" translatable=\"yes\">From Top</col>\n      </row>\n      <row>\n        <col id=\"0\">-1</col>\n        <col id=\"1\" translatable=\"yes\">From Bottom</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenuType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">List</col>\n        <col id=\"1\">menu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Grid</col>\n        <col id=\"1\">gridmenu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Radial</col>\n        <col id=\"1\">radialmenu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Horizontal Menu</col>\n        <col id=\"1\">hmenu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Quick Menu</col>\n        <col id=\"1\">quickmenu</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOSDSize\">\n    <columns>\n      <!-- column-name size -->\n      <column type=\"gint\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">1</col>\n        <col id=\"1\" translatable=\"yes\">Smaller</col>\n      </row>\n      <row>\n        <col id=\"0\">2</col>\n        <col id=\"1\" translatable=\"yes\">Small</col>\n      </row>\n      <row>\n        <col id=\"0\">3</col>\n        <col id=\"1\" translatable=\"yes\">Default</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstProfile\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name giofile -->\n      <column type=\"GObject\"/>\n      <!-- column-name changed -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkBox\" id=\"special_action\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"orientation\">vertical</property>\n    <child>\n      <object class=\"GtkLabel\" id=\"label5\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"label\" translatable=\"yes\">Action</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbActionType\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"model\">lstActionType</property>\n        <property name=\"active\">0</property>\n        <signal name=\"changed\" handler=\"on_cbActionType_changed\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"text\"/>\n          <attributes>\n            <attribute name=\"text\">1</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkStack\" id=\"stActionData\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">20</property>\n        <property name=\"transition_type\">crossfade</property>\n        <child>\n          <object class=\"GtkFixed\" id=\"nothing\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n          </object>\n          <packing>\n            <property name=\"name\">page4</property>\n            <property name=\"title\" translatable=\"yes\">page4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbShell\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Command</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkEntry\" id=\"enCommand\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <signal name=\"changed\" handler=\"on_enCommand_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">vbCommand</property>\n            <property name=\"title\" translatable=\"yes\">page3</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbProfile\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Profile</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbProfile\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"model\">lstProfile</property>\n                <signal name=\"changed\" handler=\"on_cbProfile_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"cellrenderertext1\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"cellrenderertext2\">\n                    <property name=\"xalign\">1</property>\n                    <property name=\"alignment\">right</property>\n                    <property name=\"style\">italic</property>\n                  </object>\n                  <attributes>\n                    <attribute name=\"text\">2</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">vbProfile</property>\n            <property name=\"title\" translatable=\"yes\">page0</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbOSD\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <property name=\"spacing\">1</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Text to Display</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkEntry\" id=\"enOSDText\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_right\">5</property>\n                <signal name=\"changed\" handler=\"on_osd_settings_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label7\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"label\" translatable=\"yes\">Display Time</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScale\" id=\"sclOSDTimeout\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_right\">5</property>\n                <property name=\"adjustment\">adjOSDTimeout</property>\n                <property name=\"round_digits\">1</property>\n                <property name=\"value_pos\">right</property>\n                <signal name=\"format-value\" handler=\"on_sclOSDTimeout_format_value\" swapped=\"no\"/>\n                <signal name=\"value-changed\" handler=\"on_osd_settings_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label9\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Size</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">4</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbOSDSize\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"model\">lstOSDSize</property>\n                <property name=\"active\">0</property>\n                <signal name=\"changed\" handler=\"on_osd_settings_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"text1\"/>\n                  <attributes>\n                    <attribute name=\"text\">1</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">5</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page0</property>\n            <property name=\"title\" translatable=\"yes\">page0</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grMenu\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"label\" translatable=\"yes\">Menu Type</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbMenuType\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"model\">lstMenuType</property>\n                <property name=\"active\">0</property>\n                <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"cellrenderertext3\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label6\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"label\" translatable=\"yes\">Menu</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbMenus\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_right\">5</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"model\">lstMenu</property>\n                <signal name=\"button-press-event\" handler=\"on_cbMenus_button_press_event\" swapped=\"no\"/>\n                <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"cellrenderertext4\"/>\n                  <attributes>\n                    <attribute name=\"text\">0</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btEditMenu\">\n                <property name=\"label\">gtk-edit</property>\n                <property name=\"visible\">True</property>\n                <property name=\"sensitive\">False</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"use_stock\">True</property>\n                <signal name=\"clicked\" handler=\"on_btEditMenu_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkExpander\" id=\"exMenuControl\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_top\">10</property>\n                <signal name=\"activate\" handler=\"on_exMenuControl_activate\" swapped=\"no\"/>\n                <child>\n                  <placeholder/>\n                </child>\n                <child type=\"label\">\n                  <object class=\"GtkLabel\" id=\"label67\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Control Options...</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">4</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvMenuControl\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grMenuControl\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">20</property>\n                    <property name=\"margin_right\">20</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblControlWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_right\">20</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"label\" translatable=\"yes\">Control Menu With</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblConfirmWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_right\">20</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"label\" translatable=\"yes\">Confirm Button</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblCancelWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_right\">20</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"label\" translatable=\"yes\">Cancel Button</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbMenuAutoConfirm\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblMenuAutoConfirm\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Confirm selection by releasing the button</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                        <property name=\"width\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbControlWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"model\">lstControlWith</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"cellrenderertext5\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbConfirmWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"model\">lstButton</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"cellrenderertext6\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbCancelWith\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_bottom\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"model\">lstButton</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"cellrenderertext7\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">5</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkExpander\" id=\"exMenuPosition\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_top\">10</property>\n                <signal name=\"activate\" handler=\"on_exMenuPosition_activate\" swapped=\"no\"/>\n                <child>\n                  <placeholder/>\n                </child>\n                <child type=\"label\">\n                  <object class=\"GtkLabel\" id=\"label8\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Menu Position...</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">6</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvMenuPosition\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grid7\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">20</property>\n                    <property name=\"margin_right\">20</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <child>\n                      <object class=\"GtkSpinButton\" id=\"spMenuPosX\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjMenuPosX</property>\n                        <signal name=\"value-changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkSpinButton\" id=\"spMenuPosY\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjMenuPosY</property>\n                        <signal name=\"value-changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbMenuPosX\">\n                        <property name=\"width_request\">200</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">20</property>\n                        <property name=\"model\">lstMenuPosX</property>\n                        <property name=\"active\">0</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"crMenuPosX\"/>\n                          <attributes>\n                            <attribute name=\"text\">1</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbMenuPosY\">\n                        <property name=\"width_request\">200</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">20</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"model\">lstMenuPosY</property>\n                        <property name=\"active\">1</property>\n                        <signal name=\"changed\" handler=\"on_cbMenus_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"crMenuPosY\"/>\n                          <attributes>\n                            <attribute name=\"text\">1</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">7</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page1</property>\n            <property name=\"title\" translatable=\"yes\">page1</property>\n            <property name=\"position\">4</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">2</property>\n      </packing>\n    </child>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuMenu\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuNew\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_New Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuNew_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuCopy\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Copy Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuCopy_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuRename\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Rename Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuRename_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuMenuDelete\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Delete Menu</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuMenuDelete_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/tilt.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.22.1 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkListStore\" id=\"lstMenu\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMenuType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">List</col>\n        <col id=\"1\">menu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Grid</col>\n        <col id=\"1\">gridmenu</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Radial</col>\n        <col id=\"1\">radialmenu</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOutType\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Simple DPAD</col>\n        <col id=\"1\">dpad</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">8-Way DPAD</col>\n        <col id=\"1\">dpad8</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">WSAD (DPAD)</col>\n        <col id=\"1\">wsad</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Arrows (DPAD)</col>\n        <col id=\"1\">arrows</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Menu</col>\n        <col id=\"1\">menu</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkGrid\" id=\"tilt\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkGrid\" id=\"grTilt\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"row_spacing\">5</property>\n        <property name=\"column_spacing\">5</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btTilt2\">\n            <property name=\"name\">2</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"valign\">start</property>\n            <property name=\"margin_top\">20</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_btTilt_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblTilt2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btTilt3\">\n            <property name=\"name\">3</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"valign\">start</property>\n            <property name=\"margin_top\">20</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_btTilt_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblTilt3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">3</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btTilt0\">\n            <property name=\"name\">0</property>\n            <property name=\"width_request\">200</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"valign\">start</property>\n            <property name=\"margin_top\">50</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_btTilt_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblTilt0\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btTilt1\">\n            <property name=\"name\">1</property>\n            <property name=\"width_request\">200</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"valign\">start</property>\n            <property name=\"margin_top\">50</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_btTilt_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblTilt\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">3</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkImage\" id=\"image1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"pixbuf\">../../images/controller-side.svg</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">0</property>\n            <property name=\"width\">2</property>\n            <property name=\"height\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblEmptySpace\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblEmptySpace1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">3</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btTilt4\">\n            <property name=\"name\">4</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"valign\">start</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_btTilt_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblTilt4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btTilt5\">\n            <property name=\"name\">5</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"valign\">start</property>\n            <property name=\"hexpand\">True</property>\n            <signal name=\"clicked\" handler=\"on_btTilt_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblTilt5\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">2</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">0</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ae/trigger.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.36.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjARangeEnd\">\n    <property name=\"lower\">1</property>\n    <property name=\"upper\">255</property>\n    <property name=\"value\">255</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjARangeStart\">\n    <property name=\"upper\">255</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjFullLevel\">\n    <property name=\"lower\">1</property>\n    <property name=\"upper\">254</property>\n    <property name=\"value\">254</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjPartialLevel\">\n    <property name=\"lower\">1</property>\n    <property name=\"upper\">255</property>\n    <property name=\"value\">50</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjTimeOut\">\n    <property name=\"lower\">0.05</property>\n    <property name=\"upper\">0.8</property>\n    <property name=\"value\">0.15</property>\n    <property name=\"step_increment\">0.01</property>\n    <property name=\"page_increment\">0.1</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstTriggerStyle\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Normal</col>\n        <col id=\"1\" translatable=\"yes\">NORMAL</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Normal - Exclusive Buttons</col>\n        <col id=\"1\" translatable=\"yes\">NORMAL_EXCLUSIVE</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Hip Fire</col>\n        <col id=\"1\" translatable=\"yes\">HIPFIRE_NORMAL</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Hip Fire - Exclusive Buttons</col>\n        <col id=\"1\" translatable=\"yes\">HIPFIRE_EXCLUSIVE</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Hip fire - Sensible</col>\n        <col id=\"1\" translatable=\"yes\">HIPFIRE_SENSIBLE</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkGrid\" id=\"trigger\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"margin_left\">40</property>\n    <property name=\"margin_right\">40</property>\n    <property name=\"row_spacing\">5</property>\n    <property name=\"column_spacing\">2</property>\n    <child>\n      <object class=\"GtkButton\" id=\"btPartPressed\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"hexpand\">True</property>\n        <signal name=\"clicked\" handler=\"on_btPartPressed_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkBox\" id=\"box4\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label10\">\n                <property name=\"width_request\">200</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Partially Pressed Action</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkSeparator\" id=\"separator1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_right\">5</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblPartPressed\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(...)</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">0</property>\n        <property name=\"width\">3</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btFullPress\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"hexpand\">True</property>\n        <signal name=\"clicked\" handler=\"on_btFullPress_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkBox\" id=\"box5\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label11\">\n                <property name=\"width_request\">200</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Fully Pressed Action</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkSeparator\" id=\"separator2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_right\">5</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblFullPressed\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(not set)</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">1</property>\n        <property name=\"width\">3</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btFullPressedClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_left\">10</property>\n        <signal name=\"clicked\" handler=\"on_btFullPressedClear_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"image1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">1</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btPartPresedClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_left\">10</property>\n        <signal name=\"clicked\" handler=\"on_btPartPresedClear_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"image7\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btFullyPresedClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_left\">10</property>\n        <signal name=\"clicked\" handler=\"on_btFullyPresedClear_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"image8\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">3</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label13\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btAnalog\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_top\">20</property>\n        <property name=\"hexpand\">True</property>\n        <signal name=\"clicked\" handler=\"on_btAnalog_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkBox\" id=\"box1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label8\">\n                <property name=\"width_request\">200</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Analog Output</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkSeparator\" id=\"separator3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblAnalog\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">(disabled)</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">6</property>\n        <property name=\"width\">3</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btAnalogClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_top\">20</property>\n        <signal name=\"clicked\" handler=\"on_btAnalogClear_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"image2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">6</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label6\">\n        <property name=\"width_request\">250</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Analog Range Start</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">7</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkScale\" id=\"sclARangeStart\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"adjustment\">adjARangeStart</property>\n        <property name=\"fill_level\">255</property>\n        <property name=\"round_digits\">0</property>\n        <property name=\"digits\">0</property>\n        <property name=\"value_pos\">right</property>\n        <signal name=\"value-changed\" handler=\"on_ui_value_changed\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">7</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkScale\" id=\"sclARangeEnd\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"adjustment\">adjARangeEnd</property>\n        <property name=\"fill_level\">255</property>\n        <property name=\"round_digits\">0</property>\n        <property name=\"digits\">0</property>\n        <property name=\"value_pos\">right</property>\n        <signal name=\"value-changed\" handler=\"on_ui_value_changed\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">8</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label1\">\n        <property name=\"width_request\">250</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Analog Range End</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">8</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btARangeStartClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_left\">10</property>\n        <signal name=\"clicked\" handler=\"on_btARangeStartClear_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"image3\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">7</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btARangeEndClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_left\">10</property>\n        <signal name=\"clicked\" handler=\"on_btARangeEndClear_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"image4\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">8</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkScale\" id=\"sclFullLevel\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"adjustment\">adjFullLevel</property>\n        <property name=\"fill_level\">255</property>\n        <property name=\"round_digits\">0</property>\n        <property name=\"digits\">0</property>\n        <property name=\"value_pos\">right</property>\n        <signal name=\"value-changed\" handler=\"on_ui_value_changed\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">2</property>\n        <property name=\"top_attach\">3</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label5\">\n        <property name=\"width_request\">250</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Fully Pressed Level</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">3</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkScale\" id=\"sclPartialLevel\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"adjustment\">adjPartialLevel</property>\n        <property name=\"fill_level\">255</property>\n        <property name=\"round_digits\">0</property>\n        <property name=\"digits\">0</property>\n        <property name=\"value_pos\">right</property>\n        <signal name=\"value-changed\" handler=\"on_ui_value_changed\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">2</property>\n        <property name=\"top_attach\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label4\">\n        <property name=\"width_request\">250</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Partially Pressed Level</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">2</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label3\">\n        <property name=\"width_request\">200</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Dual Stage Trigger Style</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">4</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkComboBox\" id=\"cbActionType\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"model\">lstTriggerStyle</property>\n        <property name=\"row_span_column\">0</property>\n        <property name=\"active\">0</property>\n        <signal name=\"changed\" handler=\"on_cbActionType_changed\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkCellRendererText\" id=\"cellrenderertext1\"/>\n          <attributes>\n            <attribute name=\"text\">0</attribute>\n          </attributes>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">1</property>\n        <property name=\"top_attach\">4</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkLabel\" id=\"label2\">\n        <property name=\"width_request\">200</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Hip Fire Delay</property>\n        <property name=\"xalign\">0</property>\n        <attributes>\n          <attribute name=\"weight\" value=\"bold\"/>\n        </attributes>\n      </object>\n      <packing>\n        <property name=\"left_attach\">0</property>\n        <property name=\"top_attach\">5</property>\n        <property name=\"width\">2</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btTimeOutClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <property name=\"margin_left\">10</property>\n        <signal name=\"clicked\" handler=\"on_btTimeOutClear_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"image5\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"left_attach\">3</property>\n        <property name=\"top_attach\">5</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkScale\" id=\"sclTimeOut\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"adjustment\">adjTimeOut</property>\n        <property name=\"fill_level\">1</property>\n        <property name=\"round_digits\">0</property>\n        <property name=\"digits\">2</property>\n        <property name=\"value_pos\">right</property>\n        <signal name=\"value-changed\" handler=\"on_ui_value_changed\" swapped=\"no\"/>\n      </object>\n      <packing>\n        <property name=\"left_attach\">2</property>\n        <property name=\"top_attach\">5</property>\n      </packing>\n    </child>\n    <child>\n      <placeholder/>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/app.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkWindow\" id=\"OsdmodeMappings\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"border_width\">2</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"skip_taskbar_hint\">True</property>\n    <property name=\"skip_pager_hint\">True</property>\n    <property name=\"accept_focus\">False</property>\n    <property name=\"focus_on_map\">False</property>\n    <property name=\"decorated\">False</property>\n    <property name=\"deletable\">False</property>\n    <child>\n      <object class=\"GtkBox\" id=\"vb86\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">5</property>\n        <property name=\"margin_right\">5</property>\n        <property name=\"margin_top\">5</property>\n        <property name=\"margin_bottom\">5</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbOsdmodeExit\">\n            <property name=\"width_request\">75</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkImage\" id=\"imgOsdmodeExit\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-missing-image</property>\n                <property name=\"icon_size\">3</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">5</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblOsdmodeExit\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Exit</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                  <attribute name=\"scale\" value=\"1.5\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"padding\">5</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbOsdmodeAct\">\n            <property name=\"width_request\">75</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <child>\n              <object class=\"GtkImage\" id=\"imgOsdmodeAct\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-missing-image</property>\n                <property name=\"icon_size\">3</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">5</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblOsdmodeAct\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Activate</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                  <attribute name=\"scale\" value=\"1.5\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"padding\">5</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbOsdmodeOK\">\n            <property name=\"width_request\">75</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkImage\" id=\"imgOsdmodeOK\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-missing-image</property>\n                <property name=\"icon_size\">3</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">5</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblOsdmodeOK\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">OK</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                  <attribute name=\"scale\" value=\"1.5\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"padding\">5</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbOsdmodeClose\">\n            <property name=\"width_request\">75</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <child>\n              <object class=\"GtkImage\" id=\"imgOsdmodeClose\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-missing-image</property>\n                <property name=\"icon_size\">3</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">5</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblOsdmodeClose\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Close</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                  <attribute name=\"scale\" value=\"1.5\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"padding\">5</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbOsdmodeSave\">\n            <property name=\"width_request\">75</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkImage\" id=\"imgOsdmodeSave\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-missing-image</property>\n                <property name=\"icon_size\">3</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">5</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblOsdmodeSave\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Save</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                  <attribute name=\"scale\" value=\"1.5\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"padding\">5</property>\n            <property name=\"position\">4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label57\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">5</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label58\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">6</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <placeholder/>\n    </child>\n  </object>\n  <object class=\"GtkTextBuffer\" id=\"buffProfileDescription\">\n    <signal name=\"changed\" handler=\"on_buffProfileDescription_changed\" swapped=\"no\"/>\n  </object>\n  <object class=\"GtkDialog\" id=\"dlgNewProfile\">\n    <property name=\"width_request\">600</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"border_width\">18</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"modal\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <property name=\"skip_taskbar_hint\">True</property>\n    <property name=\"skip_pager_hint\">True</property>\n    <signal name=\"delete-event\" handler=\"undeletable_dialog\" swapped=\"no\"/>\n    <child internal-child=\"vbox\">\n      <object class=\"GtkBox\" id=\"dialog-vbox1\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">12</property>\n        <child internal-child=\"action_area\">\n          <object class=\"GtkButtonBox\" id=\"dialog-action_area1\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"layout_style\">end</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">False</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"profileNameBox\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <property name=\"spacing\">6</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblNewProfile\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"label\" translatable=\"yes\">Profile name</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkEntry\" id=\"txNewProfile\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <signal name=\"changed\" handler=\"on_txNewProfile_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"newProfileChoices\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"homogeneous\">True</property>\n            <child>\n              <object class=\"GtkRadioButton\" id=\"rbNewProfile\">\n                <property name=\"label\" translatable=\"yes\">Empty profile</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"active\">True</property>\n                <property name=\"draw_indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_rbNewProfile_group_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRadioButton\" id=\"rbCopyProfile\">\n                <property name=\"label\" translatable=\"yes\">Copy current profile</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"active\">True</property>\n                <property name=\"draw_indicator\">True</property>\n                <property name=\"group\">rbNewProfile</property>\n                <signal name=\"toggled\" handler=\"on_rbNewProfile_group_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"headerbar2\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">New Profile</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btNewProfile\">\n            <property name=\"label\">gtk-ok</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btNewProfile_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkDialog\" id=\"dlgRenameProfile\">\n    <property name=\"width_request\">600</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"border_width\">18</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"modal\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <property name=\"skip_taskbar_hint\">True</property>\n    <property name=\"skip_pager_hint\">True</property>\n    <signal name=\"delete-event\" handler=\"undeletable_dialog\" swapped=\"no\"/>\n    <child internal-child=\"vbox\">\n      <object class=\"GtkBox\" id=\"dialog-vbox2\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">6</property>\n        <child internal-child=\"action_area\">\n          <object class=\"GtkButtonBox\" id=\"dialog-action_area2\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"layout_style\">end</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">False</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label3\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"halign\">start</property>\n            <property name=\"label\" translatable=\"yes\">Profile name</property>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkEntry\" id=\"txRename\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <signal name=\"changed\" handler=\"on_txRename_changed\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"headerbar1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Rename Profile</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btRenameProfile\">\n            <property name=\"label\">gtk-ok</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btRenameProfile_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstProfile\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name giofile -->\n      <column type=\"GObject\"/>\n      <!-- column-name changed -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuDaemon\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkCheckMenuItem\" id=\"mnuEmulationEnabled\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Emulation Enabled</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"toggled\" handler=\"on_mnuEmulationEnabled_toggled\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuGlobalSettings\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Settings</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuGlobalSettings_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuImport\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Import/Export _Profile...</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuImport_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkSeparatorMenuItem\" id=\"menuitem1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuAbout\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">About</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuAbout_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuExit\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Quit</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuExit_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkWindow\" id=\"window\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center</property>\n    <signal name=\"delete-event\" handler=\"on_window_delete_event\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkBox\" id=\"content\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <signal name=\"drag-data-received\" handler=\"on_drag_data_received\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkBox\" id=\"vbSwitchers\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_bottom\">2</property>\n            <property name=\"orientation\">vertical</property>\n            <property name=\"spacing\">6</property>\n            <child>\n              <object class=\"GtkSeparator\" id=\"sepSwitchers\">\n                <property name=\"can_focus\">False</property>\n                <property name=\"no_show_all\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkStack\" id=\"stckEditor\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">12</property>\n            <property name=\"margin_right\">12</property>\n            <property name=\"margin_bottom\">12</property>\n            <property name=\"transition_duration\">500</property>\n            <property name=\"transition_type\">crossfade</property>\n            <property name=\"interpolate_size\">True</property>\n            <child>\n              <object class=\"GtkGrid\" id=\"grEditor\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"row_spacing\">12</property>\n                <property name=\"column_spacing\">12</property>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbLeft\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btLT\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btLB\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btLGRIP2\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">False</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btLGRIP\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btBACK\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">4</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbRight\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRT\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRB\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRGRIP2\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">False</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRGRIP\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btSTART\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btDOTS\">\n                        <property name=\"height_request\">42</property>\n                        <property name=\"visible\">False</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"margin_left\">6</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">5</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbPads\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"spacing\">12</property>\n                    <property name=\"baseline_position\">bottom</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btLPAD\">\n                        <property name=\"width_request\">220</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btDPAD\">\n                        <property name=\"width_request\">220</property>\n                        <property name=\"visible\">False</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btSTICK\">\n                        <property name=\"width_request\">220</property>\n                        <property name=\"visible\">False</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRSTICK\">\n                        <property name=\"width_request\">220</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btGYRO\">\n                        <property name=\"width_request\">220</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">5</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRPAD\">\n                        <property name=\"width_request\">220</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">6</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkFixed\" id=\"mainArea\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"halign\">center</property>\n                    <signal name=\"size-allocate\" handler=\"on_c_size_allocate\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkBox\" id=\"vbC\">\n                        <property name=\"width_request\">150</property>\n                        <property name=\"height_request\">90</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"valign\">end</property>\n                        <property name=\"orientation\">vertical</property>\n                        <child>\n                          <object class=\"GtkButton\" id=\"btC\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">True</property>\n                            <property name=\"receives_default\">True</property>\n                            <property name=\"margin_left\">6</property>\n                            <property name=\"margin_right\">6</property>\n                            <child>\n                              <placeholder/>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkButton\" id=\"btCPAD\">\n                            <property name=\"height_request\">40</property>\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">True</property>\n                            <property name=\"receives_default\">True</property>\n                            <property name=\"margin_top\">6</property>\n                            <child>\n                              <placeholder/>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">0</property>\n                    <property name=\"height\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbXY\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btY\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btX\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbAB\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btB\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btA\">\n                        <property name=\"width_request\">170</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"name\">page0</property>\n                <property name=\"title\" translatable=\"yes\">page0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblEmpty\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"name\">page1</property>\n                <property name=\"title\" translatable=\"yes\">page1</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"hbWindow\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <property name=\"decoration_layout\">menu:minimize,close</property>\n        <child>\n          <object class=\"GtkMenuButton\" id=\"btDaemon\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"relief\">none</property>\n            <property name=\"popup\">mnuDaemon</property>\n            <child>\n              <object class=\"GtkBox\" id=\"boxDaemon\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkImage\" id=\"imgDaemonStatus\">\n                    <property name=\"width_request\">24</property>\n                    <property name=\"height_request\">24</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblDaemon\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Menu</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                    <property name=\"padding\">5</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btUndo\">\n            <property name=\"visible\">True</property>\n            <property name=\"sensitive\">False</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"tooltip_text\" translatable=\"yes\">Undo</property>\n            <signal name=\"clicked\" handler=\"on_btUndo_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkImage\" id=\"image1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-undo</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btRedo\">\n            <property name=\"visible\">True</property>\n            <property name=\"sensitive\">False</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"tooltip_text\" translatable=\"yes\">Redo</property>\n            <signal name=\"clicked\" handler=\"on_btRedo_clicked\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkImage\" id=\"image3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"stock\">gtk-redo</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkDialog\" id=\"dlgProfileDetails\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"border_width\">18</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"modal\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <property name=\"transient_for\">window</property>\n    <signal name=\"delete-event\" handler=\"undeletable_dialog\" swapped=\"no\"/>\n    <child internal-child=\"vbox\">\n      <object class=\"GtkBox\" id=\"dlgProfileDetailsVB\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">12</property>\n        <child internal-child=\"action_area\">\n          <object class=\"GtkButtonBox\" id=\"dlgProfileDetailsBB\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"layout_style\">end</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">False</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"profileFilenameBox\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <property name=\"spacing\">6</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblProfileFilename\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"label\" translatable=\"yes\">_Filename</property>\n                <property name=\"use_underline\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkEntry\" id=\"txProfileFilename\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"profileDescBox\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <property name=\"spacing\">6</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblProfileDescription\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"label\" translatable=\"yes\">_Description</property>\n                <property name=\"use_underline\">True</property>\n                <property name=\"mnemonic_widget\">txProfileDescription</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScrolledWindow\" id=\"swProfileDescription\">\n                <property name=\"width_request\">500</property>\n                <property name=\"height_request\">75</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"shadow_type\">in</property>\n                <child>\n                  <object class=\"GtkTextView\" id=\"txProfileDescription\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"buffer\">buffProfileDescription</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkCheckButton\" id=\"cbProfileIsTemplate\">\n            <property name=\"label\" translatable=\"yes\">Profile is _template</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">False</property>\n            <property name=\"use_underline\">True</property>\n            <property name=\"draw_indicator\">True</property>\n            <signal name=\"toggled\" handler=\"on_cbProfileIsTemplate_toggled\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"hbProfileDetails\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Profile details</property>\n        <property name=\"show_close_button\">True</property>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuImage\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuImgSticks\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Change Sticks and Background</property>\n        <property name=\"use_underline\">True</property>\n        <child type=\"submenu\">\n          <object class=\"GtkMenu\" id=\"submnuImgSticks\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkMenuItem\" id=\"mnuImgSticksSym\">\n                <property name=\"name\">background,ds4-config</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Symetrical</property>\n                <property name=\"use_underline\">True</property>\n                <signal name=\"activate\" handler=\"on_mnu_change_background_image\" swapped=\"no\"/>\n              </object>\n            </child>\n            <child>\n              <object class=\"GtkMenuItem\" id=\"mnuImgAsym\">\n                <property name=\"name\">background,x360-config</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Asymmetrical</property>\n                <property name=\"use_underline\">True</property>\n                <signal name=\"activate\" handler=\"on_mnu_change_background_image\" swapped=\"no\"/>\n              </object>\n            </child>\n            <child>\n              <object class=\"GtkMenuItem\" id=\"mnuImgTouchpads\">\n                <property name=\"name\">background,sc-config</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Touchpads</property>\n                <property name=\"use_underline\">True</property>\n                <signal name=\"activate\" handler=\"on_mnu_change_background_image\" swapped=\"no\"/>\n              </object>\n            </child>\n          </object>\n        </child>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuImgButtons\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Change Buttons</property>\n        <property name=\"use_underline\">True</property>\n        <child type=\"submenu\">\n          <object class=\"GtkMenu\" id=\"submnuImgButtons\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkMenuItem\" id=\"mnuImgPSx\">\n                <property name=\"name\">buttons,ds4-config</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">PSx</property>\n                <property name=\"use_underline\">True</property>\n                <signal name=\"activate\" handler=\"on_mnu_change_background_image\" swapped=\"no\"/>\n              </object>\n            </child>\n            <child>\n              <object class=\"GtkMenuItem\" id=\"mnuImgABXY\">\n                <property name=\"name\">buttons,x360-config</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">ABXY</property>\n                <property name=\"use_underline\">True</property>\n                <signal name=\"activate\" handler=\"on_mnu_change_background_image\" swapped=\"no\"/>\n              </object>\n            </child>\n          </object>\n        </child>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuImgClear\">\n        <property name=\"name\">undo,undo</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Clear Above Settings</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnu_change_background_image\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuPS\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuConfigureController\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Configure Controller</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuConfigureController_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuTurnoffController\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Turn Off Controller</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"mnuTurnoffController_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkSeparatorMenuItem\" id=\"mnuProfileSeparator2\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuProfileNew\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_New Profile</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuProfileNew_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuProfileCopy\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Copy Profile</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuProfileCopy_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuProfileRename\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Rename Profile</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuProfileRename_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuProfileDelete\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Delete Profile</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuProfileDelete_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuProfileRevert\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Revert Profile to Defaults</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuProfileDelete_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkSeparatorMenuItem\" id=\"mnuProfileSeparator1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuProfileDetails\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Profile D_etails</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuProfileDetails_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuPopup\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuClear\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">C_lear</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuClear_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuCopy\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Copy</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuCopy_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuPaste\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Paste</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuPaste_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkSeparatorMenuItem\" id=\"mnuEditPressSeparator\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuEditPress\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Edit Pressed Action</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuEditPress_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuTray\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuShowWindowTray\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Show/Hide _Window</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_statusicon_clicked\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkCheckMenuItem\" id=\"mnuEmulationEnabledTray\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">_Emulation Enabled</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"toggled\" handler=\"on_mnuEmulationEnabled_toggled\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkSeparatorMenuItem\" id=\"menuitem7\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuAboutTray\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">About</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuAbout_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuExitTray\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Quit</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuExit_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkInfoBar\" id=\"riNewRelease\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <signal name=\"close\" handler=\"hide_error\" swapped=\"no\"/>\n    <signal name=\"response\" handler=\"hide_error\" swapped=\"no\"/>\n    <child internal-child=\"action_area\">\n      <object class=\"GtkButtonBox\" id=\"riNewReleaseBB\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"spacing\">6</property>\n        <property name=\"layout_style\">end</property>\n        <child>\n          <placeholder/>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">False</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child internal-child=\"content_area\">\n      <object class=\"GtkBox\" id=\"riNewReleaseVB\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"hexpand\">True</property>\n        <property name=\"vexpand\">False</property>\n        <property name=\"spacing\">16</property>\n        <child>\n          <object class=\"GtkImage\" id=\"image6\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-info</property>\n            <property name=\"icon_size\">6</property>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grNewRelease\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblNewRelease\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">Welcome to the version &lt;b&gt;%s&lt;/b&gt;</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"xalign\">0</property>\n                <property name=\"yalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"cbNewReleaseHeader\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">SC Controller was upgraded</property>\n                <property name=\"xalign\">0</property>\n                <property name=\"yalign\">0.5</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbNewRelease\">\n                <property name=\"label\" translatable=\"yes\">Display with each new version</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"halign\">end</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"active\">True</property>\n                <property name=\"draw_indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_cbNewRelease_toggled\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">False</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <placeholder/>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/controller_settings.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.4 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjIdleTimeout\">\n    <property name=\"lower\">60</property>\n    <property name=\"upper\">7200</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjLED\">\n    <property name=\"upper\">100</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjLeftRotation\">\n    <property name=\"lower\">-180</property>\n    <property name=\"upper\">180</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjRightRotation\">\n    <property name=\"lower\">-180</property>\n    <property name=\"upper\">180</property>\n    <property name=\"step_increment\">1</property>\n    <property name=\"page_increment\">10</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstButton\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name button -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">A</col>\n        <col id=\"1\">A</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">B</col>\n        <col id=\"1\">B</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">X</col>\n        <col id=\"1\">X</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Y</col>\n        <col id=\"1\">Y</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Back (select)</col>\n        <col id=\"1\">BACK</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Center</col>\n        <col id=\"1\">C</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Start</col>\n        <col id=\"1\">START</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Grip</col>\n        <col id=\"1\">LGRIP</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Grip</col>\n        <col id=\"1\">RGRIP</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Bumper</col>\n        <col id=\"1\">LB</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Bumper</col>\n        <col id=\"1\">RB</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Trigger</col>\n        <col id=\"1\">LT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Trigger</col>\n        <col id=\"1\">RT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">-</col>\n        <col id=\"1\">x</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Stick Press</col>\n        <col id=\"1\">STICKPRESS</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Pad Press</col>\n        <col id=\"1\">LPAD</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Pad Press</col>\n        <col id=\"1\">RPAD</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstControlWith\">\n    <columns>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name stickorpad -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Stick</col>\n        <col id=\"1\">STICK</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Left Pad</col>\n        <col id=\"1\">LEFT</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Right Pad</col>\n        <col id=\"1\">RIGHT</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstIcons\">\n    <columns>\n      <!-- column-name path -->\n      <column type=\"gchararray\"/>\n      <!-- column-name filename -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name icon -->\n      <column type=\"GdkPixbuf\"/>\n    </columns>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">650</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"on_Dialog_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkGrid\" id=\"grid12\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"label30\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"label\" translatable=\"yes\">LED brightness</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkScale\" id=\"sclLED\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"margin_top\">5</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"adjustment\">adjLED</property>\n            <property name=\"round_digits\">0</property>\n            <property name=\"digits\">0</property>\n            <property name=\"value_pos\">right</property>\n            <signal name=\"value-changed\" handler=\"on_sclLED_value_changed\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">5</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Controller Name</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">5</property>\n            <property name=\"label\" translatable=\"yes\">Icon</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkEntry\" id=\"txName\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">20</property>\n            <signal name=\"changed\" handler=\"save_config\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkComboBox\" id=\"cbIcon\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"model\">lstIcons</property>\n            <signal name=\"changed\" handler=\"save_config\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkCellRendererPixbuf\" id=\"crIcon\"/>\n              <attributes>\n                <attribute name=\"pixbuf\">3</attribute>\n              </attributes>\n            </child>\n            <child>\n              <object class=\"GtkCellRendererText\" id=\"crText\"/>\n              <attributes>\n                <attribute name=\"text\">2</attribute>\n              </attributes>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkCheckButton\" id=\"cbAlignOSD\">\n            <property name=\"name\">0</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_top\">5</property>\n            <property name=\"draw_indicator\">True</property>\n            <signal name=\"toggled\" handler=\"save_config\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkLabel\" id=\"label3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Right-aligned OSD</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">8</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label8\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Automatically turn off when inactive for</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">6</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkScale\" id=\"sclIdleTimeout\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_top\">5</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"adjustment\">adjIdleTimeout</property>\n            <property name=\"round_digits\">0</property>\n            <property name=\"digits\">0</property>\n            <property name=\"value_pos\">right</property>\n            <signal name=\"format-value\" handler=\"on_sclIdleTimeout_format_value\" swapped=\"no\"/>\n            <signal name=\"value-changed\" handler=\"on_sclIdleTimeout_value_changed\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">7</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkExpander\" id=\"exTouchpadRotation\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_top\">5</property>\n            <signal name=\"activate\" handler=\"on_exTouchpadRotation_activate\" swapped=\"no\"/>\n            <child>\n              <placeholder/>\n            </child>\n            <child type=\"label\">\n              <object class=\"GtkLabel\" id=\"label4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"label\" translatable=\"yes\">Touchpad Input Rotation</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">11</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvTouchpadRotation\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkGrid\" id=\"grTouchpadRotation\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label5\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Applied to everything, even if profile sets no rotation on its actions.</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label6\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Left Pad Angle</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label7\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Right Pad Angle</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScale\" id=\"sclLeftRotation\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"adjustment\">adjLeftRotation</property>\n                    <property name=\"round_digits\">1</property>\n                    <property name=\"value_pos\">right</property>\n                    <signal name=\"value-changed\" handler=\"on_rotation_value_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScale\" id=\"sclRightRotation\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"adjustment\">adjRightRotation</property>\n                    <property name=\"round_digits\">1</property>\n                    <property name=\"value_pos\">right</property>\n                    <signal name=\"value-changed\" handler=\"on_rotation_value_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearLeftRotation\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btClearLeftRotation_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image48\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearRightRotation\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btClearRightRotation_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image56\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">12</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkExpander\" id=\"exMenuButtons\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"margin_top\">5</property>\n            <signal name=\"activate\" handler=\"on_exMenuButtons_activate\" swapped=\"no\"/>\n            <child>\n              <placeholder/>\n            </child>\n            <child type=\"label\">\n              <object class=\"GtkLabel\" id=\"label9\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"label\" translatable=\"yes\">Default Menu Controls</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">9</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvMenuButtons\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">20</property>\n            <child>\n              <object class=\"GtkGrid\" id=\"grMenuButtons\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"row_spacing\">2</property>\n                <property name=\"column_spacing\">5</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label10\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Used to control menus unless profile sets something different</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblControlWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Control Menu With</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBox\" id=\"cbControlWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"model\">lstControlWith</property>\n                    <property name=\"active\">0</property>\n                    <signal name=\"changed\" handler=\"save_config\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkCellRendererText\" id=\"cellrenderertext5\"/>\n                      <attributes>\n                        <attribute name=\"text\">0</attribute>\n                      </attributes>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblConfirmWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Confirm Button</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblCancelWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Cancel Button</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBox\" id=\"cbConfirmWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"model\">lstButton</property>\n                    <property name=\"active\">0</property>\n                    <signal name=\"changed\" handler=\"save_config\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkCellRendererText\" id=\"cellrenderertext6\"/>\n                      <attributes>\n                        <attribute name=\"text\">0</attribute>\n                      </attributes>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBox\" id=\"cbCancelWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"model\">lstButton</property>\n                    <property name=\"active\">1</property>\n                    <signal name=\"changed\" handler=\"save_config\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkCellRendererText\" id=\"cellrenderertext7\"/>\n                      <attributes>\n                        <attribute name=\"text\">0</attribute>\n                      </attributes>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearControlWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btClearControlWith_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image1\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearConfirmWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btClearConfirmWith_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image2\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearCancelWith\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btClearCancelWith_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image3\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">2</property>\n                    <property name=\"top_attach\">3</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">10</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Controller Settings</property>\n        <property name=\"show_close_button\">True</property>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/creg.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkTextBuffer\" id=\"buffRawData\">\n    <signal name=\"changed\" handler=\"on_buffRawData_changed\" swapped=\"no\"/>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstAccessMode\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n      <!-- column-name enabled -->\n      <column type=\"gboolean\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">hid</col>\n        <col id=\"1\" translatable=\"yes\">USB HID</col>\n        <col id=\"2\">False</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">evdev</col>\n        <col id=\"1\" translatable=\"yes\">evdev</col>\n        <col id=\"2\">True</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstButtons\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name a -->\n      <column type=\"GdkPixbuf\"/>\n      <!-- column-name b -->\n      <column type=\"GdkPixbuf\"/>\n      <!-- column-name x -->\n      <column type=\"GdkPixbuf\"/>\n      <!-- column-name y -->\n      <column type=\"GdkPixbuf\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstControllerType\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">psx</col>\n        <col id=\"1\" translatable=\"yes\">Two sticks, symmetrical (PSx Controller)</col>\n      </row>\n      <row>\n        <col id=\"0\">x360</col>\n        <col id=\"1\" translatable=\"yes\">Two sticks, asymmetrical (x360 pad)</col>\n      </row>\n      <row>\n        <col id=\"0\">ps1</col>\n        <col id=\"1\" translatable=\"yes\">Single stick</col>\n      </row>\n      <row>\n        <col id=\"0\">snes</col>\n        <col id=\"1\" translatable=\"yes\">Single DPAD (NES/SNES controller)</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstDevices\">\n    <columns>\n      <!-- column-name path -->\n      <column type=\"gchararray\"/>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name icon -->\n      <column type=\"GdkPixbuf\"/>\n    </columns>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">650</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"kill_tester\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkStack\" id=\"stDialog\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"transition_type\">slide-left</property>\n        <property name=\"interpolate_size\">True</property>\n        <child>\n          <object class=\"GtkGrid\" id=\"grDevices\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"margin_bottom\">10</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Controller Device</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label5\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"label\" translatable=\"yes\">This dialog allows to setup traditional controllers to be used with SC Controller. \n\nPlease, select your controller from list bellow and click Next.</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScrolledWindow\" id=\"swDevices\">\n                <property name=\"height_request\">200</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"vexpand\">True</property>\n                <property name=\"border_width\">1</property>\n                <property name=\"shadow_type\">in</property>\n                <child>\n                  <object class=\"GtkTreeView\" id=\"tvDevices\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"model\">lstDevices</property>\n                    <property name=\"headers_visible\">False</property>\n                    <child internal-child=\"selection\">\n                      <object class=\"GtkTreeSelection\" id=\"tsDevices\"/>\n                    </child>\n                    <child>\n                      <object class=\"GtkTreeViewColumn\" id=\"tcDevice\">\n                        <property name=\"title\" translatable=\"yes\">column</property>\n                        <child>\n                          <object class=\"GtkCellRendererPixbuf\" id=\"tcDeviceIcon\"/>\n                          <attributes>\n                            <attribute name=\"pixbuf\">2</attribute>\n                          </attributes>\n                        </child>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"tcDeviceName\"/>\n                          <attributes>\n                            <attribute name=\"text\">1</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbShowAllDevices\">\n                <property name=\"label\" translatable=\"yes\">Show All Devices</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"halign\">end</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"xalign\">1</property>\n                <property name=\"draw_indicator\">True</property>\n                <signal name=\"toggled\" handler=\"refresh_devices\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btnRefresh\">\n                <property name=\"label\">gtk-refresh</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"relief\">half</property>\n                <property name=\"use_stock\">True</property>\n                <signal name=\"clicked\" handler=\"refresh_devices\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page0</property>\n            <property name=\"title\" translatable=\"yes\">page0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grSettings\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"margin_bottom\">10</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">&lt;b&gt;Input Configuration&lt;/b&gt;\nPlease, select options that are best describing controller you are configuring.</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbControllerType\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"model\">lstControllerType</property>\n                <property name=\"active\">0</property>\n                <signal name=\"changed\" handler=\"refresh_controller_image\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"crControllerType\"/>\n                  <attributes>\n                    <attribute name=\"text\">1</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"label\" translatable=\"yes\">Sticks</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"label\" translatable=\"yes\">Labels</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbControllerButtons\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"model\">lstButtons</property>\n                <property name=\"active\">0</property>\n                <signal name=\"changed\" handler=\"refresh_controller_image\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererPixbuf\" id=\"crControllerButtonsA\">\n                    <property name=\"width\">30</property>\n                  </object>\n                  <attributes>\n                    <attribute name=\"pixbuf\">1</attribute>\n                  </attributes>\n                </child>\n                <child>\n                  <object class=\"GtkCellRendererPixbuf\" id=\"crControllerButtonsB\">\n                    <property name=\"width\">30</property>\n                  </object>\n                  <attributes>\n                    <attribute name=\"pixbuf\">2</attribute>\n                  </attributes>\n                </child>\n                <child>\n                  <object class=\"GtkCellRendererPixbuf\" id=\"crControllerButtonsX\">\n                    <property name=\"width\">30</property>\n                  </object>\n                  <attributes>\n                    <attribute name=\"pixbuf\">3</attribute>\n                  </attributes>\n                </child>\n                <child>\n                  <object class=\"GtkCellRendererPixbuf\" id=\"crControllerButtonsY\">\n                    <property name=\"width\">30</property>\n                  </object>\n                  <attributes>\n                    <attribute name=\"pixbuf\">4</attribute>\n                  </attributes>\n                </child>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"crControllerButtonsText\"/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">4</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvController\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"margin_bottom\">10</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"vexpand\">True</property>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">5</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page1</property>\n            <property name=\"title\" translatable=\"yes\">page1</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grBindings\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_bottom\">10</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label6\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">&lt;b&gt;Input Mappings&lt;/b&gt;\n\nPlease, press every button on the controller, push every trigger at least twice and move every stick. Make sure that correct buttons, sticks and triggers on image blinks blue when you do so.\n\nIf anything is assigned incorrectly, click on button on image to (re)assign physical button to it.</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"wrap\">True</property>\n                <property name=\"max_width_chars\">60</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label7\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">Red colored controls are not assigned.</property>\n                <property name=\"wrap\">True</property>\n                <property name=\"max_width_chars\">60</property>\n                <property name=\"xalign\">1</property>\n                <attributes>\n                  <attribute name=\"style\" value=\"italic\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkAlignment\" id=\"alController\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"xscale\">0</property>\n                <child>\n                  <object class=\"GtkFixed\" id=\"fxController\">\n                    <property name=\"width_request\">32</property>\n                    <property name=\"height_request\">32</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvAdditionalOptions\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grAdditionalOptions\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">20</property>\n                    <property name=\"margin_right\">20</property>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbEmulateC\">\n                        <property name=\"label\" translatable=\"yes\">Emulate center (guide) button by pressing Back + Start at once</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"draw_indicator\">True</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblInvertAxes\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_top\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"label\" translatable=\"yes\">Invert Axes</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">1</property>\n                        <property name=\"width\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblLeftStick\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">40</property>\n                        <property name=\"label\" translatable=\"yes\">Left stick</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblRigthStick\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">40</property>\n                        <property name=\"label\" translatable=\"yes\">Right stick (right pad)</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDPAD\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">40</property>\n                        <property name=\"label\" translatable=\"yes\">DPAD (left pad)</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbInvert_0\">\n                        <property name=\"label\" translatable=\"yes\">X</property>\n                        <property name=\"name\">cbInvert_0</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"cbInvert_toggled_cb\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbInvert_1\">\n                        <property name=\"label\" translatable=\"yes\">Y</property>\n                        <property name=\"name\">cbInvert_1</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"cbInvert_toggled_cb\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbInvert_2\">\n                        <property name=\"label\" translatable=\"yes\">X</property>\n                        <property name=\"name\">cbInvert_2</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"cbInvert_toggled_cb\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbInvert_4\">\n                        <property name=\"label\" translatable=\"yes\">X</property>\n                        <property name=\"name\">cbInvert_4</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"cbInvert_toggled_cb\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbInvert_3\">\n                        <property name=\"label\" translatable=\"yes\">Y</property>\n                        <property name=\"name\">cbInvert_3</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"cbInvert_toggled_cb\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkCheckButton\" id=\"cbInvert_5\">\n                        <property name=\"label\" translatable=\"yes\">Y</property>\n                        <property name=\"name\">cbInvert_5</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"cbInvert_toggled_cb\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">4</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblAccessMode\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_top\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Access Mode</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">5</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkComboBox\" id=\"cbAccessMode\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"model\">lstAccessMode</property>\n                        <property name=\"active\">1</property>\n                        <signal name=\"changed\" handler=\"on_cbAccessMode_changed\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"crAccessMode1\"/>\n                          <attributes>\n                            <attribute name=\"sensitive\">2</attribute>\n                            <attribute name=\"markup\">1</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">5</property>\n                        <property name=\"width\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">5</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkExpander\" id=\"exAdditionalOptions\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">20</property>\n                <signal name=\"activate\" handler=\"on_exAdditionalOptions_activate\" swapped=\"no\"/>\n                <child>\n                  <placeholder/>\n                </child>\n                <child type=\"label\">\n                  <object class=\"GtkLabel\" id=\"lblAdditionalOptions\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Advanced Options</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">4</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkExpander\" id=\"exRawData\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">5</property>\n                <signal name=\"activate\" handler=\"on_exRawData_activate\" swapped=\"no\"/>\n                <child>\n                  <placeholder/>\n                </child>\n                <child type=\"label\">\n                  <object class=\"GtkLabel\" id=\"lblRawData\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Display Raw Data</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">6</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvRawData\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"vexpand\">True</property>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swRawData\">\n                    <property name=\"height_request\">300</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"border_width\">1</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTextView\" id=\"txtRawData\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"buffer\">buffRawData</property>\n                        <property name=\"monospace\">True</property>\n                      </object>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">7</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRevealer\" id=\"rvHIDWarning\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkInfoBar\" id=\"ibHIDWarning\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"show_close_button\">True</property>\n                    <signal name=\"response\" handler=\"on_ibHIDWarning_response\" swapped=\"no\"/>\n                    <child internal-child=\"action_area\">\n                      <object class=\"GtkButtonBox\" id=\"box1HIDWarning\">\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"spacing\">6</property>\n                        <property name=\"layout_style\">end</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                        <child>\n                          <placeholder/>\n                        </child>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">False</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child internal-child=\"content_area\">\n                      <object class=\"GtkBox\" id=\"box2HIDWarning\">\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"spacing\">16</property>\n                        <child>\n                          <object class=\"GtkImage\" id=\"imgHIDWarning\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-dialog-warning</property>\n                            <property name=\"icon_size\">6</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblHIDWarning\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Warning: evdev access mode was auto-selected for HID device.\n\nThat means your account lacks permissons needed to read controller exclusivelly. You can still use controller in evdev mode, but you may have trouble with some games detecting it twice.\n\n&lt;a href=\"https://github.com/kozec/sc-controller/wiki/Evdev-and-HID-mode-for-non-steam-controllers\"&gt;Click here&lt;/a&gt; for more info.</property>\n                            <property name=\"use_markup\">True</property>\n                            <property name=\"justify\">fill</property>\n                            <property name=\"wrap\">True</property>\n                            <property name=\"max_width_chars\">60</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">False</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page2</property>\n            <property name=\"title\" translatable=\"yes\">page2</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grDS4\">\n            <property name=\"name\">01</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">20</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_top\">20</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label8\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">&lt;b&gt;DS4 Controller&lt;/b&gt;\n\nSC-Controller supports DS4 controllers automatically\nand requires no additional configuration. All you need\nis to make sure \"DS4 Controller Support\" checkbox\nis enabled in Settings or here:</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"wrap\">True</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbDS4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"margin_bottom\">20</property>\n                <property name=\"draw_indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_cbDS4_toggled\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblDS4\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">_Enable DS4 Controller Support</property>\n                    <property name=\"use_underline\">True</property>\n                    <property name=\"mnemonic_widget\">cbDS4</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label9\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">Change will be applied after emulation is restarted.\nIf you have any games running, restarting emulation will\n\"unplug\" virtual gamepad, what may cause them to ignore \nfuture inputs or even crash.</property>\n                <property name=\"use_markup\">True</property>\n                <property name=\"wrap\">True</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkImage\" id=\"imgDS4\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_right\">30</property>\n                <property name=\"xalign\">0.5</property>\n                <property name=\"yalign\">0</property>\n                <property name=\"stock\">gtk-missing-image</property>\n                <property name=\"icon_size\">6</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"height\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"name\">page3</property>\n            <property name=\"title\" translatable=\"yes\">page3</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Controller Registration</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btBack\">\n            <property name=\"label\" translatable=\"yes\">_Back</property>\n            <property name=\"visible\">True</property>\n            <property name=\"sensitive\">False</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_underline\">True</property>\n            <signal name=\"clicked\" handler=\"on_btBack_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btNext\">\n            <property name=\"label\" translatable=\"yes\">_Next</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_underline\">True</property>\n            <signal name=\"clicked\" handler=\"on_btNext_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkWindow\" id=\"dlgPressButton\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"modal\">True</property>\n    <property name=\"skip_taskbar_hint\">True</property>\n    <property name=\"skip_pager_hint\">True</property>\n    <property name=\"deletable\">False</property>\n    <property name=\"transient_for\">Dialog</property>\n    <child>\n      <object class=\"GtkBox\" id=\"vbJustFuckingBox\">\n        <property name=\"name\">vb687i</property>\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"orientation\">vertical</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblPressButton\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">25</property>\n            <property name=\"margin_bottom\">25</property>\n            <property name=\"label\" translatable=\"yes\">Press button...</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btCancelInput\">\n            <property name=\"label\">gtk-cancel</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"margin_left\">50</property>\n            <property name=\"margin_right\">50</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"margin_bottom\">10</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btCancelInput_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"hb485\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Button Input</property>\n        <child>\n          <placeholder/>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstTriggers\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">l1r1</col>\n        <col id=\"1\" translatable=\"yes\">L1 L2 R1 R2</col>\n      </row>\n      <row>\n        <col id=\"0\">ltrt</col>\n        <col id=\"1\" translatable=\"yes\">LB LT RB RT</col>\n      </row>\n      <row>\n        <col id=\"0\">lr</col>\n        <col id=\"1\" translatable=\"yes\">L R</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkMenu\" id=\"mnuStick\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuStickPress\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Change mapping for _Pressing stick</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuStickPress_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n    <child>\n      <object class=\"GtkMenuItem\" id=\"mnuStickmove\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"label\" translatable=\"yes\">Change mapping for _Moving stick</property>\n        <property name=\"use_underline\">True</property>\n        <signal name=\"activate\" handler=\"on_mnuStickmove_activate\" swapped=\"no\"/>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/global_settings.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.38.2 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjLED\">\n    <property name=\"upper\">100</property>\n    <property name=\"step-increment\">1</property>\n    <property name=\"page-increment\">10</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSensitivityX\">\n    <property name=\"lower\">0.01</property>\n    <property name=\"upper\">10.01</property>\n    <property name=\"value\">1</property>\n    <property name=\"step-increment\">0.10</property>\n    <property name=\"page-increment\">1</property>\n    <property name=\"page-size\">0.01</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSensitivityY\">\n    <property name=\"lower\">0.01</property>\n    <property name=\"upper\">10.01</property>\n    <property name=\"value\">1</property>\n    <property name=\"step-increment\">0.10</property>\n    <property name=\"page-increment\">1</property>\n    <property name=\"page-size\">0.01</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstControllers\">\n    <columns>\n      <!-- column-name path -->\n      <column type=\"gchararray\"/>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name icon -->\n      <column type=\"GdkPixbuf\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstItems\">\n    <columns>\n      <!-- column-name item -->\n      <column type=\"GObject\"/>\n      <!-- column-name label -->\n      <column type=\"gchararray\"/>\n      <!-- column-name profile -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOSDColorPreset\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">None</col>\n        <col id=\"1\" translatable=\"yes\">None / Custom</col>\n      </row>\n      <row>\n        <col id=\"0\">Green.colors.json</col>\n        <col id=\"1\" translatable=\"yes\">Green</col>\n      </row>\n      <row>\n        <col id=\"0\">Blue.colors.json</col>\n        <col id=\"1\" translatable=\"yes\">Blue</col>\n      </row>\n      <row>\n        <col id=\"0\">Cyan.colors.json</col>\n        <col id=\"1\" translatable=\"yes\">Cyan</col>\n      </row>\n      <row>\n        <col id=\"0\">Yellow.colors.json</col>\n        <col id=\"1\" translatable=\"yes\">Yellow</col>\n      </row>\n      <row>\n        <col id=\"0\">Red.colors.json</col>\n        <col id=\"1\" translatable=\"yes\">Red</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstOSDStyle\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">Classic.gtkstyle.css</col>\n        <col id=\"1\" translatable=\"yes\">Classic</col>\n      </row>\n      <row>\n        <col id=\"0\">Reloaded.gtkstyle.css</col>\n        <col id=\"1\" translatable=\"yes\">Reloaded</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstProfile\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name giofile -->\n      <column type=\"GObject\"/>\n      <!-- column-name changed -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstStickAction\">\n    <columns>\n      <!-- column-name label -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n      <!-- column-name is_custom -->\n      <column type=\"gboolean\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Move Keyboard</col>\n        <col id=\"1\">OSK.move()</col>\n        <col id=\"2\">False</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Emulate Arrows</col>\n        <col id=\"1\">dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))</col>\n        <col id=\"2\">False</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Emulate Arrows, Move Keyboard when Left Grip is Pressed</col>\n        <col id=\"1\">mode(LGRIP, OSK.move(), dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT)))</col>\n        <col id=\"2\">False</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Emulate Arrows, Move Keyboard when Right Grip is Pressed</col>\n        <col id=\"1\">mode(RGRIP, OSK.move(), dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT)))</col>\n        <col id=\"2\">False</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstTriggersAction\">\n    <columns>\n      <!-- column-name label -->\n      <column type=\"gchararray\"/>\n      <!-- column-name action -->\n      <column type=\"gchararray\"/>\n      <!-- column-name is_custom -->\n      <column type=\"gboolean\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Shift and Ctrl</col>\n        <col id=\"1\">trigger(50, button(Keys.KEY_LEFTSHIFT))|trigger(50, button(Keys.KEY_LEFTCTRL))</col>\n        <col id=\"2\">False</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Press Keyboard Buttons</col>\n        <col id=\"1\">OSK.press(LEFT)|OSK.press(RIGHT)</col>\n        <col id=\"2\">False</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width-request\">650</property>\n    <property name=\"can-focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window-position\">center-on-parent</property>\n    <property name=\"destroy-with-parent\">True</property>\n    <property name=\"type-hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"on_Dialog_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkBox\" id=\"vbMain\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvRestartWarning\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <object class=\"GtkInfoBar\" id=\"infoBarRestartWarning\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"message-type\">warning</property>\n                <child internal-child=\"action_area\">\n                  <object class=\"GtkButtonBox\" id=\"bbRestartWarning\">\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"spacing\">6</property>\n                    <property name=\"layout-style\">end</property>\n                    <child>\n                      <placeholder/>\n                    </child>\n                    <child>\n                      <placeholder/>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRestartEmulation\">\n                        <property name=\"label\" translatable=\"yes\">Restart Emulation</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"receives-default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_btRestartEmulation_clicked\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">False</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child internal-child=\"content_area\">\n                  <object class=\"GtkBox\" id=\"vbRestartWarning\">\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"spacing\">16</property>\n                    <child>\n                      <object class=\"GtkImage\" id=\"imgRestartWarning\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"stock\">gtk-dialog-warning</property>\n                        <property name=\"icon_size\">6</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblRestartWarning\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Emulation has to be restarted to apply all settings.\nIf you have any games running, restarting emulation will \"unplug\"\nvirtual gamepad, what may cause them to ignore future inputs\nor even crash.</property>\n                        <property name=\"wrap\">True</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <placeholder/>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">False</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkNotebook\" id=\"notebook7\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <child>\n              <!-- n-columns=6 n-rows=13 -->\n              <object class=\"GtkGrid\" id=\"grid3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"margin-left\">10</property>\n                <property name=\"margin-right\">10</property>\n                <property name=\"margin-top\">10</property>\n                <property name=\"margin-bottom\">10</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label8\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">On-Screen Keyboard Bindings</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">6</property>\n                    <property name=\"width\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label9\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Stick</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">7</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label10\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Triggers</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"butEditKeyboardBindings\">\n                    <property name=\"label\" translatable=\"yes\">Advanced...</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">50</property>\n                    <property name=\"margin-right\">50</property>\n                    <property name=\"margin-top\">15</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"clicked\" handler=\"on_butEditKeyboardBindings_clicked\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">9</property>\n                    <property name=\"width\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBox\" id=\"cbStickAction\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"model\">lstStickAction</property>\n                    <signal name=\"changed\" handler=\"on_cbStickAction_changed\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkCellRendererText\" id=\"crt1\"/>\n                      <attributes>\n                        <attribute name=\"text\">0</attribute>\n                      </attributes>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">7</property>\n                    <property name=\"width\">5</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBox\" id=\"cbTriggersAction\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"model\">lstTriggersAction</property>\n                    <signal name=\"changed\" handler=\"on_cbTriggersAction_changed\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkCellRendererText\" id=\"crt2\"/>\n                      <attributes>\n                        <attribute name=\"text\">0</attribute>\n                      </attributes>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">8</property>\n                    <property name=\"width\">5</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label4\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-top\">20</property>\n                    <property name=\"label\" translatable=\"yes\">Pad Sensitivity</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">10</property>\n                    <property name=\"width\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label5\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Horizontal</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">11</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label6\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Vertical</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">12</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScale\" id=\"sclSensX\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"adjustment\">adjSensitivityX</property>\n                    <property name=\"round-digits\">1</property>\n                    <property name=\"value-pos\">right</property>\n                    <signal name=\"value-changed\" handler=\"on_sens_value_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">11</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScale\" id=\"sclSensY\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"adjustment\">adjSensitivityY</property>\n                    <property name=\"round-digits\">1</property>\n                    <property name=\"value-pos\">right</property>\n                    <signal name=\"value-changed\" handler=\"on_sens_value_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">12</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearSensX\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">10</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-top\">5</property>\n                    <signal name=\"clicked\" handler=\"on_btClearSensX_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image77\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">5</property>\n                    <property name=\"top-attach\">11</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btClearSensY\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">10</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-top\">5</property>\n                    <signal name=\"clicked\" handler=\"on_btClearSensY_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkImage\" id=\"image89\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"stock\">gtk-clear</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">5</property>\n                    <property name=\"top-attach\">12</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label37\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Default Menu Items</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">0</property>\n                    <property name=\"width\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_0\">\n                    <property name=\"name\">cbMI_0</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label98\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">List of recent profiles</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">1</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_6\">\n                    <property name=\"name\">cbMI_6</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">5</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label93\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Run Program...</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">1</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_1\">\n                    <property name=\"name\">cbMI_1</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label99\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Autoswitch options</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">2</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_5\">\n                    <property name=\"name\">cbMI_5</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">5</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label91\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Kill Current Window</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">2</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_2\">\n                    <property name=\"name\">cbMI_2</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label97\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Window switcher</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">3</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_7\">\n                    <property name=\"name\">cbMI_7</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">5</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label38\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Show Current Bindings</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">3</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_8\">\n                    <property name=\"name\">cbMI_8</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label39\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Games List</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">4</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_4\">\n                    <property name=\"name\">cbMI_4</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">5</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label95\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Turn Controller OFF</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">5</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_3\">\n                    <property name=\"name\">cbMI_3</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label7\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Display Keyboard</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">5</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMI_9\">\n                    <property name=\"name\">cbMI_9</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">5</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbMI_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label11\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Edit Current Bindings</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">4</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"label18\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Menus &amp; Keyboard</property>\n              </object>\n              <packing>\n                <property name=\"tab-expand\">True</property>\n                <property name=\"tab-fill\">False</property>\n              </packing>\n            </child>\n            <child>\n              <!-- n-columns=4 n-rows=11 -->\n              <object class=\"GtkGrid\" id=\"grid67\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"margin-left\">10</property>\n                <property name=\"margin-right\">10</property>\n                <property name=\"margin-top\">10</property>\n                <property name=\"margin-bottom\">10</property>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbtext\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbmenuitem_border\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbbackground\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbborder\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">5</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblmenuseparator\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Separator Text</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblmenuitem_hilight_text\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Selected Text</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblmenuitem_hilight\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Selected Background</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">5</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblmenuitem_hilight_border\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Selected Border</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbmenuseparator\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbmenuitem_hilight_text\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbmenuitem_hilight\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">5</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbmenuitem_hilight_border\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label2985\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">OSD Menu Colors</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">2</property>\n                    <property name=\"width\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lbltext\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Text</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblmenuitem_border\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Item Border</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblbackground\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Background</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblborder\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Border</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">5</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label21\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-top\">10</property>\n                    <property name=\"label\" translatable=\"yes\">On-Screen Keyboard Colors</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">7</property>\n                    <property name=\"width\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblosk_button1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Normal Button</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbosk_button1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblosk_pressed\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Pressed Button</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">9</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbosk_pressed\">\n                    <property name=\"name\">20</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">9</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblosk_button2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Special Button</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">10</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbosk_button2\">\n                    <property name=\"name\">20</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">10</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblosk_text\">\n                    <property name=\"name\">20</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Text</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbosk_text\">\n                    <property name=\"name\">20</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblosk_hilight\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Higlight</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">9</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkColorButton\" id=\"cbosk_hilight\">\n                    <property name=\"name\">20</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <signal name=\"color-set\" handler=\"on_osd_color_set\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">3</property>\n                    <property name=\"top-attach\">9</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label269\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\"> </property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">10</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">OSD Style</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">0</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBox\" id=\"cbOSDStyle\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">50</property>\n                    <property name=\"margin-right\">10</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"margin-bottom\">10</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"model\">lstOSDStyle</property>\n                    <property name=\"active\">0</property>\n                    <signal name=\"changed\" handler=\"on_cbOSDStyle_changed\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkCellRendererText\" id=\"crt3\"/>\n                      <attributes>\n                        <attribute name=\"text\">1</attribute>\n                      </attributes>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">1</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label119\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Color Preset</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">0</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBox\" id=\"cbOSDColorPreset\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-right\">50</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"margin-bottom\">10</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"model\">lstOSDColorPreset</property>\n                    <property name=\"active\">0</property>\n                    <signal name=\"changed\" handler=\"on_cbOSDColorPreset_changed\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkCellRendererText\" id=\"crt4\"/>\n                      <attributes>\n                        <attribute name=\"text\">1</attribute>\n                      </attributes>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">2</property>\n                    <property name=\"top-attach\">1</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"label19\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">OSD Colors</property>\n              </object>\n              <packing>\n                <property name=\"position\">1</property>\n                <property name=\"tab-expand\">True</property>\n                <property name=\"tab-fill\">False</property>\n              </packing>\n            </child>\n            <child>\n              <!-- n-columns=3 n-rows=14 -->\n              <object class=\"GtkGrid\" id=\"grid68\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"margin-left\">10</property>\n                <property name=\"margin-right\">10</property>\n                <property name=\"margin-top\">10</property>\n                <property name=\"margin-bottom\">10</property>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"vbEnableDriverSteam\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"sensitive\">False</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"active\">True</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblKuaa19\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable Steam Controller support</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">0</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableDriver_ds4drv\">\n                    <property name=\"name\">ds4drv</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbEnableDriver_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblKuaa13\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable Dualshock4 (PS4 controller) support</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">1</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableDriver_ds5drv\">\n                    <property name=\"name\">ds5drv</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbEnableDriver_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblKuaa3\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable DualSense (PS5 controller) support</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">3</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblKuaa4\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">30</property>\n                    <property name=\"label\" translatable=\"yes\">If enabled, any connected DualSense controller will be automatically used by SC-Controller</property>\n                    <property name=\"xalign\">0</property>\n                    <property name=\"yalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">4</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblKuaa15\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-top\">20</property>\n                    <property name=\"label\" translatable=\"yes\">Other registered controllers</property>\n                    <property name=\"xalign\">0</property>\n                    <property name=\"yalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">10</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblKuaa14\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">30</property>\n                    <property name=\"label\" translatable=\"yes\">If enabled, any connected Dualshock4 controller will be automatically used by SC-Controller</property>\n                    <property name=\"xalign\">0</property>\n                    <property name=\"yalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">2</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblKuaa16\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"label\" translatable=\"yes\">Controller listed here are automatically used by SC-Controller whenever they are connected.</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">11</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swControllers\">\n                    <property name=\"height-request\">100</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"shadow-type\">in</property>\n                    <child>\n                      <object class=\"GtkTreeView\" id=\"tvControllers\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"model\">lstControllers</property>\n                        <property name=\"headers-visible\">False</property>\n                        <child internal-child=\"selection\">\n                          <object class=\"GtkTreeSelection\" id=\"tsControllers\"/>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"tcDevice\">\n                            <property name=\"title\" translatable=\"yes\">column</property>\n                            <child>\n                              <object class=\"GtkCellRendererPixbuf\" id=\"tcDeviceIcon\"/>\n                              <attributes>\n                                <attribute name=\"pixbuf\">2</attribute>\n                              </attributes>\n                            </child>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"tcDeviceName\"/>\n                              <attributes>\n                                <attribute name=\"markup\">1</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">12</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableDriver_hiddrv\">\n                    <property name=\"name\">hiddrv</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbEnableDriver_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblKuaa12\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable HID device support</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">5</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableDriver_evdevdrv\">\n                    <property name=\"name\">evdevdrv</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbEnableDriver_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblKuaa11\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable evdev support</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">6</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbButtons1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">20</property>\n                    <property name=\"margin-right\">20</property>\n                    <property name=\"margin-top\">3</property>\n                    <property name=\"hexpand\">True</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btAddController\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"receives-default\">True</property>\n                        <property name=\"hexpand\">False</property>\n                        <property name=\"vexpand\">False</property>\n                        <signal name=\"clicked\" handler=\"on_btAddController_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkBox\" id=\"box1\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <child>\n                              <object class=\"GtkImage\" id=\"image1\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"stock\">gtk-add</property>\n                                <property name=\"icon_size\">3</property>\n                              </object>\n                              <packing>\n                                <property name=\"expand\">False</property>\n                                <property name=\"fill\">True</property>\n                                <property name=\"position\">0</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <object class=\"GtkLabel\" id=\"label1\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"label\" translatable=\"yes\">Register New Controller</property>\n                              </object>\n                              <packing>\n                                <property name=\"expand\">False</property>\n                                <property name=\"fill\">True</property>\n                                <property name=\"position\">1</property>\n                              </packing>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblPlaceholder1\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"hexpand\">True</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRemoveController\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"receives-default\">True</property>\n                        <property name=\"hexpand\">False</property>\n                        <property name=\"vexpand\">False</property>\n                        <signal name=\"clicked\" handler=\"on_btRemoveController_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image2\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"stock\">gtk-remove</property>\n                            <property name=\"icon_size\">3</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">13</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"txEvdevMissing\">\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"no-show-all\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Note: python-evdev package is needed for non-steam controller support</property>\n                    <attributes>\n                      <attribute name=\"style\" value=\"italic\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">7</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableDriver_remotepad\">\n                    <property name=\"name\">remotepad</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbEnableDriver_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblKuaa1\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable Remote RetroPad protocol support</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">8</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblKuaa2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">30</property>\n                    <property name=\"label\" translatable=\"yes\">If enabled, any device or application compatibile with Retroarch's Remote RetroPad protocol\non network can connect and remote-control this computer as additional gamepad.\n&lt;a href=\"https://github.com/kozec/sc-controller/wiki/Using-phone-with-Retroarch-as-additional-controller\"&gt;See wiki for instructions and more info.&lt;/a&gt;</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"xalign\">0</property>\n                    <property name=\"yalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">9</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"label69\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Controllers</property>\n              </object>\n              <packing>\n                <property name=\"position\">2</property>\n                <property name=\"tab-fill\">False</property>\n              </packing>\n            </child>\n            <child>\n              <!-- n-columns=3 n-rows=5 -->\n              <object class=\"GtkGrid\" id=\"grid1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"margin-left\">10</property>\n                <property name=\"margin-right\">10</property>\n                <property name=\"margin-top\">10</property>\n                <property name=\"margin-bottom\">10</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"vexpand\">True</property>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbShowOSD\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">10</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbShowOSD_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label42\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Show _OSD notification when profile is switched automatically</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblItems\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-top\">10</property>\n                    <property name=\"label\" translatable=\"yes\">Switching Rules</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swItems\">\n                    <property name=\"name\">sw</property>\n                    <property name=\"height-request\">200</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"margin-left\">10</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"margin-bottom\">5</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"shadow-type\">in</property>\n                    <child>\n                      <object class=\"GtkTreeView\" id=\"tvItems\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"model\">lstItems</property>\n                        <property name=\"reorderable\">True</property>\n                        <property name=\"rubber-banding\">True</property>\n                        <signal name=\"cursor-changed\" handler=\"on_tvItems_cursor_changed\" swapped=\"no\"/>\n                        <signal name=\"row-activated\" handler=\"btEdit_clicked_cb\" swapped=\"no\"/>\n                        <child internal-child=\"selection\">\n                          <object class=\"GtkTreeSelection\" id=\"nonimportatboxthatneedsidjustbecauseofubunt\"/>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"clWhen\">\n                            <property name=\"resizable\">True</property>\n                            <property name=\"title\" translatable=\"yes\">When window...</property>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crWhen\"/>\n                              <attributes>\n                                <attribute name=\"text\">1</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"clThen\">\n                            <property name=\"resizable\">True</property>\n                            <property name=\"title\" translatable=\"yes\">... then</property>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crProfile\"/>\n                              <attributes>\n                                <attribute name=\"text\">2</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblItems1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Automatic Profile Switching Options</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbButtons\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">10</property>\n                    <property name=\"hexpand\">True</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btEdit\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"receives-default\">True</property>\n                        <property name=\"hexpand\">False</property>\n                        <property name=\"vexpand\">False</property>\n                        <signal name=\"clicked\" handler=\"btEdit_clicked_cb\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkBox\" id=\"box4\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <child>\n                              <object class=\"GtkImage\" id=\"image34\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"stock\">gtk-edit</property>\n                                <property name=\"icon_size\">3</property>\n                              </object>\n                              <packing>\n                                <property name=\"expand\">False</property>\n                                <property name=\"fill\">True</property>\n                                <property name=\"position\">0</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <object class=\"GtkLabel\" id=\"label90\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"margin-left\">5</property>\n                                <property name=\"label\" translatable=\"yes\">Edit Condition</property>\n                              </object>\n                              <packing>\n                                <property name=\"expand\">False</property>\n                                <property name=\"fill\">True</property>\n                                <property name=\"position\">1</property>\n                              </packing>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblPlaceholder\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"hexpand\">True</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btRemove\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"receives-default\">True</property>\n                        <property name=\"margin-left\">10</property>\n                        <property name=\"hexpand\">False</property>\n                        <property name=\"vexpand\">False</property>\n                        <signal name=\"clicked\" handler=\"on_btRemove_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image35\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"stock\">gtk-remove</property>\n                            <property name=\"icon_size\">3</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btAdd\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"receives-default\">True</property>\n                        <property name=\"margin-left\">10</property>\n                        <property name=\"hexpand\">False</property>\n                        <property name=\"vexpand\">False</property>\n                        <signal name=\"clicked\" handler=\"on_btAdd_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image39\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"stock\">gtk-add</property>\n                            <property name=\"icon_size\">3</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">3</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"label78\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Autoswitcher</property>\n              </object>\n              <packing>\n                <property name=\"position\">3</property>\n                <property name=\"tab-expand\">True</property>\n                <property name=\"tab-fill\">False</property>\n              </packing>\n            </child>\n            <child>\n              <!-- n-columns=3 n-rows=10 -->\n              <object class=\"GtkGrid\" id=\"grid12\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"margin-left\">10</property>\n                <property name=\"margin-right\">10</property>\n                <property name=\"margin-top\">10</property>\n                <property name=\"margin-bottom\">10</property>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbInputTestMode\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_random_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label27\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable Input Test Mode</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label28\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">25</property>\n                    <property name=\"margin-right\">25</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Allows applications to &lt;b&gt;watch and possibly log&lt;/b&gt; gamepad inputs.\n\nWhen enabled, main application window will display pressed buttons,\ngrips, triggers, finger positions on both pads and stick angle.</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"wrap\">True</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableSerials\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">15</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_restarting_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label32\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Use Serial Numbers to Identify Controllers</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbAutokillDaemon\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">15</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_random_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label30\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Automatically Disable Emulation When Closing GUI</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMinimizeToStatusIcon\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">30</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_random_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label31\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Minimize to status icon instead closing</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">6</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableStatusIcon\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">15</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_random_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label29\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable Status (Systray) Icon</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">5</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label33\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">25</property>\n                    <property name=\"margin-right\">25</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Try disabling this option your controller stops working randomly.</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"wrap\">True</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">4</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbEnableRumble\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">15</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_restarting_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label34\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Enable Rumble Support</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbNewRelease\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-top\">15</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_random_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label40\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Display message about new version after SC-Controller upgrade</property>\n                        <property name=\"use-underline\">True</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">9</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbMinimizeOnStart\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"receives-default\">False</property>\n                    <property name=\"margin-left\">30</property>\n                    <property name=\"margin-top\">5</property>\n                    <property name=\"draw-indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_random_checkbox_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label15\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Minimize to tray on start</property>\n                        <property name=\"mnemonic-widget\">cbShowOSD</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">7</property>\n                  </packing>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"position\">4</property>\n              </packing>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"label79\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Advanced</property>\n              </object>\n              <packing>\n                <property name=\"position\">4</property>\n                <property name=\"tab-expand\">True</property>\n                <property name=\"tab-fill\">False</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"title\">Settings</property>\n        <property name=\"show-close-button\">True</property>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkWindow\" id=\"ConditionEditor\">\n    <property name=\"width-request\">500</property>\n    <property name=\"can-focus\">False</property>\n    <property name=\"role\">condition editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"modal\">True</property>\n    <property name=\"window-position\">center-on-parent</property>\n    <property name=\"type-hint\">dialog</property>\n    <property name=\"skip-taskbar-hint\">True</property>\n    <property name=\"skip-pager-hint\">True</property>\n    <property name=\"transient-for\">Dialog</property>\n    <signal name=\"delete-event\" handler=\"hide_dont_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_ConditionEditor_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkBox\" id=\"vbox321\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"margin-left\">20</property>\n        <property name=\"margin-right\">25</property>\n        <property name=\"margin-top\">5</property>\n        <property name=\"margin-bottom\">15</property>\n        <property name=\"orientation\">vertical</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"label3\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"margin-top\">5</property>\n            <property name=\"label\" translatable=\"yes\">Condition</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkCheckButton\" id=\"cbMatchTitle\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"receives-default\">False</property>\n            <property name=\"margin-top\">5</property>\n            <property name=\"margin-bottom\">5</property>\n            <property name=\"xalign\">0</property>\n            <property name=\"draw-indicator\">True</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label74\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Match Window Title</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbBox1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"margin-left\">20</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkEntry\" id=\"entTitle\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <signal name=\"changed\" handler=\"on_entTitle_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbExactTitle\">\n                <property name=\"label\" translatable=\"yes\">Match exact title</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">False</property>\n                <property name=\"xalign\">0</property>\n                <property name=\"draw-indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_cbExactTitle_toggled\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbRegExp\">\n                <property name=\"label\" translatable=\"yes\">Use regular expression</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">False</property>\n                <property name=\"xalign\">0</property>\n                <property name=\"draw-indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_cbRegExp_toggled\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkCheckButton\" id=\"cbMatchClass\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"receives-default\">False</property>\n            <property name=\"margin-top\">15</property>\n            <property name=\"margin-bottom\">5</property>\n            <property name=\"xalign\">0</property>\n            <property name=\"draw-indicator\">True</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label35\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Match Window Class</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkEntry\" id=\"entClass\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"margin-left\">20</property>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label36\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"margin-top\">15</property>\n            <property name=\"label\" translatable=\"yes\">Action</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">5</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRadioButton\" id=\"rbProfile\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"receives-default\">False</property>\n            <property name=\"margin-top\">5</property>\n            <property name=\"active\">True</property>\n            <property name=\"draw-indicator\">True</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label77\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Activate Profile</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">6</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkComboBox\" id=\"cbProfile\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"margin-left\">25</property>\n            <property name=\"margin-right\">5</property>\n            <property name=\"margin-top\">5</property>\n            <property name=\"model\">lstProfile</property>\n            <child>\n              <object class=\"GtkCellRendererText\" id=\"cellrenderertext1\"/>\n              <attributes>\n                <attribute name=\"text\">0</attribute>\n              </attributes>\n            </child>\n            <child>\n              <object class=\"GtkCellRendererText\" id=\"cellrenderertext2\">\n                <property name=\"xalign\">1</property>\n                <property name=\"alignment\">right</property>\n                <property name=\"style\">italic</property>\n              </object>\n              <attributes>\n                <attribute name=\"text\">2</attribute>\n              </attributes>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">7</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRadioButton\" id=\"rbTurnOff\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"receives-default\">False</property>\n            <property name=\"margin-top\">5</property>\n            <property name=\"active\">True</property>\n            <property name=\"draw-indicator\">True</property>\n            <property name=\"group\">rbProfile</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label41\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Turn Off Controller</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">8</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRadioButton\" id=\"rbRestart\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"receives-default\">False</property>\n            <property name=\"margin-top\">5</property>\n            <property name=\"active\">True</property>\n            <property name=\"draw-indicator\">True</property>\n            <property name=\"group\">rbProfile</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label43\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Restart Daemon</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">9</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header2\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"title\">Edit Condition</property>\n        <property name=\"show-close-button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btSave\">\n            <property name=\"label\">gtk-save</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"receives-default\">True</property>\n            <property name=\"use-stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btSave_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/icon_chooser.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkListStore\" id=\"lstDirs\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name proper_name -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstIcons\">\n    <columns>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name icon -->\n      <column type=\"GdkPixbuf\"/>\n      <!-- column-name colors -->\n      <column type=\"gboolean\"/>\n    </columns>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">650</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">icon-chooser</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkBox\" id=\"iHateUselessIDs\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <child>\n          <object class=\"GtkGrid\" id=\"grid1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"margin_bottom\">5</property>\n            <property name=\"column_homogeneous\">True</property>\n            <child>\n              <object class=\"GtkEntry\" id=\"entName\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_bottom\">2</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"editable\">False</property>\n                <property name=\"activates_default\">True</property>\n                <signal name=\"changed\" handler=\"on_entName_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScrolledWindow\" id=\"swIcons\">\n                <property name=\"name\">sw</property>\n                <property name=\"height_request\">400</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"vexpand\">True</property>\n                <property name=\"shadow_type\">in</property>\n                <child>\n                  <object class=\"GtkTreeView\" id=\"tvIcons\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"model\">lstIcons</property>\n                    <property name=\"headers_clickable\">False</property>\n                    <property name=\"rubber_banding\">True</property>\n                    <signal name=\"cursor-changed\" handler=\"on_tvItems_cursor_changed\" swapped=\"no\"/>\n                    <signal name=\"row-activated\" handler=\"on_btOk_clicked\" swapped=\"no\"/>\n                    <child internal-child=\"selection\">\n                      <object class=\"GtkTreeSelection\" id=\"tsIcons\"/>\n                    </child>\n                    <child>\n                      <object class=\"GtkTreeViewColumn\" id=\"clIcon\">\n                        <property name=\"title\" translatable=\"yes\">Icon</property>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"crIconName\"/>\n                          <attributes>\n                            <attribute name=\"text\">0</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">1</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScrolledWindow\" id=\"swCategories\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_right\">5</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"shadow_type\">in</property>\n                <child>\n                  <object class=\"GtkTreeView\" id=\"tvCategories\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"model\">lstDirs</property>\n                    <property name=\"headers_clickable\">False</property>\n                    <property name=\"rubber_banding\">True</property>\n                    <signal name=\"cursor-changed\" handler=\"on_tvCategories_cursor_changed\" swapped=\"no\"/>\n                    <child internal-child=\"selection\">\n                      <object class=\"GtkTreeSelection\" id=\"tsCategories\"/>\n                    </child>\n                    <child>\n                      <object class=\"GtkTreeViewColumn\" id=\"clCategory\">\n                        <property name=\"title\" translatable=\"yes\">Category</property>\n                        <child>\n                          <object class=\"GtkCellRendererText\" id=\"crCategory\"/>\n                          <attributes>\n                            <attribute name=\"text\">1</attribute>\n                          </attributes>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvLicense\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"margin_bottom\">5</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblLicense\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"xalign\">0</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Choose Icon</property>\n        <property name=\"has_subtitle\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkLinkButton\" id=\"btUserFolder\">\n            <property name=\"label\" translatable=\"yes\">button</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"relief\">none</property>\n            <property name=\"uri\">file:///</property>\n            <signal name=\"activate-link\" handler=\"on_btUserFolder_activate_link\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btOk\">\n            <property name=\"label\">gtk-ok</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btOk_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/import_export.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkListStore\" id=\"lstImportPackage\">\n    <columns>\n      <!-- column-name enabled -->\n      <column type=\"gint\"/>\n      <!-- column-name original_name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name target_name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name type -->\n      <column type=\"gchararray\"/>\n      <!-- column-name object -->\n      <column type=\"GObject\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstPackage\">\n    <columns>\n      <!-- column-name export -->\n      <column type=\"gboolean\"/>\n      <!-- column-name type -->\n      <column type=\"gchararray\"/>\n      <!-- column-name name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name filename -->\n      <column type=\"gchararray\"/>\n      <!-- column-name enabled -->\n      <column type=\"gboolean\"/>\n      <!-- column-name type_internal -->\n      <column type=\"gint\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstProfiles\">\n    <columns>\n      <!-- column-name index -->\n      <column type=\"gint\"/>\n      <!-- column-name giofile -->\n      <column type=\"GObject\"/>\n      <!-- column-name profile_name -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstShellCommands\">\n    <columns>\n      <!-- column-name checked -->\n      <column type=\"gboolean\"/>\n      <!-- column-name command -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstVdfProfiles\">\n    <columns>\n      <!-- column-name index -->\n      <column type=\"gint\"/>\n      <!-- column-name game -->\n      <column type=\"gchararray\"/>\n      <!-- column-name profile_name -->\n      <column type=\"gchararray\"/>\n      <!-- column-name profile_file -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkTextBuffer\" id=\"tbError\"/>\n  <object class=\"GtkDialog\" id=\"Dialog\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"type_hint\">dialog</property>\n    <property name=\"skip_taskbar_hint\">True</property>\n    <property name=\"skip_pager_hint\">True</property>\n    <child internal-child=\"vbox\">\n      <object class=\"GtkBox\" id=\"vbDialog\">\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">2</property>\n        <child internal-child=\"action_area\">\n          <object class=\"GtkButtonBox\" id=\"vbDialogButtons\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"layout_style\">end</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">False</property>\n            <property name=\"position\">4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkStack\" id=\"stDialog\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"transition_type\">slide-left-right</property>\n            <property name=\"interpolate_size\">True</property>\n            <child>\n              <object class=\"GtkGrid\" id=\"grIntro\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <property name=\"column_homogeneous\">True</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label3\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"label\" translatable=\"yes\">Please, select an option</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btExport\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"use_underline\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btExport_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkBox\" id=\"vbBtExport\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">20</property>\n                        <property name=\"margin_right\">20</property>\n                        <child>\n                          <object class=\"GtkImage\" id=\"imgBtExport\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"margin_right\">10</property>\n                            <property name=\"pixel_size\">32</property>\n                            <property name=\"icon_name\">document-save-as</property>\n                            <property name=\"icon_size\">6</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblBtExport\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">_Export Profile</property>\n                            <property name=\"use_underline\">True</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btImportSccprofile\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"use_underline\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btImportSccprofile_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkBox\" id=\"vbBtImport\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">20</property>\n                        <property name=\"margin_right\">20</property>\n                        <child>\n                          <object class=\"GtkImage\" id=\"imgBtImport\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"margin_right\">10</property>\n                            <property name=\"pixel_size\">32</property>\n                            <property name=\"icon_name\">document-open</property>\n                            <property name=\"icon_size\">6</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblBtImport\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">_Import SC-Controller Profile</property>\n                            <property name=\"use_underline\">True</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btImportVdf\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"use_underline\">True</property>\n                    <signal name=\"clicked\" handler=\"on_btImportVdf_clicked\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkBox\" id=\"vbBtImportVdf\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">20</property>\n                        <property name=\"margin_right\">20</property>\n                        <child>\n                          <object class=\"GtkImage\" id=\"imgBtImportVdf\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"margin_right\">10</property>\n                            <property name=\"pixel_size\">32</property>\n                            <property name=\"icon_name\">steam</property>\n                            <property name=\"use_fallback\">True</property>\n                            <property name=\"icon_size\">6</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblBtImportVdf\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Import _Steam Profile</property>\n                            <property name=\"use_underline\">True</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblImportText\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">You can also drag-n-drop profile file on main application window</property>\n                    <property name=\"xalign\">1</property>\n                    <property name=\"yalign\">1</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">4</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Import / Export</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grSelectProfile\">\n                <property name=\"name\">grSelectProfile</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"margin_bottom\">10</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"label\" translatable=\"yes\">Select Profile to Export</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swProfiles\">\n                    <property name=\"height_request\">400</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTreeView\" id=\"tvProfiles\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"vexpand\">True</property>\n                        <property name=\"model\">lstProfiles</property>\n                        <property name=\"search_column\">2</property>\n                        <property name=\"show_expanders\">False</property>\n                        <signal name=\"cursor-changed\" handler=\"on_tvProfiles_cursor_changed\" swapped=\"no\"/>\n                        <child internal-child=\"selection\">\n                          <object class=\"GtkTreeSelection\" id=\"tsProfiles\"/>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"tvcProfile\">\n                            <property name=\"resizable\">True</property>\n                            <property name=\"sizing\">autosize</property>\n                            <property name=\"title\" translatable=\"yes\">Profile</property>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crProfile\"/>\n                              <attributes>\n                                <attribute name=\"text\">2</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Export Profile</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grMakePackage\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblTLDR\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"label\" translatable=\"yes\">Selected profile references another profile(s) or menu(s).\nYou may wish to export and package referenced items along with it.\nOtherwise, profile switching or menus may not work when profile is imported.\n\nPlease, select what should be packaged along with exported profile.</property>\n                    <property name=\"wrap\">True</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swPackage\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">5</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTreeView\" id=\"tvPackage\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"model\">lstPackage</property>\n                        <property name=\"search_column\">2</property>\n                        <child internal-child=\"selection\">\n                          <object class=\"GtkTreeSelection\" id=\"tsPackage\"/>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"clPackageItem\">\n                            <property name=\"title\" translatable=\"yes\">Item</property>\n                            <child>\n                              <object class=\"GtkCellRendererToggle\" id=\"crPackageCheckbox\">\n                                <signal name=\"toggled\" handler=\"on_crPackageCheckbox_toggled\" swapped=\"no\"/>\n                              </object>\n                              <attributes>\n                                <attribute name=\"sensitive\">4</attribute>\n                                <attribute name=\"active\">0</attribute>\n                              </attributes>\n                            </child>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crPackageItemType\"/>\n                              <attributes>\n                                <attribute name=\"sensitive\">4</attribute>\n                                <attribute name=\"text\">1</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"clPackageName\">\n                            <property name=\"title\" translatable=\"yes\">Name</property>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crPackageItemName\"/>\n                              <attributes>\n                                <attribute name=\"sensitive\">4</attribute>\n                                <attribute name=\"text\">2</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btSelectAll\">\n                    <property name=\"label\" translatable=\"yes\">Select All</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <signal name=\"clicked\" handler=\"on_btSelectAll_clicked\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblPlaceholder\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"hexpand\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Export Profile</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grVdfImport\">\n                <property name=\"name\">grVdfImport</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"margin_top\">5</property>\n                <property name=\"margin_bottom\">10</property>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swVdfProfiles\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTreeView\" id=\"tvVdfProfiles\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"vexpand\">True</property>\n                        <property name=\"model\">lstVdfProfiles</property>\n                        <property name=\"search_column\">1</property>\n                        <property name=\"show_expanders\">False</property>\n                        <signal name=\"cursor-changed\" handler=\"on_tvVdfProfiles_cursor_changed\" swapped=\"no\"/>\n                        <child internal-child=\"selection\">\n                          <object class=\"GtkTreeSelection\" id=\"tsVdfProfiles\"/>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"tvcGame\">\n                            <property name=\"resizable\">True</property>\n                            <property name=\"sizing\">autosize</property>\n                            <property name=\"title\" translatable=\"yes\">Game</property>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crGame\"/>\n                              <attributes>\n                                <attribute name=\"text\">1</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"tvcProfile1\">\n                            <property name=\"resizable\">True</property>\n                            <property name=\"sizing\">autosize</property>\n                            <property name=\"title\" translatable=\"yes\">Profile</property>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crProfile1\"/>\n                              <attributes>\n                                <attribute name=\"text\">2</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Select Profile to Import</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkRevealer\" id=\"rvLoading\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"transition_type\">none</property>\n                    <property name=\"reveal_child\">True</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblSearching\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_top\">10</property>\n                        <property name=\"margin_bottom\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Searching for profiles. This may take som time.</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Import Steam Profile</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grVdfImportFinished\">\n                <property name=\"name\">grVdfImportFinished</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblVdfImportFinished\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">55</property>\n                    <property name=\"margin_bottom\">50</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Import results goes here</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblError\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"no_show_all\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Error message:</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swError\">\n                    <property name=\"height_request\">300</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"no_show_all\">True</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTextView\" id=\"tvError\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"editable\">False</property>\n                        <property name=\"buffer\">tbError</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblName\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Enter name for imported profile and click \"Apply\"</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">3</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkEntry\" id=\"txName\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_top\">5</property>\n                    <signal name=\"changed\" handler=\"on_txName_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">4</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblASetsNotice\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"no_show_all\">True</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"label\" translatable=\"yes\">Note: Imported profile contains actions sets.\n\t\tEach set will be converted into separate, hidden profile with following names:</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">5</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblASetList\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"no_show_all\">True</property>\n                    <property name=\"margin_left\">20</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"margin_bottom\">5</property>\n                    <property name=\"label\" translatable=\"yes\">.XYZ.aset_1\n\t\t.XYZ.aset_2\n\t\t.XYZ.aset_3\n\t\t.XYZ.aset_4</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"style\" value=\"italic\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">6</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblPad\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_bottom\">20</property>\n                    <property name=\"hexpand\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkButton\" id=\"btDump\">\n                    <property name=\"label\" translatable=\"yes\">Display Profile Dump</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">True</property>\n                    <property name=\"margin_bottom\">20</property>\n                    <signal name=\"clicked\" handler=\"on_btDump_clicked\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">8</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblPad2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"vexpand\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">7</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Import Steam Profile</property>\n                <property name=\"position\">4</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grShellCommands\">\n                <property name=\"name\">grImportShellCommands</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblShellWarning\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">20</property>\n                    <property name=\"margin_bottom\">20</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">&lt;b&gt;Warning&lt;/b&gt;\n\nProfile You are trying to import contains shell actions.\nShell action allows to execute any arbitrary shell command\nand may modify, create or &lt;span color=\"#ff0000\"&gt;&lt;b&gt;delete&lt;/b&gt;&lt;/span&gt; any files, download &lt;span color=\"#ff0000\"&gt;&lt;b&gt;viruses&lt;/b&gt;&lt;/span&gt;\nor even &lt;span color=\"#ff0000\"&gt;&lt;b&gt;destroy&lt;/b&gt;&lt;/span&gt; some hardware.\n\nPlease, read list of commands used by imported profile\nand mark each checkbox to indicate You fully understand\nwhat command does before clicking \"Continue.\"</property>\n                    <property name=\"use_markup\">True</property>\n                    <property name=\"wrap\">True</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">1</property>\n                    <property name=\"top_attach\">0</property>\n                    <property name=\"width\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkImage\" id=\"imgShellWarning\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_right\">20</property>\n                    <property name=\"stock\">gtk-dialog-warning</property>\n                    <property name=\"icon_size\">6</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblNeverShown\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"no_show_all\">True</property>\n                    <property name=\"label\">(be scared. Be very scared ^^)</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swShellCommands\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTreeView\" id=\"tvShellCommands\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"model\">lstShellCommands</property>\n                        <property name=\"headers_visible\">False</property>\n                        <child internal-child=\"selection\">\n                          <object class=\"GtkTreeSelection\" id=\"tsShellCommands\"/>\n                        </child>\n                        <child>\n                          <object class=\"GtkTreeViewColumn\" id=\"clShellCommand\">\n                            <property name=\"title\" translatable=\"yes\">column</property>\n                            <child>\n                              <object class=\"GtkCellRendererToggle\" id=\"crShellCommandChecked\">\n                                <signal name=\"toggled\" handler=\"on_crShellCommandChecked_toggled\" swapped=\"no\"/>\n                              </object>\n                              <attributes>\n                                <attribute name=\"active\">0</attribute>\n                              </attributes>\n                            </child>\n                            <child>\n                              <object class=\"GtkCellRendererText\" id=\"crShellCommandText\"/>\n                              <attributes>\n                                <attribute name=\"text\">1</attribute>\n                              </attributes>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Import</property>\n                <property name=\"position\">5</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grSccImportFinished\">\n                <property name=\"name\">grImportFailed</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblSccImportFinished\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">35</property>\n                    <property name=\"margin_bottom\">30</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Import results goes here</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblName1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Enter name for imported profile and click \"Apply\"</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkEntry\" id=\"txName2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_top\">5</property>\n                    <signal name=\"changed\" handler=\"on_txName2_changed\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbImportPackage\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"no_show_all\">True</property>\n                    <property name=\"orientation\">vertical</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblEmbededInfo\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_top\">20</property>\n                        <property name=\"label\" translatable=\"yes\">There is one or more additional profile file or menu file embeded in profile\nyou are importing. Please, choose if those those additionnal files should be\nimported as well and how they should be named.\n\nPlease, note that if you choose to not import those files at all, some menus\nand profile switches in imported profile may not work as expected.</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkRadioButton\" id=\"cbImportPackageHidden\">\n                        <property name=\"label\" translatable=\"yes\">Import as hidden menus and profiles named \".%s:name\"</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"active\">True</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <signal name=\"toggled\" handler=\"on_txName2_changed\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkRadioButton\" id=\"cbImportPackageVisible\">\n                        <property name=\"label\" translatable=\"yes\">Import normaly, with names formated as \"%s:name\"</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"active\">True</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <property name=\"group\">cbImportPackageHidden</property>\n                        <signal name=\"toggled\" handler=\"on_txName2_changed\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkRadioButton\" id=\"cbImportPackageNone\">\n                        <property name=\"label\" translatable=\"yes\">Don't import any additional profiles or menus</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"active\">True</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <property name=\"group\">cbImportPackageHidden</property>\n                        <signal name=\"toggled\" handler=\"on_txName2_changed\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">3</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkRadioButton\" id=\"cbImportPackageAdvanced\">\n                        <property name=\"label\" translatable=\"yes\">Display list and let me choose name for each imported profile</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">False</property>\n                        <property name=\"active\">True</property>\n                        <property name=\"draw_indicator\">True</property>\n                        <property name=\"group\">cbImportPackageHidden</property>\n                        <signal name=\"toggled\" handler=\"on_cbImportPackageAdvanced_toggled\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">4</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">3</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkRevealer\" id=\"rvImportPackageAdvanced\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">25</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"vexpand\">True</property>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"svImportPackage\">\n                        <property name=\"height_request\">150</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"shadow_type\">in</property>\n                        <child>\n                          <object class=\"GtkTreeView\" id=\"tvImportPackage\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">True</property>\n                            <property name=\"model\">lstImportPackage</property>\n                            <child internal-child=\"selection\">\n                              <object class=\"GtkTreeSelection\" id=\"tsImportPackage\"/>\n                            </child>\n                            <child>\n                              <object class=\"GtkTreeViewColumn\" id=\"tvIPKGEnabled\">\n                                <property name=\"title\" translatable=\"yes\">En.</property>\n                                <child>\n                                  <object class=\"GtkCellRendererToggle\" id=\"crIPKGEnabled\">\n                                    <signal name=\"toggled\" handler=\"on_crIPKGEnabled_toggled\" swapped=\"no\"/>\n                                  </object>\n                                  <attributes>\n                                    <attribute name=\"active\">0</attribute>\n                                  </attributes>\n                                </child>\n                              </object>\n                            </child>\n                            <child>\n                              <object class=\"GtkTreeViewColumn\" id=\"tvIPKGOriginalName\">\n                                <property name=\"title\" translatable=\"yes\">Original name</property>\n                                <child>\n                                  <object class=\"GtkCellRendererText\" id=\"crIPKGOriginalName\"/>\n                                  <attributes>\n                                    <attribute name=\"sensitive\">0</attribute>\n                                    <attribute name=\"text\">1</attribute>\n                                  </attributes>\n                                </child>\n                                <child>\n                                  <object class=\"GtkCellRendererText\" id=\"crIPKGType\"/>\n                                  <attributes>\n                                    <attribute name=\"sensitive\">0</attribute>\n                                    <attribute name=\"text\">3</attribute>\n                                  </attributes>\n                                </child>\n                              </object>\n                            </child>\n                            <child>\n                              <object class=\"GtkTreeViewColumn\" id=\"tvIPKGImportAs\">\n                                <property name=\"title\" translatable=\"yes\">Import As</property>\n                                <child>\n                                  <object class=\"GtkCellRendererText\" id=\"crIPKGImportAs\">\n                                    <property name=\"editable\">True</property>\n                                    <signal name=\"edited\" handler=\"on_crIPKGImportAs_edited\" swapped=\"no\"/>\n                                  </object>\n                                  <attributes>\n                                    <attribute name=\"sensitive\">0</attribute>\n                                    <attribute name=\"text\">2</attribute>\n                                  </attributes>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">4</property>\n                    <property name=\"width\">3</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Import</property>\n                <property name=\"position\">6</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkGrid\" id=\"grImportFailed\">\n                <property name=\"name\">grImportFailed</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">20</property>\n                <property name=\"margin_right\">20</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblImportFinished1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_top\">20</property>\n                    <property name=\"margin_bottom\">20</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Import failed</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblError1\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"no_show_all\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Error message:</property>\n                    <property name=\"xalign\">0</property>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"swError1\">\n                    <property name=\"height_request\">300</property>\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"vexpand\">True</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTextView\" id=\"tvError1\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"editable\">False</property>\n                        <property name=\"buffer\">tbError</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left_attach\">0</property>\n                    <property name=\"top_attach\">2</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"title\" translatable=\"yes\">Import</property>\n                <property name=\"position\">7</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"hbDialog\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Import / Export</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btBack\">\n            <property name=\"label\" translatable=\"yes\">Back</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"no_show_all\">True</property>\n            <signal name=\"clicked\" handler=\"on_btBack_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btSaveAs\">\n            <property name=\"label\">gtk-save-as</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"relief\">half</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btSaveAs_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btNext\">\n            <property name=\"label\" translatable=\"yes\">Next</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"relief\">half</property>\n            <signal name=\"clicked\" handler=\"on_btNext_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/key_grabber.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkWindow\" id=\"KeyGrab\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"on_KeyGrab_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_keyGrab_key_press_event\" swapped=\"no\"/>\n    <signal name=\"key-release-event\" handler=\"on_keyGrab_key_release_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkGrid\" id=\"grid2\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">30</property>\n        <property name=\"margin_right\">30</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <property name=\"row_spacing\">3</property>\n        <property name=\"column_spacing\">3</property>\n        <child>\n          <object class=\"GtkToggleButton\" id=\"tgKEY_LEFTCTRL\">\n            <property name=\"label\" translatable=\"yes\">Ctrl</property>\n            <property name=\"width_request\">90</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkToggleButton\" id=\"tgKEY_LEFTMETA\">\n            <property name=\"label\" translatable=\"yes\">Meta</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkToggleButton\" id=\"tgKEY_LEFTALT\">\n            <property name=\"label\" translatable=\"yes\">Alt</property>\n            <property name=\"width_request\">80</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">2</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label6\">\n            <property name=\"width_request\">100</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"label\" translatable=\"yes\"> </property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">3</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkToggleButton\" id=\"tgKEY_RIGHTALT\">\n            <property name=\"label\" translatable=\"yes\">Alt</property>\n            <property name=\"width_request\">80</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">4</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkToggleButton\" id=\"tgKEY_RIGHTMETA\">\n            <property name=\"label\" translatable=\"yes\">Meta</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">5</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkToggleButton\" id=\"tgKEY_RIGHTCTRL\">\n            <property name=\"label\" translatable=\"yes\">Ctrl</property>\n            <property name=\"width_request\">90</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"left_attach\">6</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"box1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkToggleButton\" id=\"tgKEY_LEFTSHIFT\">\n                <property name=\"label\" translatable=\"yes\">Shift</property>\n                <property name=\"width_request\">140</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"receives_default\">True</property>\n                <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label5\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"box2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"label8\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkToggleButton\" id=\"tgKEY_RIGHTSHIFT\">\n                <property name=\"label\" translatable=\"yes\">Shift</property>\n                <property name=\"width_request\">140</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"receives_default\">True</property>\n                <signal name=\"toggled\" handler=\"on_tgkey_toggled\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">5</property>\n            <property name=\"top_attach\">0</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblKey\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"has_focus\">True</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"label\" translatable=\"yes\">(...)</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">2</property>\n            <property name=\"top_attach\">0</property>\n            <property name=\"width\">3</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"headerbar1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">Press a Key...</property>\n        <property name=\"show_close_button\">True</property>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/macro_editor.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkListStore\" id=\"lstMacroType\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">macro</col>\n        <col id=\"1\" translatable=\"yes\">Run once</col>\n      </row>\n      <row>\n        <col id=\"0\">repeat</col>\n        <col id=\"1\" translatable=\"yes\">Repeat</col>\n      </row>\n      <row>\n        <col id=\"0\">cycle</col>\n        <col id=\"1\" translatable=\"yes\">Cycle</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">600</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"on_Dialog_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkGrid\" id=\"grid1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblName\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">4</property>\n            <property name=\"label\" translatable=\"yes\">Action Name</property>\n            <property name=\"ellipsize\">end</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkEntry\" id=\"entName\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_bottom\">2</property>\n            <property name=\"activates_default\">True</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">4</property>\n            <property name=\"label\" translatable=\"yes\">Macro Type</property>\n            <property name=\"ellipsize\">end</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkComboBox\" id=\"cbMacroType\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"model\">lstMacroType</property>\n            <property name=\"active\">0</property>\n            <signal name=\"changed\" handler=\"on_cbMacroType_changed\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkCellRendererText\" id=\"cellrenderertext1\"/>\n              <attributes>\n                <attribute name=\"text\">1</attribute>\n              </attributes>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grActions\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"row_spacing\">5</property>\n            <property name=\"row_homogeneous\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">4</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"box1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <child>\n              <object class=\"GtkEntry\" id=\"entAction\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"activates_default\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btAddAction\">\n                <property name=\"label\" translatable=\"yes\">Add _Action</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">5</property>\n                <property name=\"use_underline\">True</property>\n                <signal name=\"clicked\" handler=\"on_btAddAction_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"pack_type\">end</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btAddDelay\">\n                <property name=\"label\" translatable=\"yes\">Add Delay</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <signal name=\"clicked\" handler=\"on_btAddDelay_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"pack_type\">end</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">6</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label3\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"vexpand\">True</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">5</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkSeparator\" id=\"separator1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">2</property>\n            <property name=\"margin_bottom\">10</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">3</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">5</property>\n            <property name=\"label\" translatable=\"yes\">(Widget)</property>\n            <property name=\"xalign\">0</property>\n            <property name=\"yalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_bottom\">5</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btCustomActionEditor\">\n            <property name=\"label\" translatable=\"yes\">Custom Editor</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"clicked\" handler=\"on_btCustomActionEditor_clicked\" swapped=\"no\"/>\n          </object>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btOK\">\n            <property name=\"label\">gtk-ok</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btOK_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btClear\">\n            <property name=\"label\">gtk-clear</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btClear_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstPressClickOrHold\">\n    <columns>\n      <!-- column-name value -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">button</col>\n        <col id=\"1\" translatable=\"yes\">Click</col>\n      </row>\n      <row>\n        <col id=\"0\">press</col>\n        <col id=\"1\" translatable=\"yes\">Press</col>\n      </row>\n      <row>\n        <col id=\"0\">release</col>\n        <col id=\"1\" translatable=\"yes\">Release</col>\n      </row>\n    </data>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/menu_editor.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.2 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkListStore\" id=\"lstItems\">\n    <columns>\n      <!-- column-name item -->\n      <column type=\"GObject\"/>\n      <!-- column-name label -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">650</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"delete-event\" handler=\"on_Dialog_delete_event\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkBox\" id=\"iHateUselessIDs\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvInvalidID\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <object class=\"GtkInfoBar\" id=\"ibNope\">\n                <property name=\"visible\">True</property>\n                <property name=\"app_paintable\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">False</property>\n                <property name=\"orientation\">vertical</property>\n                <property name=\"message_type\">error</property>\n                <child internal-child=\"action_area\">\n                  <object class=\"GtkButtonBox\" id=\"kuaaaaaa\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"spacing\">6</property>\n                    <property name=\"layout_style\">end</property>\n                    <child>\n                      <placeholder/>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">False</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child internal-child=\"content_area\">\n                  <object class=\"GtkBox\" id=\"nonimportatboxthatneedsidjustbecauseofubuntu1\">\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"spacing\">16</property>\n                    <child>\n                      <object class=\"GtkImage\" id=\"imgNope\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"stock\">gtk-dialog-error</property>\n                        <property name=\"icon_size\">3</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblNope\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">No!. Bad, bad user!</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">False</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grid1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"margin_bottom\">10</property>\n            <property name=\"column_homogeneous\">True</property>\n            <child>\n              <object class=\"GtkEntry\" id=\"entName\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_bottom\">2</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"activates_default\">True</property>\n                <signal name=\"changed\" handler=\"on_entName_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblName\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_bottom\">4</property>\n                <property name=\"label\" translatable=\"yes\">Menu ID</property>\n                <property name=\"ellipsize\">end</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblName1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"margin_bottom\">4</property>\n                <property name=\"label\" translatable=\"yes\">Type</property>\n                <property name=\"ellipsize\">end</property>\n                <property name=\"xalign\">0</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRadioButton\" id=\"rbGlobal\">\n                <property name=\"label\" translatable=\"yes\">Global</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"active\">True</property>\n                <property name=\"draw_indicator\">True</property>\n                <signal name=\"toggled\" handler=\"on_entName_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkRadioButton\" id=\"rbInProfile\">\n                <property name=\"label\" translatable=\"yes\">Stored in Profile</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"active\">True</property>\n                <property name=\"draw_indicator\">True</property>\n                <property name=\"group\">rbGlobal</property>\n                <signal name=\"toggled\" handler=\"on_entName_changed\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkToolbar\" id=\"tbItems\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"valign\">end</property>\n                <child>\n                  <object class=\"GtkToolButton\" id=\"btEdit\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"sensitive\">False</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"is_important\">True</property>\n                    <property name=\"label\" translatable=\"yes\">_Edit Item</property>\n                    <property name=\"use_underline\">True</property>\n                    <property name=\"stock_id\">gtk-edit</property>\n                    <signal name=\"clicked\" handler=\"btEdit_clicked_cb\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"homogeneous\">True</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkSeparatorToolItem\" id=\"sep17\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"draw\">False</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">True</property>\n                    <property name=\"homogeneous\">True</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkToolButton\" id=\"btRemoveItem\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"sensitive\">False</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">_Remove</property>\n                    <property name=\"use_underline\">True</property>\n                    <property name=\"stock_id\">gtk-remove</property>\n                    <signal name=\"clicked\" handler=\"on_btRemoveItem_clicked\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"homogeneous\">True</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkMenuToolButton\" id=\"btMnuAdd\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">_Add</property>\n                    <property name=\"use_underline\">True</property>\n                    <property name=\"stock_id\">gtk-add</property>\n                    <signal name=\"clicked\" handler=\"on_btAddItem_clicked\" swapped=\"no\"/>\n                    <child type=\"menu\">\n                      <object class=\"GtkMenu\" id=\"mnuAdd\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <child>\n                          <object class=\"GtkMenuItem\" id=\"mnuAddItem\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Add _Action</property>\n                            <property name=\"use_underline\">True</property>\n                            <signal name=\"activate\" handler=\"on_btAddItem_clicked\" swapped=\"no\"/>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkMenuItem\" id=\"mnuAddSeparator\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Add SSeparator</property>\n                            <property name=\"use_underline\">True</property>\n                            <signal name=\"activate\" handler=\"on_mnuAddSeparator_clicked\" swapped=\"no\"/>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkMenuItem\" id=\"mnuAddSubmenu\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Add Su_bmenu</property>\n                            <property name=\"use_underline\">True</property>\n                            <signal name=\"activate\" handler=\"on_mnuAddSubmenu_clicked\" swapped=\"no\"/>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkSeparatorMenuItem\" id=\"mnuSep714\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkMenuItem\" id=\"mnuAddProfList\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Add List of All _Profiles</property>\n                            <property name=\"use_underline\">True</property>\n                            <signal name=\"activate\" handler=\"on_mnuAddProfList_clicked\" swapped=\"no\"/>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkMenuItem\" id=\"mnuAddRecentList\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Add List of _Recent Profiles</property>\n                            <property name=\"use_underline\">True</property>\n                            <signal name=\"activate\" handler=\"on_mnuAddRecentList_clicked\" swapped=\"no\"/>\n                          </object>\n                        </child>\n                        <child>\n                          <object class=\"GtkMenuItem\" id=\"mnuAddGamesList\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Add List of _Games</property>\n                            <property name=\"use_underline\">True</property>\n                            <signal name=\"activate\" handler=\"on_mnuAddGamesList_activate\" swapped=\"no\"/>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"homogeneous\">True</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkFrame\" id=\"frItems\">\n                <property name=\"height_request\">300</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_top\">10</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"vexpand\">True</property>\n                <property name=\"label_xalign\">0</property>\n                <property name=\"shadow_type\">none</property>\n                <child>\n                  <object class=\"GtkAlignment\" id=\"alFuckWhyIHaveToNameAllThisShit\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"vexpand\">False</property>\n                    <property name=\"top_padding\">12</property>\n                    <property name=\"left_padding\">12</property>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"swItems\">\n                        <property name=\"name\">sw</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"vexpand\">True</property>\n                        <property name=\"shadow_type\">in</property>\n                        <child>\n                          <object class=\"GtkTreeView\" id=\"tvItems\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">True</property>\n                            <property name=\"model\">lstItems</property>\n                            <property name=\"headers_visible\">False</property>\n                            <property name=\"reorderable\">True</property>\n                            <property name=\"rubber_banding\">True</property>\n                            <signal name=\"cursor-changed\" handler=\"on_tvItems_cursor_changed\" swapped=\"no\"/>\n                            <signal name=\"row-activated\" handler=\"btEdit_clicked_cb\" swapped=\"no\"/>\n                            <child internal-child=\"selection\">\n                              <object class=\"GtkTreeSelection\" id=\"nonimportatboxthatneedsidjustbecauseofubunt\"/>\n                            </child>\n                            <child>\n                              <object class=\"GtkTreeViewColumn\" id=\"clItemLabel\">\n                                <property name=\"title\" translatable=\"yes\">Item</property>\n                                <child>\n                                  <object class=\"GtkCellRendererText\" id=\"clItemLabelText\"/>\n                                  <attributes>\n                                    <attribute name=\"text\">1</attribute>\n                                  </attributes>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                </child>\n                <child type=\"label\">\n                  <object class=\"GtkLabel\" id=\"lblItems\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Items</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"has_subtitle\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btSave\">\n            <property name=\"label\">gtk-save</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btSave_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n  <object class=\"GtkBox\" id=\"menu_icon\">\n    <property name=\"visible\">True</property>\n    <property name=\"can_focus\">False</property>\n    <child>\n      <object class=\"GtkButton\" id=\"btChangeItemIcon\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <signal name=\"clicked\" handler=\"on_btChangeItemIcon_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkBox\" id=\"vbChangeItemIcon\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblItemIconName\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_left\">10</property>\n                <property name=\"margin_right\">10</property>\n                <property name=\"label\" translatable=\"yes\">&lt;icon name&gt;</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">True</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">0</property>\n      </packing>\n    </child>\n    <child>\n      <object class=\"GtkButton\" id=\"btClearItemIcon\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">True</property>\n        <property name=\"receives_default\">True</property>\n        <signal name=\"clicked\" handler=\"on_btClearItemIcon_clicked\" swapped=\"no\"/>\n        <child>\n          <object class=\"GtkImage\" id=\"imgClearItemIcon\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"stock\">gtk-clear</property>\n          </object>\n        </child>\n      </object>\n      <packing>\n        <property name=\"expand\">False</property>\n        <property name=\"fill\">True</property>\n        <property name=\"position\">1</property>\n      </packing>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/modeshift_editor.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjHoldFeedback\">\n    <property name=\"lower\">15</property>\n    <property name=\"upper\">32767</property>\n    <property name=\"step_increment\">32</property>\n    <property name=\"page_increment\">128</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjSoftLevel\">\n    <property name=\"lower\">0.01</property>\n    <property name=\"upper\">1</property>\n    <property name=\"value\">0.69999999999999996</property>\n    <property name=\"step_increment\">0.01</property>\n    <property name=\"page_increment\">0.01</property>\n  </object>\n  <object class=\"GtkAdjustment\" id=\"adjTime\">\n    <property name=\"lower\">0.01</property>\n    <property name=\"upper\">10</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">0.20000000000000001</property>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstControllerButtons\">\n    <columns>\n      <!-- column-name value -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">600</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"on_Dialog_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkGrid\" id=\"grid1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"label1\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">4</property>\n            <property name=\"label\" translatable=\"yes\">Action Name</property>\n            <property name=\"ellipsize\">end</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkEntry\" id=\"entry1\">\n            <property name=\"can_focus\">True</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_bottom\">2</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"activates_default\">True</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"box1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">5</property>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbButtonChooser\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_right\">5</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"model\">lstControllerButtons</property>\n                <property name=\"active\">0</property>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"cellrenderertext1\"/>\n                  <attributes>\n                    <attribute name=\"text\">1</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btAddAction\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"margin_left\">5</property>\n                <signal name=\"clicked\" handler=\"on_btAddAction_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkImage\" id=\"image2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"stock\">gtk-add</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">4</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkNotebook\" id=\"ntbMore\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"vexpand\">True</property>\n            <signal name=\"switch-page\" handler=\"on_ntbMore_switch_page\" swapped=\"no\"/>\n            <child>\n              <object class=\"GtkBox\" id=\"vbPress\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"orientation\">vertical</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grActions\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">10</property>\n                    <property name=\"margin_right\">10</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"row_spacing\">5</property>\n                    <property name=\"column_spacing\">15</property>\n                    <property name=\"row_homogeneous\">True</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearDefault\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_nomodclear_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image1\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btDefault\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <signal name=\"clicked\" handler=\"on_nomodbt_clicked\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblPressAlone\">\n                        <property name=\"width_request\">150</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">(pressed alone)</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblPad\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"vexpand\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"label89\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">Press</property>\n              </object>\n              <packing>\n                <property name=\"tab_fill\">False</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkBox\" id=\"vbHold\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"orientation\">vertical</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grHold\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">10</property>\n                    <property name=\"margin_right\">10</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"row_spacing\">5</property>\n                    <property name=\"column_spacing\">15</property>\n                    <property name=\"row_homogeneous\">True</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearHold\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_nomodclear_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image3\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btHold\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <signal name=\"clicked\" handler=\"on_nomodbt_clicked\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label4\">\n                        <property name=\"width_request\">150</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">(held alone)</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblPad2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_bottom\">5</property>\n                    <property name=\"vexpand\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbHoldTime\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblHoldTime\">\n                        <property name=\"width_request\">150</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">10</property>\n                        <property name=\"margin_right\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Hold Time</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclHoldTime\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"margin_right\">5</property>\n                        <property name=\"hexpand\">True</property>\n                        <property name=\"adjustment\">adjTime</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkCheckButton\" id=\"cbHoldFeedback\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"receives_default\">False</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_top\">5</property>\n                    <property name=\"draw_indicator\">True</property>\n                    <signal name=\"toggled\" handler=\"on_cbHoldFeedback_toggled\" swapped=\"no\"/>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblHoldFeedback\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">Play haptic feedback effect when Hold action is executed</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">3</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkRevealer\" id=\"rvHoldFeedbackAmplitude\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_bottom\">5</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"vbHoldFeedbackAmplitude\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_left\">25</property>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"lblHoldFeedbackAmplitude\">\n                            <property name=\"width_request\">150</property>\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"margin_left\">10</property>\n                            <property name=\"margin_right\">10</property>\n                            <property name=\"label\" translatable=\"yes\">Effect Amplitude</property>\n                            <property name=\"xalign\">0</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkScale\" id=\"sclHoldFeedback\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">True</property>\n                            <property name=\"margin_right\">10</property>\n                            <property name=\"hexpand\">True</property>\n                            <property name=\"adjustment\">adjHoldFeedback</property>\n                            <property name=\"round_digits\">0</property>\n                            <property name=\"digits\">0</property>\n                            <property name=\"value_pos\">right</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">4</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"lblHold\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">Hold</property>\n              </object>\n              <packing>\n                <property name=\"position\">1</property>\n                <property name=\"tab_fill\">False</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkBox\" id=\"vbDoubleClick\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"margin_bottom\">5</property>\n                <property name=\"orientation\">vertical</property>\n                <child>\n                  <object class=\"GtkGrid\" id=\"grDoubleClick\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">10</property>\n                    <property name=\"margin_right\">10</property>\n                    <property name=\"margin_top\">10</property>\n                    <property name=\"margin_bottom\">5</property>\n                    <property name=\"row_spacing\">5</property>\n                    <property name=\"column_spacing\">15</property>\n                    <property name=\"row_homogeneous\">True</property>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btClearDoubleClick\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <signal name=\"clicked\" handler=\"on_nomodclear_clicked\" swapped=\"no\"/>\n                        <child>\n                          <object class=\"GtkImage\" id=\"image4\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"stock\">gtk-clear</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">2</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"btDoubleClick\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"hexpand\">True</property>\n                        <signal name=\"clicked\" handler=\"on_nomodbt_clicked\" swapped=\"no\"/>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">1</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label5\">\n                        <property name=\"width_request\">150</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"label\" translatable=\"yes\">(double-clicked)</property>\n                        <property name=\"xalign\">0</property>\n                      </object>\n                      <packing>\n                        <property name=\"left_attach\">0</property>\n                        <property name=\"top_attach\">0</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblPad1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_bottom\">10</property>\n                    <property name=\"vexpand\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbDoubleClickTime\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">10</property>\n                    <property name=\"margin_right\">10</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"lblDoubleClickTime\">\n                        <property name=\"width_request\">150</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"margin_right\">10</property>\n                        <property name=\"label\" translatable=\"yes\">Double-click Time</property>\n                        <property name=\"xalign\">0</property>\n                        <attributes>\n                          <attribute name=\"weight\" value=\"bold\"/>\n                        </attributes>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScale\" id=\"sclDoubleClickTime\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"adjustment\">adjTime</property>\n                        <property name=\"round_digits\">1</property>\n                        <property name=\"value_pos\">right</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child type=\"tab\">\n              <object class=\"GtkLabel\" id=\"lblDoubleClick\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"hexpand\">True</property>\n                <property name=\"label\" translatable=\"yes\">Double-click</property>\n              </object>\n              <packing>\n                <property name=\"position\">2</property>\n                <property name=\"tab_fill\">False</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">5</property>\n            <property name=\"label\" translatable=\"yes\">(Widget)</property>\n            <property name=\"xalign\">0</property>\n            <property name=\"yalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_bottom\">5</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkRevealer\" id=\"rvSoftLevel\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <child>\n              <object class=\"GtkBox\" id=\"vbSoftLevel\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"lblSoftLevel\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"label\" translatable=\"yes\">Soft Pull Level</property>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkScale\" id=\"sclSoftLevel\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"margin_left\">5</property>\n                    <property name=\"margin_right\">5</property>\n                    <property name=\"adjustment\">adjSoftLevel</property>\n                    <property name=\"round_digits\">2</property>\n                    <property name=\"value_pos\">right</property>\n                    <signal name=\"format-value\" handler=\"on_sclSoftLevel_format_value\" swapped=\"no\"/>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">True</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">3</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btCustomActionEditor\">\n            <property name=\"label\" translatable=\"yes\">Custom Editor</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"clicked\" handler=\"on_btCustomActionEditor_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">10</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btOK\">\n            <property name=\"label\">gtk-ok</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btOK_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btClear\">\n            <property name=\"label\">gtk-clear</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btClear_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">5</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/osk_binding_editor.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">osk-bind-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"modal\">True</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <property name=\"skip_taskbar_hint\">True</property>\n    <property name=\"skip_pager_hint\">True</property>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkGrid\" id=\"gruid7\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">20</property>\n        <property name=\"margin_right\">20</property>\n        <property name=\"margin_top\">20</property>\n        <property name=\"margin_bottom\">20</property>\n        <property name=\"row_spacing\">2</property>\n        <property name=\"column_spacing\">20</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"grid8\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">A</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"grid9\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">B</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label10\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">X</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label11\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Y</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label12\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"label\" translatable=\"yes\">Back</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label13\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Start</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">6</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label14\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"label\" translatable=\"yes\">Left Trigger</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">7</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label15\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Left Bumper</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">8</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label16\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"label\" translatable=\"yes\">Right Trigger</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">9</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label17\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">Right Bumper</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">10</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btA\">\n            <property name=\"width_request\">300</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btB\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btX\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btY\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">3</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btSTART\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">6</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btBACK\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">4</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btTriggerLEFT\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">7</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btTriggerRIGHT\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">9</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btLB\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">8</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btRB\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">10</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label21\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"label\" translatable=\"yes\">C</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">5</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btC\">\n            <property name=\"visible\">True</property>\n            <property name=\"sensitive\">False</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">5</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"label\" translatable=\"yes\">Stick Movement</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">11</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btSTICK\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"hexpand\">True</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">11</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"label1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"label\" translatable=\"yes\">Stick Press</property>\n            <property name=\"xalign\">0</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">12</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btSTICKPRESS\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"hexpand\">True</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">12</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header3\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"title\">OSD Keyboard Bindings</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <placeholder/>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/ring_editor.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkAdjustment\" id=\"adjRadius\">\n    <property name=\"lower\">0.10000000000000001</property>\n    <property name=\"upper\">0.98999999999999999</property>\n    <property name=\"step_increment\">0.10000000000000001</property>\n    <property name=\"page_increment\">0.20000000000000001</property>\n    <property name=\"page_size\">0.10000000000000001</property>\n    <signal name=\"value-changed\" handler=\"on_adjRadius_value_changed\" swapped=\"no\"/>\n  </object>\n  <object class=\"GtkListStore\" id=\"lstMode\">\n    <columns>\n      <!-- column-name key -->\n      <column type=\"gchararray\"/>\n      <!-- column-name text -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\">two</col>\n        <col id=\"1\" translatable=\"yes\">Two separate rings</col>\n      </row>\n      <row>\n        <col id=\"0\">inner</col>\n        <col id=\"1\" translatable=\"yes\">Pad binding with Inner ring binding</col>\n      </row>\n      <row>\n        <col id=\"0\">outer</col>\n        <col id=\"1\" translatable=\"yes\">Pad binding with Outer ring binding</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"width_request\">600</property>\n    <property name=\"can_focus\">False</property>\n    <property name=\"role\">action-editor</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"destroy\" handler=\"on_Dialog_destroy\" swapped=\"no\"/>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <object class=\"GtkGrid\" id=\"grid1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"margin_left\">10</property>\n        <property name=\"margin_right\">10</property>\n        <property name=\"margin_top\">10</property>\n        <property name=\"margin_bottom\">10</property>\n        <child>\n          <object class=\"GtkLabel\" id=\"label1\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">4</property>\n            <property name=\"label\" translatable=\"yes\">Action Name</property>\n            <property name=\"ellipsize\">end</property>\n            <property name=\"xalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkEntry\" id=\"entry1\">\n            <property name=\"can_focus\">True</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_bottom\">2</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"activates_default\">True</property>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkLabel\" id=\"lblAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_right\">20</property>\n            <property name=\"margin_bottom\">5</property>\n            <property name=\"label\" translatable=\"yes\">(Widget)</property>\n            <property name=\"xalign\">0</property>\n            <property name=\"yalign\">0</property>\n            <attributes>\n              <attribute name=\"weight\" value=\"bold\"/>\n            </attributes>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbAddedWidget\">\n            <property name=\"can_focus\">False</property>\n            <property name=\"no_show_all\">True</property>\n            <property name=\"margin_bottom\">5</property>\n            <property name=\"hexpand\">True</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">1</property>\n            <property name=\"top_attach\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkGrid\" id=\"grActions\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"margin_left\">10</property>\n            <property name=\"margin_right\">10</property>\n            <property name=\"margin_top\">10</property>\n            <property name=\"row_spacing\">4</property>\n            <property name=\"column_spacing\">15</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblRadius\">\n                <property name=\"width_request\">150</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Inner Ring Radius</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkScale\" id=\"sclRadius\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"adjustment\">adjRadius</property>\n                <property name=\"round_digits\">2</property>\n                <property name=\"value_pos\">right</property>\n                <signal name=\"format-value\" handler=\"on_sclRadius_format_value\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btClearRadius\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <signal name=\"clicked\" handler=\"on_btClearRadius_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkImage\" id=\"image3\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"stock\">gtk-clear</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"cbPreview\">\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">False</property>\n                <property name=\"no_show_all\">True</property>\n                <property name=\"margin_top\">20</property>\n                <property name=\"draw_indicator\">True</property>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"label\" translatable=\"yes\">Preview Immediately</property>\n                    <property name=\"ellipsize\">end</property>\n                    <property name=\"xalign\">0</property>\n                    <attributes>\n                      <attribute name=\"weight\" value=\"bold\"/>\n                    </attributes>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">4</property>\n                <property name=\"width\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblInner\">\n                <property name=\"width_request\">150</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Inner Ring</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btInner\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_actionb_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btClearInner\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <signal name=\"clicked\" handler=\"on_clearb_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkImage\" id=\"image2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"stock\">gtk-clear</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btClearOuter\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <signal name=\"clicked\" handler=\"on_clearb_clicked\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkImage\" id=\"image1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"stock\">gtk-clear</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">2</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"btOuter\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"hexpand\">True</property>\n                <signal name=\"clicked\" handler=\"on_actionb_clicked\" swapped=\"no\"/>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblOuter\">\n                <property name=\"width_request\">150</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Outer Ring</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"lblPressAlone3\">\n                <property name=\"width_request\">150</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"label\" translatable=\"yes\">Mode</property>\n                <property name=\"xalign\">0</property>\n              </object>\n              <packing>\n                <property name=\"left_attach\">0</property>\n                <property name=\"top_attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkComboBox\" id=\"cbMode\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"model\">lstMode</property>\n                <property name=\"active\">0</property>\n                <signal name=\"changed\" handler=\"on_cbMode_changed\" swapped=\"no\"/>\n                <child>\n                  <object class=\"GtkCellRendererText\" id=\"crMode\"/>\n                  <attributes>\n                    <attribute name=\"text\">1</attribute>\n                  </attributes>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left_attach\">1</property>\n                <property name=\"top_attach\">0</property>\n                <property name=\"width\">2</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"left_attach\">0</property>\n            <property name=\"top_attach\">2</property>\n            <property name=\"width\">2</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <object class=\"GtkButton\" id=\"btCustomActionEditor\">\n            <property name=\"label\" translatable=\"yes\">Custom Editor</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <signal name=\"clicked\" handler=\"on_btCustomActionEditor_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btOK\">\n            <property name=\"label\">gtk-ok</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"can_default\">True</property>\n            <property name=\"has_default\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btOK_clicked\" swapped=\"no\"/>\n            <style>\n              <class name=\"suggested-action\"/>\n            </style>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButton\" id=\"btClear\">\n            <property name=\"label\">gtk-clear</property>\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"receives_default\">True</property>\n            <property name=\"use_stock\">True</property>\n            <signal name=\"clicked\" handler=\"on_btClear_clicked\" swapped=\"no\"/>\n          </object>\n          <packing>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "glade/simple_chooser.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.20.0 -->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.10\"/>\n  <object class=\"GtkWindow\" id=\"Dialog\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"type_hint\">dialog</property>\n    <signal name=\"key-press-event\" handler=\"on_window_key_press_event\" swapped=\"no\"/>\n    <child>\n      <placeholder/>\n    </child>\n    <child type=\"titlebar\">\n      <object class=\"GtkHeaderBar\" id=\"header\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"show_close_button\">True</property>\n        <child>\n          <placeholder/>\n        </child>\n        <child>\n          <placeholder/>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "images/button-images/groups.json",
    "content": "[\n{\n\t\"key\" : \"sc\",\n\t\"type\" : \"buttons\",\n\t\"buttons\" : [ \"A\", \"B\", \"X\", \"Y\", \"BACK\", \"C\", \"START\",\n\t\t\t\"LB\", \"RB\", \"LT\", \"RT\", \"LG\", \"RG\" ]\n},\n\n{\n\t\"key\" : \"xo\",\n\t\"type\" : \"buttons\",\n\t\"buttons\" : [ \"CROSS\", \"CIRCLE\", \"SQUARE\", \"TRIANGLE\", \"RECTANGLE\", \"C\",\n\t\t\t\"RTRIANGLE\", \"LB\", \"RB\", \"LT\", \"RT\", \"LG\", \"RG\" ]\n},\n\n{\n\t\"key\" : \"1234\",\n\t\"type\" : \"buttons\",\n\t\"buttons\" : [ \"1\", \"2\", \"3\", \"4\", \"BACK\", \"C\", \"START\",\n\t\t\t\"LB\", \"RB\", \"LT\", \"RT\", \"LG\", \"RG\" ]\n},\n\n{\n\t\"key\" : \"snes\",\n\t\"type\" : \"buttons\",\n\t\"buttons\" : [ \"snesB\", \"snesA\", \"snesY\", \"snesX\", \"snesSTART\", \"C\",\n\t\t\t\"snesSTART\", \"LB\", \"RB\", \"LT\", \"RT\", \"LG\", \"RG\" ]\n}\n\n]"
  },
  {
    "path": "images/deck.config.json",
    "content": "{\n\t\"gui\": {\n\t\t\"background\": \"deck\",\n\t\t\"buttons\": [\n\t\t\t\"A\", \"B\", \"X\", \"Y\", \"VIEW\", \"ELIPSE\", \"MENU\", \"LB\", \"RB\", \"LT\", \"RT\",\n\t\t\t\"STICK\", \"TOUCHPAD\", \"TOUCHPAD\", \"RGRIP\", \"LGRIP\", \"DOTS\"\n\t\t]\n\t},\n\t\"buttons\": [\n\t\t\"DOTS\", \"RSTICKTOUCH\", \"LSTICKTOUCH\", \"RGRIP2\", \"LGRIP2\", \"RSTICKPRESS\",\n\t\t\"LSTICKPRESS\", \"RPADTOUCH\", \"LPADTOUCH\", \"RPADPRESS\", \"LPADPRESS\", \"C\",\n\t\t\"RGRIP\", \"LGRIP\", \"START\", \"BACK\", \"A\", \"X\", \"B\", \"Y\", \"LB\", \"RB\"\n\t],\n\t\"axes\": [\n\t\t\"stick_x\", \"stick_y\", \"rstick_x\", \"rstick_y\",\n\t\t\"lpad_x\", \"lpad_x\", \"rpad_y\", \"rpad_y\",\n\t\t\"dpad_x\", \"dpad_y\", \"ltrig\", \"rtrig\"\n\t],\n\t\"gyros\": true\n}\n\n"
  },
  {
    "path": "images/ds4-config.json",
    "content": "{\n\t\"gui\": {\n\t\t\"background\": \"ds4\",\n\t\t\"buttons\": [\n\t\t\t\"CROSS\", \"CIRCLE\", \"SQUARE\", \"TRIANGLE\", \"ds4SHARE\",\n\t\t\t\"C\", \"ds4SHARE\", \"LB\", \"RB\", \"LT\", \"RT\",\n\t\t\t\"STICK\", \"LPAD\", \"RPAD\", \"LG\", \"RG\"\n\t\t]\n\t},\n\t\n\t\"_ \" : \"Buttons defined here are not actually loaded by driver, but gui\",\n\t\"__\" : \"uses list to determine what buttons and axes are available\",\n\t\n\t\"gyros\": true,\n\t\"buttons\": {\n\t\t\"288\": \"Y\",\n\t\t\"289\": \"B\",\n\t\t\"290\": \"A\",\n\t\t\"291\": \"X\",\n\t\t\"294\": \"RB\",\n\t\t\"295\": \"LB\",\n\t\t\"298\": \"C\",\n\t\t\"293\": \"CPAD\",\n\t\t\"296\": \"BACK\",\n\t\t\"297\": \"START\"\n\t}\n}"
  },
  {
    "path": "images/ds5-config.json",
    "content": "{\n\t\"gui\": {\n\t\t\"background\": \"ds5\",\n\t\t\"buttons\": [\n\t\t\t\"CROSS\", \"CIRCLE\", \"SQUARE\", \"TRIANGLE\", \"ds5SELECT\",\n\t\t\t\"C\", \"ds5START\", \"LB\", \"RB\", \"LT\", \"RT\",\n\t\t\t\"STICK\", \"LPAD\", \"RPAD\", \"LG\", \"RG\"\n\t\t]\n\t},\n\t\n\t\"_ \" : \"Buttons defined here are not actually loaded by driver, but gui\",\n\t\"__\" : \"uses list to determine what buttons and axes are available\",\n\t\n\t\"gyros\": true,\n\t\"buttons\": {\n\t\t\"288\": \"Y\",\n\t\t\"289\": \"B\",\n\t\t\"290\": \"A\",\n\t\t\"291\": \"X\",\n\t\t\"294\": \"RB\",\n\t\t\"295\": \"LB\",\n\t\t\"298\": \"C\",\n\t\t\"293\": \"CPAD\",\n\t\t\"296\": \"BACK\",\n\t\t\"297\": \"START\"\n\t}\n}"
  },
  {
    "path": "images/keyboard.svg.json",
    "content": "{\n\t\"colors\" : {\n\t\t\"button1\" : \"162082\",\n\t\t\"button1_border\" : \"262b5e\",\n\t\t\"button2\" : \"162d44\",\n\t\t\"button2_border\" : \"27323e\",\n\t\t\"text\" : \"ffffff\",\n\t\t\"background\" : \"160c00\"\n\t}\n}"
  },
  {
    "path": "images/menu-icons/buttons/LICENCES",
    "content": "All images in this specific directory are licensed under CC0, https://creativecommons.org/publicdomain/zero/1.0/\n"
  },
  {
    "path": "images/menu-icons/driving/LICENCES",
    "content": "Icons provided on http://opengameart.org and http://game-icons.net/ under\nthe Creative Commons 3.0 BY\n\nList of icons and contributors:\n\nsteering-wheel.bw.png\t\t\t- Delapouite, http://delapouite.com\npedal1.bw.png\t\t\t\t\t- Based on image by Looneybits, (CC 0)\npedal2.bw.png\t\t\t\t\t- Based on image by Looneybits, (CC 0)\nautoshift-drive.bw.pn\t\t\t- Based on image by Looneybits, (CC 0)\nautoshift-reverse.bw.png\t\t- Based on image by Looneybits, (CC 0)\ngearbox-N.bw.png\t\t\t\t- Kozec, (CC 0)\ngearbox-1.bw.png\t\t\t\t- Kozec, (CC 0)\ngearbox-2.bw.png\t\t\t\t- Kozec, (CC 0)\ngearbox-3.bw.png\t\t\t\t- Kozec, (CC 0)\ngearbox-4.bw.png\t\t\t\t- Kozec, (CC 0)\ngearbox-5.bw.png\t\t\t\t- Kozec, (CC 0)\ngearbox-6.bw.png\t\t\t\t- Kozec, (CC 0)\ngearbox-R.bw.png\t\t\t\t- Kozec, (CC 0)\n"
  },
  {
    "path": "images/menu-icons/items/LICENCES",
    "content": "Icons provided on http://opengameart.org and http://game-icons.net/ under\nthe Creative Commons 3.0 BY\n\nList of icons and contributors:\n\nmedpack.bw.png\t\t\t- sbed\ncoffee.bw.png\t\t\t- Lorc, http://lorcblog.blogspot.com\npill.bw.png\t\t\t\t- Lorc, http://lorcblog.blogspot.com\nsyringe.bw.png\t\t\t- Lorc, http://lorcblog.blogspot.com\napple.bw.png\t\t\t- Lorc, http://lorcblog.blogspot.com\nflask.bw.png\t\t\t- Lorc, http://lorcblog.blogspot.com\npotion.bw.png\t\t\t- Lorc, http://lorcblog.blogspot.com\nheart-bottle.bw.png\t\t- Lorc, http://lorcblog.blogspot.com\nbottle.bw.png\t\t\t- Delapouite, http://delapouite.com\n"
  },
  {
    "path": "images/menu-icons/media/LICENCES",
    "content": "Icons under the CC0, https://creativecommons.org/publicdomain/zero/1.0/\n\nList of icons and contributors:\n\nmute.bw.png\t\t\t\t\t- Kozec, (CC 0)\nnext.bw.png\t\t\t\t\t- Kozec, (CC 0)\npause.bw.png\t\t\t\t- Kozec, (CC 0)\nplay.bw.png\t\t\t\t\t- Kozec, (CC 0)\nprev.bw.png\t\t\t\t\t- Kozec, (CC 0)\nstop.bw.png\t\t\t\t\t- Kozec, (CC 0)\nvolume-down.bw.png\t\t\t- Kozec, (CC 0)\nvolume-up.bw.png\t\t\t- Kozec, (CC 0)\n"
  },
  {
    "path": "images/menu-icons/system/LICENCES",
    "content": "Icons provided on http://game-icons.net under the Creative Commons 3.0 BY\n\nList of icons and contributors:\n\nkeyboard.bw.png\t\t\t\t- Delapouite, http://delapouite.com\nrefresh.bw.png\t\t\t\t- Delapouite, http://delapouite.com\nexpand.bw.png\t\t\t\t- Delapouite, http://delapouite.com\nmouse.bw.png\t\t\t\t- Delapouite, http://delapouite.com\ncog.bw.png\t\t\t\t\t- Lorc, http://lorcblog.blogspot.com\ncontroller.bw.png\t\t\t- Skoll\nsc-controller.png\t\t\t- Kozec, (CC 0)\nsc-controller.bw.png\t\t- Kozec, (CC 0)\nturn-off.png\t\t\t\t- Kozec, (CC 0)\nturn-off.bw.png\t\t\t\t- Kozec, (CC 0)\nautoswitch.bw.png\t\t\t- Kozec, (CC 0)\nprofiles.bw.png\t\t\t\t- Kozec, (CC 0)\nturn-off.bw.png\t\t\t\t- Kozec, (CC 0)\nwindow.bw.png\t\t\t\t- Kozec, (CC 0)\nwindowlist.bw.png\t\t\t- Kozec, (CC 0)\n"
  },
  {
    "path": "images/menu-icons/weapons/LICENCES",
    "content": "Icons provided on http://game-icons.net under the Creative Commons 3.0 BY\n\nList of icons and contributors:\n\nbow-arrow.bw.png\t\t\t- Delapouite, http://delapouite.com\nbrass-knuckles.bw.png\t\t- Delapouite, http://delapouite.com\ncrowbar.bw.png\t\t\t\t- Delapouite, http://delapouite.com\nmp5.bw.png\t\t\t\t\t- Delapouite, http://delapouite.com\nsawed-off-shotgun.bw.png\t- Delapouite, http://delapouite.com\nswitch-weapon.bw.png\t\t- Delapouite, http://delapouite.com\nuzi.bw.png\t\t\t\t\t- Delapouite, http://delapouite.com\nkatana.bw.png\t\t\t\t- Delapouite, http://delapouite.com\nbaseball-bat.bw.png\t\t\t- Delapouite, http://delapouite.com\npistol-gun.bw.png\t\t\t- John Colburn, http://ninmunanmu.com\nminigun.bw.png\t\t\t\t- Lorc, http://lorcblog.blogspot.com\nmissile-swarm.bw.png\t\t- Lorc, http://lorcblog.blogspot.com\nreticule.bw.png\t\t\t\t- Lorc, http://lorcblog.blogspot.com\ngrenade.bw.png\t\t\t\t- Lorc, http://lorcblog.blogspot.com\nstun-grenade.bw.png\t\t\t- Lorc, http://lorcblog.blogspot.com\ngladius.bw.png\t\t\t\t- Skoll\nsteyr-aug.bw.png\t\t\t- Skoll\n"
  },
  {
    "path": "images/radial-menu.svg.json",
    "content": "{\n\t\"colors\" : {\n\t\t\"text\": \"00ff00\",\n\t\t\"border\" : \"00ff00\",\n\t\t\"background\" : \"000093\",\n\t\t\"menuitem_border\": \"00005a\",\n\t\t\"menuitem_hilight_border\": \"010101\"\n\t}\n}"
  },
  {
    "path": "images/remotepad.json",
    "content": "{\n\t\"gui\": {\n\t\t\"background\": \"remotepad\",\n\t\t\"no_buttons_in_gui\": true,\n\t\t\"buttons\": [\n\t\t\t\"A\", \"B\", \"X\", \"Y\", \"RECTANGLE\",\n\t\t\t\"C\", \"RTRIANGLE\", \"LB\", \"RB\", \"LT\", \"RT\",\n\t\t\t\"STICK\", \"LPAD\", \"RPAD\", \"LG\", \"RG\"\n\t\t]\n\t},\n\t\n\t\"_ \" : \"Buttons defined here are not actually loaded by driver, but gui\",\n\t\"__\" : \"uses list to determine what buttons and axes are available\",\n\t\n\t\"gyros\": true,\n\t\"buttons\": {\n\t\t\"288\": \"Y\",\n\t\t\"289\": \"B\",\n\t\t\"290\": \"A\",\n\t\t\"291\": \"X\",\n\t\t\"294\": \"RB\",\n\t\t\"295\": \"LB\",\n\t\t\"298\": \"C\",\n\t\t\"296\": \"BACK\",\n\t\t\"297\": \"START\"\n\t}\n}"
  },
  {
    "path": "images/sc-config.json",
    "content": "{\n\t\"_ \" : \"Currently, this file is used only when changing controller image\",\n\t\"__\" : \"manually from context menu\",\n\n\t\"gui\": {\n\t\t\"background\": \"sc\",\n\t\t\"buttons\": [\n\t\t\t\"A\", \"B\", \"X\", \"Y\", \"BACK\",\n\t\t\t\"C\", \"START\", \"LB\", \"RB\", \"LT\", \"RT\",\n\t\t\t\"STICK\", \"LPAD\", \"RPAD\", \"LG\", \"RG\"\n\t\t]\n\t},\n\t\n\t\"gyros\": true,\n\t\"buttons\": {\n\t\t\"1\": \"A\",\n\t\t\"2\": \"B\",\n\t\t\"3\": \"X\",\n\t\t\"4\": \"Y\",\n\t\t\"5\": \"RB\",\n\t\t\"6\": \"LB\",\n\t\t\"7\": \"C\",\n\t\t\"9\": \"BACK\",\n\t\t\"10\": \"START\"\n\t}\n}"
  },
  {
    "path": "images/x360-config.json",
    "content": "{\n\t\"_ \" : \"Currently, this file is used only when changing controller image\",\n\t\"__\" : \"manually from context menu\",\n\n\t\"gui\": {\n\t\t\"background\": \"x360\",\n\t\t\"buttons\": [\n\t\t\t\"A\", \"B\", \"X\", \"Y\", \"BACK\",\n\t\t\t\"C\", \"START\", \"LB\", \"RB\", \"LT\", \"RT\",\n\t\t\t\"STICK\", \"LPAD\", \"RPAD\", \"LG\", \"RG\"\n\t\t]\n\t},\n\t\n\t\"gyros\": false,\n\t\"buttons\": {\n\t\t\"1\": \"A\",\n\t\t\"2\": \"B\",\n\t\t\"3\": \"X\",\n\t\t\"4\": \"Y\",\n\t\t\"5\": \"RB\",\n\t\t\"6\": \"LB\",\n\t\t\"7\": \"C\",\n\t\t\"9\": \"BACK\",\n\t\t\"10\": \"START\"\n\t}\n}"
  },
  {
    "path": "osd-styles/Blue.colors.json",
    "content": "{\n\t\"###\" : \"Colors used by OSD\",\n\t\"osd_colors\": {\n\t\t\"background\": \"101010\",\n\t\t\"border\": \"0000DF\",\n\t\t\"text\": \"4060FF\",\n\t\t\"menuitem_border\": \"000040\",\n\t\t\"menuitem_hilight\": \"000050\",\n\t\t\"menuitem_hilight_text\": \"FFFFFF\",\n\t\t\"menuitem_hilight_border\": \"0000FF\",\n\t\t\"menuseparator\": \"505090\"\n\t},\n\t\n\t\"###\" : \"Colors used by on-screen keyboard\",\n\t\"osk_colors\": {\n\t\t\"hilight\" : \"00688D\",\n\t\t\"pressed\" : \"1A9485\",\n\t\t\"button1\" : \"162082\",\n\t\t\"button1_border\" : \"262b5e\",\n\t\t\"button2\" : \"162d44\",\n\t\t\"button2_border\" : \"27323e\",\n\t\t\"text\" : \"ffffff\"\n\t}\n}"
  },
  {
    "path": "osd-styles/Classic.gtkstyle.css",
    "content": "/* Used colors: all */\n\n#osd-message, #osd-message-1, #osd-menu, #osd-menu-inactive, #osd-gesture, #osd-keyboard {\n\tbackground-color: #%(background)s;\n\tborder: 6px #%(border)s double;\n\topacity: 1;\n}\n\n#osd-message-1 {\n\tborder: 3px #%(border)s double;\n}\n\n#osd-message:active {\n\tborder: 6px #%(border+150)s double;\n}\n\n#osd-message-1:active {\n\tborder: 3px #%(border+150)s double;\n}\n\n#osd-menu-inactive {\n\topacity: 0.90;\n}\n\n#osd-area {\n\tbackground-color: #%(border)s;\n}\n\n#osd-label-3, #osd-label-2, #osd-label-1 {\n\tcolor: #%(text)s;\n\tborder: none;\n\tfont-size: xx-large;\n\tmargin: 15px 15px 15px 15px;\n}\n\n#osd-label-2 {\n\tfont-size: large;\n\tmargin: 10px 10px 10px 10px;\n}\n\n#osd-label-1 {\n\tfont-size: medium;\n\tmargin: 5px 5px 5px 5px;\n}\n\n#osd-label-3:active, #osd-label-2:active, #osd-label-1:active {\n\tcolor: #%(text+150)s;\n}\n\n#osd-menu, #osd-gesture {\n\tpadding: 7px 7px 7px 7px;\n}\n\n#osd-keyboard-container {\n\tpadding: 6px 6px 6px 6px;\n}\n\n#osd-menu-item, #osd-menu-item-selected, #osd-menu-dummy,\n#osd-menu-item-big-icon, #osd-menu-item-big-icon-selected,\n#osd-key-buton, #osd-key-buton-hilight, #osd-launcher-item,\n#osd-launcher-item-selected, #osd-hidden-item,\n#osd-hidden-item-selected, #osd-key-buton-selected {\n\tcolor: #%(text)s;\n\tborder-radius: 0;\n\tfont-size: x-large;\n\tbackground-image: none;\n\tbackground-color: #%(background)s;\n\tmargin: 0px 0px 2px 0px;\n}\n\n\n#osd-hidden-item, #osd-hidden-item-selected {\n\tcolor: #%(background)s;\n\tborder-color: #%(background)s;\n}\n\n\n#osd-radial-menu-icon {\n\tcolor: #%(text)s;\n}\n\n#osd-radial-menu-icon-selected {\n\tcolor: #%(menuitem_hilight_text)s;\n}\n\n#osd-menu-item, #osd-menu-item-big-icon,\n#osd-launcher-item, #osd-launcher-item-selected {\n\tborder: 1px #%(menuitem_border)s solid;\n}\n\n#osd-menu-separator {\n\tcolor: #%(menuseparator)s;\n\tfont-size: large;\n\tbackground-image: none;\n\tbackground-color: #%(background)s;\n\tmargin: 5px 0px 0px 0px;\n\tpadding: 0px 0px 0px 0px;\n}\n\n#osd-gesture-separator {\n\tcolor: #%(menuseparator)s;\n\tbackground-color: #%(menuseparator)s;\n\tmargin: 0px 5px 0px 5px;\n}\n\n#osd-menu-item-selected, #osd-menu-item-big-icon-selected,\n#osd-launcher-item-selected {\n\tcolor: #%(menuitem_hilight_text)s;\n\tbackground-color: #%(menuitem_hilight)s;\n\tborder: 1px #%(menuitem_hilight_border)s solid;\n}\n\n#osd-menu-cursor, #osd-keyboard-cursor {\n}\n\n#osd-dialog-buttons {\n\tmargin: 10px 20px 10px 20px;\n}\n\n#osd-dialog-text {\n\tcolor: #%(text)s;\n\tfont-size: large;\n\tmargin: 10px 20px 0px 20px;\n}\n\n#osd-application-list {\n\tmargin: 15px 15px 0px 15px;\n}\n\n#osd-key-buton, #osd-key-buton-selected {\n\tbackground-color: #%(osk_button1)s;\n\tborder: 1px #%(osk_button1_border)s solid;\n\tcolor: #%(osk_text)s;\n}\n\n#osd-key-buton-hilight {\n\tcolor: #%(osk_text)s;\n\tbackground-color: #%(osk_hilight)s;\n}\n\n#osd-key-buton-selected {\n\tbackground-color: #%(osk_pressed)s;\n}\n"
  },
  {
    "path": "osd-styles/Cyan.colors.json",
    "content": "{\n\t\"###\" : \"Colors used by OSD\",\n\t\"osd_colors\": {\n\t\t\"background\": \"101010\",\n\t\t\"border\": \"00DFDF\",\n\t\t\"text\": \"40F0FF\",\n\t\t\"menuitem_border\": \"004040\",\n\t\t\"menuitem_hilight\": \"005050\",\n\t\t\"menuitem_hilight_text\": \"FFFFFF\",\n\t\t\"menuitem_hilight_border\": \"00FFFF\",\n\t\t\"menuseparator\": \"509090\"\n\t},\n\t\n\t\"###\" : \"Colors used by on-screen keyboard\",\n\t\"osk_colors\": {\n\t\t\"hilight\" : \"00688D\",\n\t\t\"pressed\" : \"1A9485\",\n\t\t\"button1\" : \"162082\",\n\t\t\"button1_border\" : \"262b5e\",\n\t\t\"button2\" : \"162d44\",\n\t\t\"button2_border\" : \"27323e\",\n\t\t\"text\" : \"ffffff\"\n\t}\n}"
  },
  {
    "path": "osd-styles/Green.colors.json",
    "content": "{\n\t\"####\" : \"This is default color scheme\",\n\t\n\t\"###\" : \"Colors used by OSD\",\n\t\"osd_colors\": {\n\t\t\"background\": \"101010\",\n\t\t\"border\": \"00FF00\",\n\t\t\"text\": \"16BF24\",\n\t\t\"menuitem_border\": \"004000\",\n\t\t\"menuitem_hilight\": \"000070\",\n\t\t\"menuitem_hilight_text\": \"16FF26\",\n\t\t\"menuitem_hilight_border\": \"00FF00\",\n\t\t\"menuseparator\": \"109010\"\n\t},\n\t\n\t\"###\" : \"Colors used by on-screen keyboard\",\n\t\"osk_colors\": {\n\t\t\"hilight\" : \"00688D\",\n\t\t\"pressed\" : \"1A9485\",\n\t\t\"button1\" : \"162082\",\n\t\t\"button1_border\" : \"262b5e\",\n\t\t\"button2\" : \"162d44\",\n\t\t\"button2_border\" : \"27323e\",\n\t\t\"text\" : \"ffffff\"\n\t}\n}"
  },
  {
    "path": "osd-styles/Red.colors.json",
    "content": "{\n\t\"###\" : \"Colors used by OSD\",\n\t\"osd_colors\": {\n\t\t\"background\": \"101010\",\n\t\t\"border\": \"DF0000\",\n\t\t\"text\": \"FF2020\",\n\t\t\"menuitem_border\": \"400000\",\n\t\t\"menuitem_hilight\": \"600000\",\n\t\t\"menuitem_hilight_text\": \"FFFFFF\",\n\t\t\"menuitem_hilight_border\": \"FF0000\",\n\t\t\"menuseparator\": \"905050\"\n\t},\n\t\n\t\"###\" : \"Colors used by on-screen keyboard\",\n\t\"osk_colors\": {\n\t\t\"hilight\" : \"00688D\",\n\t\t\"pressed\" : \"1A9485\",\n\t\t\"button1\" : \"162082\",\n\t\t\"button1_border\" : \"262b5e\",\n\t\t\"button2\" : \"162d44\",\n\t\t\"button2_border\" : \"27323e\",\n\t\t\"text\" : \"ffffff\"\n\t}\n}"
  },
  {
    "path": "osd-styles/Reloaded.gtkstyle.css",
    "content": "/* Used colors: background text menuseparator osk_text osk_button1 osk_button1_border osk_hilight osk_pressed */\n\n#osd-message, #osd-message-1, #osd-menu, #osd-menu-inactive, #osd-gesture, #osd-keyboard {\n\tbackground-color: #%(background)s;\n\tborder: 6px #%(background+1)s solid;\n\topacity: 0.95;\n}\n\n#osd-message-1 {\n\tborder: 3px #%(background+1)s solid;\n}\n\n#osd-message:active, #osd-message-1:active {\n\tbackground-color: #%(background+10)s;\n}\n\n#osd-menu-inactive {\n\topacity: 0.50;\n}\n\n#osd-message {\n\tborder-left: 5px #%(menuitem_hilight_text)s solid;\n}\n\n#osd-area {\n\tbackground-color: #%(background+1)s;\n}\n\n#osd-label-3, #osd-label-2, #osd-label-1 {\n\tcolor: #%(text)s;\n\tborder: none;\n\tfont-size: xx-large;\n\tpadding: 15px 15px 15px 15px;\n}\n\n#osd-label-2 {\n\tfont-size: large;\n\tmargin: 5px 5px 5px 5px;\n}\n\n#osd-label-1 {\n\tfont-size: small;\n\tmargin: 0px 0px 0px 0px;\n}\n\n#osd-label-3:active, #osd-label-2:active, #osd-label-1:active {\n\tcolor: #%(text+50)s;\n}\n\n#osd-menu, #osd-gesture {\n\tpadding: 7px 7px 7px 7px;\n}\n\n#osd-keyboard-container {\n\tpadding: 6px 6px 6px 6px;\n}\n\n#osd-menu-item, #osd-menu-item-selected, #osd-menu-dummy,\n#osd-menu-item-big-icon, #osd-menu-item-big-icon-selected,\n#osd-key-buton, #osd-key-buton-hilight, #osd-launcher-item,\n#osd-launcher-item-selected, #osd-hidden-item,\n#osd-hidden-item-selected, #osd-key-buton-selected {\n\tcolor: #%(text)s;\n\tborder-radius: 0;\n\tfont-size: x-large;\n\tbackground-image: none;\n\tbackground-color: #%(background)s;\n\tmargin: 0px 0px 2px 0px;\n}\n\n\n#osd-hidden-item, #osd-hidden-item-selected {\n\tcolor: #%(background)s;\n\tborder-color: #%(background)s;\n}\n\n\n#osd-radial-menu-icon {\n\tcolor: #%(text)s;\n}\n\n#osd-radial-menu-icon-selected {\n\tcolor: #%(menuitem_hilight_text)s;\n}\n\n#osd-menu-item, #osd-menu-item-big-icon,\n#osd-launcher-item, #osd-launcher-item-selected {\n\tborder: 1px #%(background+1)s solid;\n}\n\n#osd-menu-separator {\n\tcolor: #%(background+50)s;\n\tfont-size: large;\n\tbackground-image: none;\n\tbackground-color: #%(background)s;\n\tmargin: 5px 0px 0px 0px;\n\tpadding: 0px 0px 0px 0px;\n}\n\n#osd-gesture-separator {\n\tcolor: #%(menuseparator)s;\n\tbackground-color: #%(menuseparator)s;\n\tmargin: 0px 5px 0px 5px;\n}\n\n#osd-menu-item-selected, #osd-menu-item-big-icon-selected,\n#osd-launcher-item-selected {\n\tcolor: #%(text)s;\n\tbackground-color: #%(background+15)s;\n\tborder: 1px #%(menuitem_hilight_border)s solid;\n}\n\n#osd-menu-cursor, #osd-keyboard-cursor {\n}\n\n#osd-dialog-buttons {\n\tmargin: 10px 20px 10px 20px;\n}\n\n#osd-dialog-text {\n\tcolor: #%(text)s;\n\tfont-size: large;\n\tmargin: 10px 20px 0px 20px;\n}\n\n#osd-application-list {\n\tmargin: 15px 15px 0px 15px;\n}\n\n#osd-key-buton, #osd-key-buton-selected {\n\tbackground-color: #%(osk_button1)s;\n\tborder: 1px #%(osk_button1_border)s solid;\n\tcolor: #%(osk_text)s;\n}\n\n#osd-key-buton-hilight {\n\tcolor: #%(osk_text)s;\n\tbackground-color: #%(osk_hilight)s;\n}\n\n#osd-key-buton-selected {\n\tbackground-color: #%(osk_pressed)s;\n}\n"
  },
  {
    "path": "osd-styles/Yellow.colors.json",
    "content": "{\n\t\"###\" : \"Colors used by OSD\",\n\t\"osd_colors\": {\n\t\t\"background\": \"101010\",\n\t\t\"border\": \"FFFF00\",\n\t\t\"text\": \"BFBF24\",\n\t\t\"menuitem_border\": \"404000\",\n\t\t\"menuitem_hilight\": \"FFFF00\",\n\t\t\"menuitem_hilight_text\": \"000000\",\n\t\t\"menuitem_hilight_border\": \"FFFF00\",\n\t\t\"menuseparator\": \"A5A5A5\"\n\t},\n\t\n\t\"###\" : \"Colors used by on-screen keyboard\",\n\t\"osk_colors\": {\n\t\t\"hilight\" : \"00688D\",\n\t\t\"pressed\" : \"1A9485\",\n\t\t\"button1\" : \"162082\",\n\t\t\"button1_border\" : \"262b5e\",\n\t\t\"button2\" : \"162d44\",\n\t\t\"button2_border\" : \"27323e\",\n\t\t\"text\" : \"ffffff\"\n\t}\n}"
  },
  {
    "path": "profile_examples/DeSmuME.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.BTN_EAST)\", \n            \"name\": \"A\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\", \n            \"name\": \"B\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.BTN_TR)\", \n            \"name\": \"Select\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.KEY_O)\", \n            \"name\": \"Boost\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBL)\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.BTN_LEFT)\", \n            \"name\": \"Tap\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.KEY_O)\", \n            \"name\": \"Boost\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_LEFT)\", \n            \"name\": \"Tap\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_ENTER)\", \n            \"name\": \"Start\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.BTN_TL)\", \n            \"name\": \"X\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.BTN_WEST)\", \n            \"name\": \"Y\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {\n        \"Load-Save\": [{\n            \"action\": \"button(Keys.KEY_LEFTSHIFT) and button(Keys.KEY_F1)\", \n            \"id\": \"item1\", \n            \"name\": \"Save S1\"\n        }, {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\", \n            \"id\": \"item2\", \n            \"name\": \"Save S2\"\n        }, {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT) and button(Keys.KEY_F3)\", \n            \"id\": \"item3\", \n            \"name\": \"Save S3\"\n        }, {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT) and button(Keys.KEY_F4)\", \n            \"id\": \"item4\", \n            \"name\": \"Save S4\"\n        }, {\n            \"action\": \"button(Keys.KEY_F1)\", \n            \"id\": \"item5\", \n            \"name\": \"Load S1\"\n        }, {\n            \"action\": \"button(Keys.KEY_F2)\", \n            \"id\": \"item6\", \n            \"name\": \"Load S2\"\n        }, {\n            \"action\": \"button(Keys.KEY_F3)\", \n            \"id\": \"item7\", \n            \"name\": \"Load S3\"\n        }, {\n            \"action\": \"button(Keys.KEY_F4)\", \n            \"id\": \"item8\", \n            \"name\": \"Load S4\"\n        }]\n    }, \n    \"pad_left\": {\n        \"action\": \"gridmenu('Load-Save')\"\n    }, \n    \"pad_right\": {\n        \"action\": \"relwinarea(0.01, 0.5, 0.99, 0.99)\"\n    }, \n    \"stick\": {\n        \"action\": \"XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, button(Keys.BTN_SELECT))\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, button(Keys.BTN_START))\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/DiRT Rally.sccprofile",
    "content": "{\n    \"_\": [\"For best results Steering Linearity, Steering Deadzone, Throttle Deadzone and\", \"Brake Deadzone should be set to 0 in Advanced Gamepad Settings. Steering Sensitivity,\", \"Steering Saturation, Throttle Saturation and Brake Saturation should be set to 100.\"], \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\", \n            \"name\": \"Gear Up\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.BTN_EAST)\", \n            \"name\": \"Handbreak\"\n        }, \n        \"BACK\": {\n            \"action\": \"resetgyro()\", \n            \"name\": \"Reset Gyro\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.BTN_TL)\", \n            \"name\": \"Clutch\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.BTN_NORTH)\", \n            \"name\": \"Gear Down\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.BTN_EAST)\", \n            \"name\": \"Handbreak\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.BTN_TR)\", \n            \"name\": \"Camera\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\", \n            \"name\": \"Gear Up\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBR)\", \n            \"name\": \"Look Back\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.BTN_START)\", \n            \"name\": \"Pause\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.BTN_NORTH)\", \n            \"name\": \"Gear Down\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.BTN_WEST)\", \n            \"name\": \"Recover Vehicle\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {\n        \"action\": \"feedback(BOTH, gyroabs(None, Axes.ABS_X))\", \n        \"name\": \"Steering\"\n    }, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {}, \n    \"pad_right\": {}, \n    \"stick\": {\n        \"action\": \"dpad(hatup(Axes.ABS_HAT0Y), hatdown(Axes.ABS_HAT0Y), hatleft(Axes.ABS_HAT0X), hatright(Axes.ABS_HAT0X))\", \n        \"name\": \"Menu Navigation\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"axis(Axes.ABS_Z)\", \n        \"name\": \"Break/Reverse\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"axis(Axes.ABS_RZ)\", \n        \"name\": \"Accelerate\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/GZDoom.sccprofile",
    "content": "{\n    \"_\": [\"Please, note that GZDoom doesn't have keys for Jump and Crouch bound\", \"by default, so you may need to do some initial setup in game as well.\"], \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.KEY_ENTER) and button(Keys.KEY_LEFTALT)\", \n            \"name\": \"Jump / Confirm\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL)\", \n            \"name\": \"Crouch\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.KEY_TAB)\", \n            \"name\": \"Map\"\n        }, \n        \"C\": {\n            \"action\": \"menu('Default.menu')\"\n        }, \n        \"LB\": {\n            \"action\": \"menu('Cheats')\", \n            \"name\": \"Cheat Menu\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\", \n            \"name\": \"Run\"\n        }, \n        \"RB\": {\n            \"action\": \"radialmenu('All Weapons',DEFAULT,SAME)\", \n            \"name\": \"Weapon Switch\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_ESC)\", \n            \"name\": \"Menu / Cancel\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.KEY_SPACE)\", \n            \"name\": \"Use\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_R)\", \n            \"name\": \"Reload\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {\n        \"4 Weapons\": [{\n            \"action\": \"button(Keys.KEY_2)\", \n            \"id\": \"item1\", \n            \"name\": \"Pistol\"\n        }, {\n            \"action\": \"button(Keys.KEY_3)\", \n            \"id\": \"item2\", \n            \"name\": \"Shotgun\"\n        }, {\n            \"action\": \"button(Keys.KEY_4)\", \n            \"id\": \"item3\", \n            \"name\": \"Chaingun\"\n        }, {\n            \"action\": \"button(Keys.KEY_6)\", \n            \"id\": \"item4\", \n            \"name\": \"Plasma\"\n        }], \n        \"All Weapons\": [{\n            \"action\": \"button(Keys.KEY_1)\", \n            \"id\": \"item1\", \n            \"name\": \"Fists\"\n        }, {\n            \"action\": \"button(Keys.KEY_2)\", \n            \"id\": \"item2\", \n            \"name\": \"Pistol\"\n        }, {\n            \"action\": \"button(Keys.KEY_3)\", \n            \"id\": \"item3\", \n            \"name\": \"Shotgun\"\n        }, {\n            \"action\": \"button(Keys.KEY_4)\", \n            \"id\": \"item4\", \n            \"name\": \"Chaingun\"\n        }, {\n            \"action\": \"button(Keys.KEY_5)\", \n            \"id\": \"item5\", \n            \"name\": \"Rocket\"\n        }, {\n            \"action\": \"button(Keys.KEY_6)\", \n            \"id\": \"item6\", \n            \"name\": \"Plasma\"\n        }, {\n            \"action\": \"button(Keys.KEY_7)\", \n            \"id\": \"item7\", \n            \"name\": \"BFG \"\n        }], \n        \"Cheats\": [{\n            \"action\": \"type('idfa')\", \n            \"id\": \"item1\", \n            \"name\": \"All Weapons & Ammo\"\n        }, {\n            \"action\": \"type('iddt')\", \n            \"id\": \"item2\", \n            \"name\": \"Full Map\"\n        }, {\n            \"action\": \"type('idclip')\", \n            \"id\": \"item3\", \n            \"name\": \"Walk Through Walls\"\n        }, {\n            \"action\": \"type('iddqd')\", \n            \"id\": \"item4\", \n            \"name\": \"God Mode\"\n        }]\n    }, \n    \"pad_left\": {\n        \"action\": \"radialmenu('4 Weapons')\", \n        \"name\": \"Quick Weapon Switch\"\n    }, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, ball(mouse(None, 1.0)))\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_COMMA), button(Keys.KEY_DOT))\", \n        \"name\": \"Movement\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, button(Keys.KEY_Z))\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, button(Keys.BTN_LEFT))\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/Kodi.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.KEY_ENTER)\"\n        }, \n        \"B\": {\n            \"action\": \"hold(mode(RT, button(Keys.KEY_B), None), mode(RT, name('Add bookmark', button(Keys.KEY_B)), button(Keys.KEY_BACKSPACE)))\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTALT) and button(Keys.KEY_LEFT)\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), mode(RT, shell('sc-controller'), menu('Default.menu')))\"\n        }, \n        \"LB\": {\n            \"action\": \"mode(RT, button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_S), button(Keys.KEY_X))\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"mode(RT, button(Keys.KEY_LEFT), button(Keys.KEY_UP))\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.KEY_M)\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.KEY_BACKSLASH)\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"mode(RT, button(Keys.KEY_RIGHT), button(Keys.KEY_DOWN))\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_LEFT)\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTALT) and button(Keys.KEY_RIGHT)\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"keyboard()\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.KEY_SPACE)\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_C)\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"circular(mouse(Rels.REL_WHEEL))\"\n    }, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, ball(mouse(None, 1.0)))\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_RIGHT))\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_LEFT))\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/No Man Sky.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.KEY_SPACE)\", \n            \"name\": \"Jump / Swim Up\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_X)\", \n            \"name\": \"Weapon Mode\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.KEY_TAB)\", \n            \"name\": \"[TAB] Inventory\"\n        }, \n        \"C\": {\n            \"action\": \"menu('Default.menu')\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.BTN_MIDDLE)\", \n            \"name\": \"Grenade\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\", \n            \"name\": \"Sprint\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.KEY_M)\", \n            \"name\": \"Map\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.KEY_Q)\", \n            \"name\": \"Melle\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.KEY_Q)\", \n            \"name\": \"Swim Down\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.KEY_T)\", \n            \"name\": \"Torch\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_ESC)\", \n            \"name\": \"Menu\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"button(Keys.KEY_C)\", \n            \"name\": \"Scan\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.KEY_E)\", \n            \"name\": \"(E) Interact\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_R)\", \n            \"name\": \"Reload\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))\", \n        \"name\": \"Move Map\"\n    }, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, sens(0.5, 0.5, ball(mouse(None, 1.0))))\", \n        \"name\": \"Mouse\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_W), button(Keys.KEY_S), button(Keys.KEY_A), button(Keys.KEY_D))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_RIGHT))\", \n        \"name\": \"(RClick) Zoom\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_LEFT))\", \n        \"name\": \"(LClick) Fire\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/Portal 2 coop.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"mode(RT, button(Keys.KEY_KPMINUS), button(Keys.KEY_ENTER))\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_E)\"\n        }, \n        \"BACK\": {\n            \"action\": \"mode(RT, button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTSHIFT) and button(Keys.KEY_R), button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTALT) and button(Keys.KEY_LEFT))\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.KEY_Q)\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"mode(RT, button(Keys.KEY_LEFT), button(Keys.KEY_LEFTCTRL))\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT) and button(Keys.KEY_TAB)\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.KEY_F)\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"mode(RT, button(Keys.KEY_RIGHT), button(Keys.KEY_SPACE))\"\n        }, \n        \"START\": {\n            \"action\": \"mode(RT, button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_R), button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTALT) and button(Keys.KEY_RIGHT))\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.KEY_TAB)\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_ESC)\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {\n        \"action\": \"mode(RPADTOUCH, mouse(ROLL), None)\"\n    }, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"circular(mouse(Rels.REL_WHEEL))\"\n    }, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, ball(mouse(None, 1.0)))\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_W), button(Keys.KEY_S), button(Keys.KEY_A), button(Keys.KEY_D))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_RIGHT))\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_LEFT))\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/Portal 2.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.KEY_SPACE)\", \n            \"name\": \"Jump / Confirm\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL)\", \n            \"name\": \"Crouch\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.KEY_ESC)\", \n            \"name\": \"Menu / Cancel\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"LB\": {\n            \"action\": \"menu('Save-Load')\", \n            \"name\": \"Quick Save / Quick Load\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.KEY_G)\", \n            \"name\": \"Gestures\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.BTN_MIDDLE)\", \n            \"name\": \"Toggle Zoom\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.BTN_MIDDLE)\", \n            \"name\": \"Zoom\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.KEY_F)\", \n            \"name\": \"Comm\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBR)\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_ESC)\", \n            \"name\": \"Menu / Cancel\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.KEY_E)\", \n            \"name\": \"Use\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_TAB)\", \n            \"name\": \"Partner View\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {\n        \"Save-Load\": [{\n            \"action\": \"button(Keys.KEY_F6)\", \n            \"id\": \"item1\", \n            \"name\": \"Quick Save\"\n        }, {\n            \"action\": \"button(Keys.KEY_F7)\", \n            \"id\": \"item2\", \n            \"name\": \"Quick Load\"\n        }]\n    }, \n    \"pad_left\": {\n        \"action\": \"feedback(LEFT, 4096, 16, ball(XY(mouse(Rels.REL_HWHEEL), mouse(Rels.REL_WHEEL))))\"\n    }, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, ball(mouse()))\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_UP) and button(Keys.KEY_W), button(Keys.KEY_DOWN) and button(Keys.KEY_S), button(Keys.KEY_LEFT) and button(Keys.KEY_A), button(Keys.KEY_RIGHT) and button(Keys.KEY_D))\", \n        \"name\": \"Move\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, button(Keys.BTN_LEFT))\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, button(Keys.BTN_RIGHT))\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/README.md",
    "content": "Drag and drop link on main window to import profile."
  },
  {
    "path": "profile_examples/Stardew Valley.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\", \n            \"name\": \"OK\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.BTN_EAST)\", \n            \"name\": \"Menu / Cancel\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.BTN_SELECT)\", \n            \"name\": \"Journal\"\n        }, \n        \"C\": {\n            \"action\": \"menu('Default.menu')\"\n        }, \n        \"LB\": {\n            \"action\": \"axis(Axes.ABS_Z)\", \n            \"name\": \"Prev. Item\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\", \n            \"name\": \"Walk\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBL)\"\n        }, \n        \"RB\": {\n            \"action\": \"axis(Axes.ABS_RZ)\", \n            \"name\": \"Next Item\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.BTN_NORTH)\", \n            \"name\": \"Use\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBR)\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_M)\", \n            \"name\": \"Map\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"button(Keys.BTN_THUMBL)\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.BTN_NORTH)\", \n            \"name\": \"Use\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.BTN_WEST)\", \n            \"name\": \"Craft\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {\n        \"Inventory\": [{\n            \"action\": \"button(Keys.KEY_1)\", \n            \"id\": \"item1\", \n            \"name\": \"1\"\n        }, {\n            \"action\": \"button(Keys.KEY_2)\", \n            \"id\": \"item2\", \n            \"name\": \"2\"\n        }, {\n            \"action\": \"button(Keys.KEY_3)\", \n            \"id\": \"item3\", \n            \"name\": \"3\"\n        }, {\n            \"action\": \"button(Keys.KEY_4)\", \n            \"id\": \"item4\", \n            \"name\": \"4\"\n        }, {\n            \"action\": \"button(Keys.KEY_5)\", \n            \"id\": \"item5\", \n            \"name\": \"5\"\n        }, {\n            \"action\": \"button(Keys.KEY_6)\", \n            \"id\": \"item6\", \n            \"name\": \"6\"\n        }]\n    }, \n    \"pad_left\": {\n        \"action\": \"radialmenu('Inventory')\", \n        \"name\": \"Inventory\"\n    }, \n    \"pad_right\": {\n        \"action\": \"mode(LGRIP, feedback(RIGHT, ball(XY(mouse(Rels.REL_HWHEEL), mouse(Rels.REL_WHEEL)))), feedback(RIGHT, 256, ball(mouse())))\"\n    }, \n    \"stick\": {\n        \"action\": \"XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y))\", \n        \"name\": \"Move\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(254, 255, button(Keys.BTN_RIGHT))\", \n        \"name\": \"RClick\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(254, 255, button(Keys.BTN_LEFT))\", \n        \"name\": \"Click\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/Tomb Raider 2013.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"mode(LGRIP, name('Mash left/right', repeat(button(Keys.KEY_LEFT); button(Keys.KEY_RIGHT))), name('Jump / Confirm', button(Keys.KEY_SPACE) and button(Keys.KEY_ENTER)))\", \n            \"name\": \"Jump / Confirm\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\", \n            \"name\": \"Dodge\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.KEY_TAB)\", \n            \"name\": \"Map\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), button(Keys.KEY_LEFTALT))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.KEY_Q)\", \n            \"name\": \"Survival Instincts\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.KEY_R)\", \n            \"name\": \"Reload\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.BTN_MIDDLE)\", \n            \"name\": \"Alt Fire\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\", \n            \"name\": \"Dodge\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.KEY_Z)\", \n            \"name\": \"Zoom\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_ESC)\", \n            \"name\": \"Menu / Cancel\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL)\", \n            \"name\": \"Walk\"\n        }, \n        \"X\": {\n            \"action\": \"mode(LGRIP, name('Interact (rapid)', repeat(button(Keys.KEY_E))), name('Interact', button(Keys.KEY_E)))\", \n            \"name\": \"Interact\"\n        }, \n        \"Y\": {\n            \"action\": \"mode(LGRIP, name('Melee (rapid)', repeat(button(Keys.KEY_F))), name('Melee', button(Keys.KEY_F)))\", \n            \"name\": \"Melee\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"dpad(button(Keys.KEY_1), button(Keys.KEY_2), button(Keys.KEY_3), button(Keys.KEY_4))\", \n        \"name\": \"Weapon Switch\"\n    }, \n    \"pad_right\": {\n        \"action\": \"ball(mouse(None, 1.0))\", \n        \"name\": \"Camera\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))\", \n        \"name\": \"Movement\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, button(Keys.BTN_RIGHT))\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, button(Keys.BTN_LEFT))\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/Undertale.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.KEY_ENTER)\", \n            \"name\": \"Confirm\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_LEFTSHIFT)\", \n            \"name\": \"Cancel\"\n        }, \n        \"BACK\": {\n            \"action\": \"button(Keys.KEY_ESC)\", \n            \"name\": \"Quit\"\n        }, \n        \"C\": {\n            \"action\": \"menu('Default.menu')\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.KEY_F4)\", \n            \"name\": \"Fullscreen\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL)\", \n            \"name\": \"In-Game Menu\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {}, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, ball(mouse(None, 1.0)))\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))\", \n        \"name\": \"Movement\"\n    }, \n    \"trigger_left\": {}, \n    \"trigger_right\": {}, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/Vanguard Princess.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\", \n            \"name\": \"Weak Attack\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.BTN_EAST)\", \n            \"name\": \"Normal Attack\"\n        }, \n        \"C\": {\n            \"action\": \"hold(position(10, 10, menu('Default.menu')), position(20, 20, menu('VP')))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.BTN_TL)\", \n            \"name\": \"Parry\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"button(Keys.BTN_GAMEPAD)\"\n        }, \n        \"LPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBL)\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.BTN_TR)\", \n            \"name\": \"2nd player\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"button(Keys.BTN_NORTH)\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_THUMBR)\"\n        }, \n        \"START\": {\n            \"action\": \"button(Keys.BTN_START)\", \n            \"name\": \"Pause\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"button(Keys.BTN_THUMBL)\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.BTN_NORTH)\", \n            \"name\": \"Strong Attack\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.BTN_WEST)\", \n            \"name\": \"Friend Assist\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {}, \n    \"is_template\": false, \n    \"menus\": {\n        \"VP\": [{\n            \"action\": \"button(Keys.KEY_F4)\", \n            \"id\": \"item1\", \n            \"name\": \"Toggle Fullscreen\"\n        }, {\n            \"action\": \"button(Keys.BTN_START) and button(Keys.BTN_GAMEPAD) and button(Keys.BTN_EAST) and button(Keys.BTN_NORTH) and button(Keys.BTN_WEST)\", \n            \"id\": \"item2\", \n            \"name\": \"Exit Match\"\n        }, {\n            \"separator\": true\n        }, {\n            \"action\": \"button(Keys.KEY_LEFTALT) and button(Keys.KEY_F4)\", \n            \"id\": \"item4\", \n            \"name\": \"Exit Game\"\n        }]\n    }, \n    \"pad_left\": {\n        \"action\": \"dpad(axis(Axes.ABS_Y, 0, -32767), axis(Axes.ABS_Y, 0, 32767), axis(Axes.ABS_X, 0, -32767), axis(Axes.ABS_X, 0, 32767))\", \n        \"name\": \"Move, Jump\"\n    }, \n    \"pad_right\": {}, \n    \"stick\": {\n        \"action\": \"XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y))\", \n        \"name\": \"Move, Jump\"\n    }, \n    \"trigger_left\": {}, \n    \"trigger_right\": {}, \n    \"version\": 1.4\n}"
  },
  {
    "path": "profile_examples/firefox.sccprofile",
    "content": "{\n    \"_\": \"\", \n    \"buttons\": {\n        \"A\": {\n            \"action\": \"button(Keys.KEY_ENTER)\"\n        }, \n        \"B\": {\n            \"action\": \"button(Keys.KEY_BACKSPACE)\"\n        }, \n        \"BACK\": {\n            \"action\": \"mode(RT, button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTSHIFT) and button(Keys.KEY_R), button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTALT) and button(Keys.KEY_LEFT))\"\n        }, \n        \"C\": {\n            \"action\": \"hold(menu('Default.menu'), menu('Default.menu'))\"\n        }, \n        \"LB\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTSHIFT) and button(Keys.KEY_TAB)\"\n        }, \n        \"LGRIP\": {\n            \"action\": \"doubleclick(button(Keys.KEY_END), mode(RT, button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_KPMINUS), button(Keys.KEY_PAGEDOWN)))\"\n        }, \n        \"RB\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_TAB)\"\n        }, \n        \"RGRIP\": {\n            \"action\": \"doubleclick(button(Keys.KEY_HOME), mode(RT, button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_KPPLUS), button(Keys.KEY_PAGEUP)))\"\n        }, \n        \"RPAD\": {\n            \"action\": \"button(Keys.BTN_LEFT)\"\n        }, \n        \"START\": {\n            \"action\": \"mode(RT, button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_R), button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_LEFTALT) and button(Keys.KEY_RIGHT))\"\n        }, \n        \"STICKPRESS\": {\n            \"action\": \"keyboard()\"\n        }, \n        \"X\": {\n            \"action\": \"button(Keys.KEY_SPACE)\"\n        }, \n        \"Y\": {\n            \"action\": \"button(Keys.KEY_LEFTCTRL) and button(Keys.KEY_W)\"\n        }\n    }, \n    \"cpad\": {}, \n    \"gyro\": {\n        \"action\": \"mode(RPADTOUCH, mouse(ROLL), None)\"\n    }, \n    \"is_template\": false, \n    \"menus\": {}, \n    \"pad_left\": {\n        \"action\": \"circular(mouse(Rels.REL_WHEEL))\"\n    }, \n    \"pad_right\": {\n        \"action\": \"feedback(RIGHT, 256, ball(mouse(None, 1.0)))\"\n    }, \n    \"stick\": {\n        \"action\": \"dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))\"\n    }, \n    \"trigger_left\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_RIGHT))\"\n    }, \n    \"trigger_right\": {\n        \"action\": \"trigger(50, 50, button(Keys.BTN_LEFT))\"\n    }, \n    \"version\": 1.4\n}"
  },
  {
    "path": "run.sh",
    "content": "#!/bin/bash\nC_MODULES=(uinput hiddrv sc_by_bt remotepad cemuhook)\nC_VERSION_uinput=9\nC_VERSION_hiddrv=5\nC_VERSION_sc_by_bt=3\nC_VERSION_remotepad=1\nC_VERSION_cemuhook=1\n\nfunction rebuild_c_modules() {\n\techo \"lib$1.so is outdated or missing, building one\"\n\techo \"Please wait, this should be done only once.\"\n\techo \"\"\n\t\n\t# Next line generates string like 'lib.linux-x86_64-2.7', directory where libuinput.so was just generated\n\tLIB=$( python3 -c 'import platform ; print(\"lib.linux-%s-%s.%s\" % ((platform.machine(),) + platform.python_version_tuple()[0:2]))' )\n\tEXT_SUFFIX=$( python3 -c 'import sysconfig ; print(sysconfig.get_config_var(\"EXT_SUFFIX\"))' )\n\t\n\tfor cmod in ${C_MODULES[@]}; do\n\t\tif [ -e build/$LIB/lib${cmod}${EXT_SUFFIX} ] ; then\n\t\t\trm build/$LIB/lib${cmod}${EXT_SUFFIX} || exit 1\n\t\tfi\n\tdone\n\t\n\tpython3 setup.py build || exit 1\n\techo \"\"\n\t\n\tfor cmod in ${C_MODULES[@]}; do\n\t\tif [ ! -e lib${cmod}.so ] ; then\n\t\t\tln -s build/$LIB/lib${cmod}${EXT_SUFFIX} ./lib${cmod}.so || exit 1\n\t\t\techo Symlinked ./lib${cmod}${EXT_SUFFIX} '->' build/$LIB/lib${cmod}.so\n\t\tfi\n\tdone\n\techo \"\"\n}\n\n\n# Ensure correct cwd\ncd \"$(dirname \"$0\")\"\n\n# Check if c modules are compiled and actual\nfor cmod in ${C_MODULES[@]}; do\n\teval expected_version=\\$C_VERSION_${cmod}\n\treported_version=$(PYTHONPATH=\".\" python3 -c 'import os, ctypes; lib=ctypes.CDLL(\"./'lib${cmod}'.so\"); print(lib.'${cmod}'_module_version())')\n\tif [ x\"$reported_version\" != x\"$expected_version\" ] ; then\n\t\trebuild_c_modules ${cmod}\n\tfi\ndone\n\n# Set PATH\nSCRIPTS=\"$(pwd)/scripts\"\nexport PATH=\"$SCRIPTS\":\"$PATH\"\nexport PYTHONPATH=\".\":\"$PYTHONPATH\"\nexport SCC_SHARED=\"$(pwd)\"\n\n# Execute\npython3 'scripts/sc-controller' $@\n"
  },
  {
    "path": "scc/__init__.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller\nCopyright (C) 2018 Kozec\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License version 2 as published by\nthe Free Software Foundation\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along\nwith this program; if not, write to the Free Software Foundation, Inc.,\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\"\"\"\n\npass\n"
  },
  {
    "path": "scc/actions.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Actions\n\nAction describes what should be done when event from physical controller button,\nstick, pad or trigger is generated - typicaly what emulated button, stick or\ntrigger should be pressed.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.tools import ensure_size, quat2euler, anglediff\nfrom scc.tools import circle_to_square, clamp, nameof\nfrom scc.uinput import Keys, Axes, Rels\nfrom scc.lib import xwrappers as X\nfrom scc.constants import (STICK_PAD_MIN, STICK_PAD_MAX, STICK_PAD_MIN_HALF,\nOUTPUT_360_STICK_MIN, OUTPUT_360_STICK_MAX)\nfrom scc.constants import STICK_PAD_MAX_HALF, TRIGGER_MIN, TRIGGER_HALF\nfrom scc.constants import HIPFIRE_NORMAL, HIPFIRE_SENSIBLE, HIPFIRE_EXCLUSIVE\nfrom scc.constants import LEFT, RIGHT, CPAD, STICK, PITCH, YAW, ROLL\nfrom scc.constants import PARSER_CONSTANTS, ControllerFlags\nfrom scc.constants import FE_STICK, FE_TRIGGER, FE_PAD\nfrom scc.constants import TRIGGER_CLICK, TRIGGER_MAX\nfrom scc.constants import SCButtons, BASE_STICK_MOUSE_SPEED\nfrom scc.aliases import ALL_BUTTONS as GAMEPAD_BUTTONS\nfrom math import copysign, sqrt, sin, cos, atan2, pi as PI\n\nimport sys, time, logging, inspect\nlog = logging.getLogger(\"Actions\")\n\n# Default delay after action, if used in macro. May be overriden using sleep() action.\nDEFAULT_DELAY = 0.01\nMOUSE_BUTTONS = ( Keys.BTN_LEFT, Keys.BTN_MIDDLE, Keys.BTN_RIGHT, Keys.BTN_SIDE, Keys.BTN_EXTRA )\nTRIGGERS = ( Axes.ABS_Z, Axes.ABS_RZ )\n\n\nclass Action(object):\n\t\"\"\"\n\tAction is what actually does something in SC-Controller. User can assotiate\n\tone or more Action to each available button, stick or pad in profile file.\n\t\"\"\"\n\t\n\t# Static dict of all available actions, filled later\n\tALL = {}\t# used by action parser\n\tPKEYS = {}\t# used by profile parser\n\t\n\t# Used everywhere, but mainly in parser, to convert strings\n\t# to Action classes and back\n\tCOMMAND = None\n\t\n\t# Additionaly, action can have aliases that are recognized by parser\n\t# ALIASES = (\"x\", \"y\", \"z\")\n\t\n\t# If action class has static 'decode' method defined, profile parser \n\t# will look for matching key in profile nodes and call this method to\n\t# decode action stored in profile.\n\t# Normaly, key to look for is same as COMMAND, but this can be overriden\n\t# by setting PROFILE_KEYS to tuple. Additionaly, PROFILE_KEY_PRIORITY can\n\t# be used to set which modifier should be parsed first.\n\t# This is used mainly by modifiers\n\t#\n\tPROFILE_KEY_PRIORITY = 0\t# default one. Loewer is parsed first\n\t# PROFILE_KEYS = (\"foo\", \"bar\")\n\t#\n\t# @staticmethod\n\t# def decode(jsondatta, action, parser, profile_version):\n\t# \t...\n\t# \treturn action\n\t\n\t# \"Action Context\" constants\n\tAC_BUTTON\t= 1 << 0\n\tAC_STICK\t= 1 << 2\n\tAC_TRIGGER\t= 1 << 3\n\tAC_GYRO\t\t= 1 << 4\n\tAC_PAD\t\t= 1 << 5\n\tAC_OSD\t\t= 1 << 8\n\tAC_OSK\t\t= 1 << 9\t# On screen keyboard\n\tAC_MENU\t\t= 1 << 10\t# Menu Item\n\tAC_SWITCHER\t= 1 << 11\t# Autoswitcher display\n\t#\t\tbit \t09876543210\n\tAC_ALL\t\t= 0b10111111111\t# ALL means everything but OSK\n\t\n\t\n\t# See get_compatible_modifiers\n\tMOD_CLICK\t\t= 1 << 0\n\tMOD_OSD\t\t\t= 1 << 1\n\tMOD_FEEDBACK\t= 1 << 2\n\tMOD_DEADZONE\t= 1 << 3\n\tMOD_SENSITIVITY\t= 1 << 4\n\tMOD_SENS_Z\t\t= 1 << 5\t# Sensitivity of 3rd axis\n\tMOD_ROTATE\t\t= 1 << 6\n\tMOD_POSITION\t= 1 << 7\n\tMOD_SMOOTH\t\t= 1 << 8\n\tMOD_BALL\t\t= 1 << 9\n\t\n\tdef __init__(self, *parameters):\n\t\tself.parameters = parameters\n\t\tself.name = None\n\t\tself.delay_after = DEFAULT_DELAY\n\t\t# internal, insignificant and never saved value used only by editor.\n\t\t# Has to be set to iterable of callbacks to do something usefull;\n\t\t# Callbacks in lilst are called with cb(app, action) after action is\n\t\t# set while editting the profile.\n\t\tself.on_action_set = None\n\t\n\t\n\t@staticmethod\n\tdef register(action_cls, prefix=None):\n\t\t\"\"\"\n\t\tRegisters action class. Basically, adds it to Action.ALL dict.\n\t\tIf prefix is specified, action is registered as Prefix.COMMAND.\n\t\t\"\"\"\n\t\tdct = Action.ALL\n\t\tif prefix:\n\t\t\tif not prefix in Action.ALL:\n\t\t\t\tAction.ALL[prefix] = {}\n\t\t\tdct = Action.ALL[prefix]\n\t\tif action_cls.COMMAND is not None:\n\t\t\tdct[action_cls.COMMAND] = action_cls\n\t\t\tif hasattr(action_cls, \"ALIASES\"):\n\t\t\t\tfor a in action_cls.ALIASES:\n\t\t\t\t\tdct[a] = action_cls\n\t\t\n\t\tif hasattr(action_cls, \"decode\"):\n\t\t\tkeys = (action_cls.COMMAND,)\n\t\t\tif hasattr(action_cls, \"PROFILE_KEYS\"):\n\t\t\t\tkeys = action_cls.PROFILE_KEYS\n\t\t\tfor k in keys:\n\t\t\t\tAction.PKEYS[k] = action_cls\n\t\n\t\n\t@staticmethod\n\tdef unregister_prefix(prefix):\n\t\t\"\"\"\n\t\tUnregisters prefix (as in Prefix.COMMAND) recognized by parser.\n\t\tReturns True on sucess, False if there is no such prefix registered.\n\t\t\"\"\"\n\t\tif prefix in Action.ALL:\n\t\t\tif type(Action.ALL) == dict:\n\t\t\t\tdel Action.ALL[prefix]\n\t\t\t\treturn True\n\t\treturn False\n\t\n\t\n\t@staticmethod\n\tdef register_all(module, prefix=None):\n\t\t\"\"\" Registers all actions from module \"\"\"\n\t\tfor x in dir(module):\n\t\t\tg = getattr(module, x)\n\t\t\tif hasattr(g, 'COMMAND'):\n\t\t\t\tAction.register(g, prefix=prefix)\n\t\n\t\n\tdef encode(self):\n\t\t\"\"\" Called from json encoder \"\"\"\n\t\trv = { 'action' : self.to_string() }\n\t\tif self.name: rv['name'] = self.name\n\t\treturn rv\n\t\n\t\n\tdef get_child_actions(self):\n\t\t\"\"\"\n\t\tReturns iterable with all direct child actions or emtpty iterable if\n\t\tthere are none. Child action is, for example, any action that DPadAction\n\t\tcan choose from.\n\t\t\n\t\tMay returns NoActions as well.\n\t\t\"\"\"\n\t\treturn []\t# Most will return this\n\t\n\t\n\tdef get_all_actions(self):\n\t\t\"\"\"\n\t\tReturns generator with self, actions from get_child_actions and child\n\t\tactions of every child action, recursively including their children.\n\t\t\"\"\"\n\t\tyield self\n\t\tfor c in self.get_child_actions():\n\t\t\tfor cc in c.get_all_actions():\n\t\t\t\tyield cc\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\t\"\"\"\n\t\tReturns bit combination of MOD_* constants to indicate which modifier\n\t\tcan be used with this action.\n\t\tUsed by GUI.\n\t\t\"\"\"\n\t\treturn 0\n\t\n\t\n\tdef get_previewable(self):\n\t\t\"\"\"\n\t\tReturns True if action can be saved immediately to preview user changes.\n\t\tUsed by editor.\n\t\t\"\"\"\n\t\t# Not for most of actions\n\t\treturn False\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Action '%s', %s>\" % (self.COMMAND, self.parameters)\n\t\n\t__repr__ = __str__\n\t\n\t\n\tdef describe(self, context):\n\t\t\"\"\"\n\t\tReturns string that describes what action does in human-readable form.\n\t\tUsed in GUI.\n\t\t\"\"\"\n\t\tif self.name: return self.name\n\t\treturn str(self)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\t\"\"\" Converts action back to string \"\"\"\n\t\treturn (\" \" * pad) + \"%s(%s)\" % (self.COMMAND, \", \".join([\n\t\t\tx.to_string() if isinstance(x, Action) else str(x)\n\t\t\tfor x in self.parameters\n\t\t]))\n\t\n\t\n\tdef set_name(self, name):\n\t\t\"\"\" Sets display name of action. Returns self. \"\"\"\n\t\tself.name = name\n\t\treturn self\n\t\n\t\n\tdef strip(self):\n\t\t\"\"\"\n\t\tFor modifier, returns first child action that actually\n\t\tdoes something (first non-modifier).\n\t\tFor everything else, returns itself.\n\t\t\n\t\tUsed only to determine effective action type in editor.\n\t\t\"\"\"\n\t\treturn self\n\t\n\t\n\tdef compress(self):\n\t\t\"\"\"\n\t\tFor most of actions, returns itself.\n\t\t\n\t\tFor few special cases, like FeedbackModifier and SensitivityModifier,\n\t\treturns child action.\n\t\t\n\t\tCalled after profile is loaded and feedback/sensitivity settings are\n\t\tapplied, when modifier doesn't do anything anymore.\n\t\t\"\"\"\n\t\treturn self\n\t\n\t\n\tdef button_press(self, mapper):\n\t\t\"\"\"\n\t\tCalled when action is executed by pressing physical gamepad button.\n\t\t'button_release' will be called later.\n\t\t\"\"\"\n\t\tlog.warn(\"Action %s can't handle button press event\", self.__class__.__name__)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\t\"\"\"\n\t\tCalled when action executed by pressing physical gamepad button is\n\t\texpected to stop.\n\t\t\"\"\"\n\t\tlog.warn(\"Action %s can't handle button release event\", self.__class__.__name__)\n\t\n\t\n\tdef axis(self, mapper, position, what):\n\t\t\"\"\"\n\t\tCalled when action is executed by moving physical stickm when\n\t\tstick has different actions for different axes defined.\n\t\t\n\t\t'position' contains current stick position on updated axis.\n\t\t'what' is one of LEFT, RIGHT or STICK (from scc.constants),\n\t\tdescribing what is being updated\n\t\t\"\"\"\n\t\tlog.warn(\"Action %s can't handle axis event\", self.__class__.__name__)\n\t\n\t\n\tdef pad(self, mapper, position, what):\n\t\t\"\"\"\n\t\tCalled when action is executed by touching physical pad,\n\t\twhen pad has different actions for different axes defined.\n\t\t\n\t\t'position' contains current finger position on updated axis.\n\t\t'what' is either LEFT or RIGHT (from scc.constants), describing which pad is updated\n\t\t\n\t\t'pad' calls 'axis' by default\n\t\t\"\"\"\n\t\treturn self.axis(mapper, position, what)\n\t\n\t\n\tdef gyro(self, mapper, pitch, yaw, roll, q1, q2, q3, q4):\n\t\t\"\"\"\n\t\tCalled when action is set by rotating gyroscope.\n\t\t'pitch', 'yaw' and 'roll' represents change in gyroscope rotations.\n\t\t'q1' to 'q4' represents current rotations expressed as quaterion.\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\t\"\"\"\n\t\tCalled when action is executed by moving physical stick or touching\n\t\tphysical pad, when one action is defined for whole pad or stick.\n\t\t\n\t\t'x' and 'y' contains current stick or finger position.\n\t\t'what' is one of LEFT, RIGHT, STICK (from scc.constants), describing what is\n\t\tbeing updated\n\t\t\"\"\"\n\t\tlog.warn(\"Action %s can't handle whole stick event\", self.__class__.__name__)\n\t\n\t\n\tdef whole_blocked(self, mapper, x, y, what):\n\t\t\"\"\"\n\t\tSpecial case called when ClickModifier is used and prevents 'whole'\n\t\tto be called because finger moves over non-pressed pad.\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef add(self, mapper, dx, dy):\n\t\t\"\"\"\n\t\tCalled from BallModifier while virtual \"ball\" is rolling.\n\t\t\n\t\tPassed to 'whole' by default.\n\t\t\"\"\"\n\t\tself.whole(mapper, dx, dy, None)\n\t\n\t\n\tdef change(self, mapper, dx, dy, what):\n\t\t\"\"\"\n\t\tCalled from CircularModifier to indicate incremental (or decremental)\n\t\tchange in value.\n\t\t\n\t\t'what' can be None.\n\t\t\"\"\"\n\t\tlog.warn(\"Action %s can't handle incremental changes\", self.__class__.__name__)\n\t\n\t\n\tdef cancel(self, mapper):\n\t\t\"\"\"\n\t\tCalled when profile is changed to give action chance to cancel\n\t\tlong-running effects it may have created.\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef strip_defaults(self):\n\t\t\"\"\"\n\t\tReturns self.parameters list with all default values stripped from right\n\t\tside.\n\t\tThat means, if last parameter is default, it's removed from list; if\n\t\tbefore-last parameter is default, it's removed as well; et cetera et\n\t\tcetera until first non-default parameter is reached.\n\t\t\n\t\tif as_strings is set to True, all parameters are converted to apropriate\n\t\tstrings (x.name for enums, x.encode('unicode_escape') for strings,\n\t\t\"\"\"\n\t\targspec = inspect.getfullargspec(self.__class__.__init__)\n\t\trequired_count = len(argspec.args) - len(argspec.defaults) - 1\n\t\td = list(argspec.defaults)\n\t\tl = list(self.parameters)\n\t\twhile len(d) and len(l) > required_count and d[-1] == l[-1]:\n\t\t\td, l = d[:-1], l[:-1]\n\t\treturn l\n\t\n\t\n\t@staticmethod\n\tdef encode_parameters(parameters):\n\t\t\"\"\"\n\t\tReturns list with parameters encoded to strings in following way:\n\t\t- x.name for enums\n\t\t- str(x) numbers\n\t\t- '%s' % (x.encode('unicode_escape'),) for strings\n\t\t\"\"\"\n\t\treturn [ Action._encode_parameter(p) for p in parameters ]\n\t\n\t\n\t@staticmethod\n\tdef _encode_parameter(parameter):\n\t\t\"\"\" Encodes one parameter. Used by encode_parameters \"\"\"\n\t\tif parameter in PARSER_CONSTANTS:\n\t\t\treturn parameter\n\t\tif type(parameter) == str:\n\t\t\treturn \"'%s'\" % (str(parameter),)\n\t\treturn nameof(parameter)\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\t\"\"\"\n\t\tCalled when action is executed by pressing (or releasing) physical\n\t\ttrigger.\n\t\t\n\t\t'position' contains current trigger position.\n\t\t'old_position' contains last known trigger position.\n\t\t\"\"\"\n\t\tlog.warn(\"Action %s can't handle trigger event\", self.__class__.__name__)\n\n\nclass RangeOP(object):\n\t\"\"\"\n\tAllows to specify and store axis range and then use it in modeshift\n\tinstead of button.\n\t\"\"\"\n\tOPS = (\"<\", \">\", \"<=\", \">=\")\n\t\n\tdef __init__(self, what, op, value):\n\t\t\"\"\" Raises ValueError if 'what' or 'op' is not supported value \"\"\"\n\t\tself.what = what\n\t\tself.op = op\n\t\tself.value = value\n\t\tself.min = float(TRIGGER_MIN)\n\t\tself.max = float(TRIGGER_MAX)\n\t\t\n\t\tif op == \"<\":\n\t\t\tself.op_method = self.cmp_lt\n\t\telif op == \">\":\n\t\t\tself.op_method = self.cmp_gt\n\t\telif op == \"<=\":\n\t\t\tself.op_method = self.cmp_le\n\t\telif op == \">=\":\n\t\t\tself.op_method = self.cmp_ge\n\t\telif op == \"ABS<\":\n\t\t\tself.op_method = self.cmp_labs\n\t\telif op == \"ABS>\":\n\t\t\tself.op_method = self.cmp_gabs\n\t\telse:\n\t\t\traise ValueError(\"Unknown operator: '%s'\" % (op, ))\n\t\t\n\t\tif what == SCButtons.LT:\n\t\t\t# TODO: Somehow unify names here, LT button is related to ltrig axis and so on\n\t\t\tself.axis_name = \"ltrig\"\n\t\telif what == SCButtons.RT:\n\t\t\tself.axis_name = \"rtrig\"\n\t\telif what == SCButtons.X:\n\t\t\tself.axis_name = \"lpad_x\"\n\t\t\tself.min, self.max = float(STICK_PAD_MIN), float(STICK_PAD_MAX)\n\t\telif what == SCButtons.Y:\n\t\t\tself.axis_name = \"lpad_y\"\n\t\t\tself.min, self.max = float(STICK_PAD_MIN), float(STICK_PAD_MAX)\n\t\telif what == STICK:\n\t\t\t# Most special case of all special cases\n\t\t\tself.axis_name = STICK\n\t\t\top = \"ABS\" + op.replace(\"=\", \"\")\n\t\t\tself.children = RangeOP(SCButtons.X, op, value), RangeOP(SCButtons.Y, op, value)\n\t\t\tself.min, self.max = float(STICK_PAD_MIN), float(STICK_PAD_MAX)\n\t\t\tself.op_method = self.cmp_or\n\t\telse:\n\t\t\traise ValueError(\"'%s' is not trigger nor axis\" % (nameof(what), ))\n\t\n\tdef cmp_or(self, mapper):\n\t\treturn any([ x(mapper) for x in self.children ])\n\t\n\tdef cmp_gt(self, mapper):\n\t\tif mapper.state is None:\n\t\t\treturn False\n\t\tstate = float(getattr(mapper.state, self.axis_name)) / self.max\n\t\treturn state > self.value\n\t\n\tdef cmp_lt(self, mapper):\n\t\tif mapper.state is None:\n\t\t\treturn False\n\t\tstate = float(getattr(mapper.state, self.axis_name)) / self.max\n\t\treturn state < self.value\n\t\n\tdef cmp_ge(self, mapper):\n\t\tif mapper.state is None:\n\t\t\treturn False\n\t\tstate = float(getattr(mapper.state, self.axis_name)) / self.max\n\t\treturn state >= self.value\n\t\n\tdef cmp_le(self, mapper):\n\t\tif mapper.state is None:\n\t\t\treturn False\n\t\tstate = float(getattr(mapper.state, self.axis_name)) / self.max\n\t\treturn state <= self.value\n\t\n\tdef cmp_labs(self, mapper):\n\t\tif mapper.state is None:\n\t\t\treturn False\n\t\tstate = float(getattr(mapper.state, self.axis_name)) / self.max\n\t\treturn abs(state) < self.value\n\t\n\tdef cmp_gabs(self, mapper):\n\t\tif mapper.state is None:\n\t\t\treturn False\n\t\tstate = float(getattr(mapper.state, self.axis_name)) / self.max\n\t\treturn abs(state) > self.value\n\t\n\tdef __call__(self, mapper):\n\t\treturn self.op_method(mapper)\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"%s %s %s\" % (nameof(self.what), self.op, self.value)\n\n\nclass HapticEnabledAction(object):\n\t\"\"\" Action that can generate haptic feedback \"\"\"\n\tdef __init__(self):\n\t\tself.haptic = None\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_FEEDBACK\n\t\n\t\n\tdef set_haptic(self, hd):\n\t\tself.haptic = hd\n\t\n\t\n\tdef get_haptic(self):\n\t\treturn self.haptic\n\n\nclass OSDEnabledAction(object):\n\t\"\"\" Action that displays some sort of OSD when executed \"\"\"\n\tdef __init__(self):\n\t\tself.osd_enabled = False\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_OSD\n\t\n\t\n\tdef enable_osd(self, timeout):\n\t\t# timeout not used by anything so far\n\t\tself.osd_enabled = True\n\n\nclass SpecialAction(object):\n\t\"\"\"\n\tAction that needs to call special_actions_handler (aka sccdaemon instance)\n\tto actually do something.\n\t\"\"\"\n\tSA = \"\"\n\t\n\tdef execute_named(self, name, mapper, *a):\n\t\tsa = mapper.get_special_actions_handler()\n\t\th_name = \"on_sa_%s\" % (name,)\n\t\tif sa is None:\n\t\t\tlog.warning(\"Mapper can't handle special actions (set_special_actions_handler never called)\")\n\t\telif hasattr(sa, h_name):\n\t\t\treturn getattr(sa, h_name)(mapper, self, *a)\n\t\telse:\n\t\t\tlog.warning(\"Mapper can't handle '%s' action\" % (name,))\n\t\n\tdef execute(self, mapper, *a):\n\t\treturn self.execute_named(self.SA, mapper, *a)\n\t\n\t# Prevent warnings when special action is bound to button\n\tdef button_press(self, mapper): pass\n\tdef button_release(self, mapper): pass\n\n\nclass AxisAction(Action):\n\t\"\"\"\n\tAction used to controll one output axis, such as one trigger\n\tor one axis of stick.\n\t\"\"\"\n\tCOMMAND = \"axis\"\n\t\n\tAXIS_NAMES = {\n\t\tAxes.ABS_X : (\"LStick\", \"Left\", \"Right\"),\n\t\tAxes.ABS_Y : (\"LStick\", \"Up\", \"Down\"),\n\t\tAxes.ABS_RX : (\"RStick\", \"Left\", \"Right\"),\n\t\tAxes.ABS_RY : (\"RStick\", \"Up\", \"Down\"),\n\t\tAxes.ABS_HAT0X : (\"DPAD\", \"Left\", \"Right\"),\n\t\tAxes.ABS_HAT0Y : (\"DPAD\", \"Up\", \"Down\"),\n\t\tAxes.ABS_Z  : (\"Left Trigger\", \"Press\", \"Press\"),\n\t\tAxes.ABS_RZ : (\"Right Trigger\", \"Press\", \"Press\"),\n\t\tRels.REL_WHEEL : (\"Mouse Wheel\", \"Up\", \"Down\"),\n\t\tRels.REL_HWHEEL : (\"Horizontal Wheel\", \"Left\", \"Right\"),\n\t}\n\tAXES_PAIRS = [\n\t\t(Axes.ABS_X, Axes.ABS_Y),\n\t\t(Axes.ABS_RX, Axes.ABS_RY),\n\t\t(Axes.ABS_HAT0X, Axes.ABS_HAT0Y)\n\t]\n\tX = [ Axes.ABS_X, Axes.ABS_RX, Axes.ABS_HAT0X ]\n\tZ = [ Axes.ABS_Z, Axes.ABS_RZ ]\n\t\n\t# Storage of positions per axis common for all AxisActions\n\t# This is important for cases when two different bindigs\n\t# are mapped to same axis using change() / CircularModifier\n\t# See https://github.com/kozec/sc-controller/issues/213\n\told_positions = {}\n\t\n\tdef __init__(self, id, min = None, max = None):\n\t\tAction.__init__(self, id, *strip_none(min, max))\n\t\tself.id = id\n\t\tself.speed = 1.0\n\t\tif self.id not in AxisAction.old_positions:\n\t\t\tAxisAction.old_positions[self.id] = 0\n\t\tif self.id in TRIGGERS:\n\t\t\tself.min = TRIGGER_MIN if min is None else min\n\t\t\tself.max = TRIGGER_MAX if max is None else max\n\t\telse:\n\t\t\tself.min = STICK_PAD_MIN if min is None else min\n\t\t\tself.max = STICK_PAD_MAX if max is None else max\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tself.speed = x\n\t\n\t\n\tdef get_speed(self):\n\t\treturn (self.speed,)\n\t\n\t\n\tdef get_previewable(self):\n\t\treturn True\n\t\n\t\n\tdef get_compatible_modifiers(self):\t\n\t\treturn Action.MOD_DEADZONE\n\t\n\t\n\t@staticmethod\n\tdef get_axis_description(id, xy=False):\n\t\t\"\"\"\n\t\tReturns (axis_description, 'Negative', 'Positive'), where all strings\n\t\tare localized and Negative/Positive may be switched over depending on\n\t\taxis.\n\t\t\"\"\"\n\t\tif id in Axes.__members__.values() or id in Rels.__members__.values():\n\t\t\taxis, neg, pos = \"%s %s\" % (id.name, _(\"Axis\")), _(\"Negative\"), _(\"Positive\")\n\t\t\tif id in AxisAction.AXIS_NAMES:\n\t\t\t\taxis, neg, pos = [ _(x) for x in AxisAction.AXIS_NAMES[id] ]\n\t\t\tif xy:\n\t\t\t\tif id.name.endswith(\"X\"): axis = _(\"%s X\") % (axis,)\n\t\t\t\tif id.name.endswith(\"Y\"): axis = _(\"%s Y\") % (axis,)\n\t\t\treturn axis, neg, pos\n\t\telif hasattr(id, \"name\"):\n\t\t\treturn _(\"Axis %s\") % (id.name,), _(\"Negative\"), _(\"Positive\")\n\t\telse:\n\t\t\treturn _(\"Axis %s\") % (id,), _(\"Negative\"), _(\"Positive\")\n\t\n\t\n\tdef get_axis(self):\n\t\treturn self.id\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\taxis, neg, pos = AxisAction.get_axis_description(self.id)\n\t\tif context == Action.AC_BUTTON:\n\t\t\tfor x in self.parameters:\n\t\t\t\tif type(x) in (int, float):\n\t\t\t\t\tif x > 0:\n\t\t\t\t\t\treturn \"%s %s\" % (axis, pos)\n\t\t\t\t\tif x < 0:\n\t\t\t\t\t\treturn \"%s %s\" % (axis, neg)\n\t\tif context in (Action.AC_TRIGGER, Action.AC_STICK, Action.AC_PAD):\n\t\t\tif self.id in AxisAction.Z: # Trigger\n\t\t\t\treturn axis\n\t\t\telse:\n\t\t\t\txy = \"X\" if self.id in AxisAction.X else \"Y\"\n\t\t\t\treturn \"%s %s\" % (axis, xy)\n\t\treturn axis\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tmapper.gamepad.axisEvent(self.id, AxisAction.clamp_axis(self.id, self.max))\n\t\tmapper.syn_list.add(mapper.gamepad)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tmapper.gamepad.axisEvent(self.id, AxisAction.clamp_axis(self.id, self.min))\n\t\tmapper.syn_list.add(mapper.gamepad)\n\t\n\t\n\t@staticmethod\n\tdef clamp_axis(id, value):\n\t\t\"\"\" Returns value clamped between min/max allowed for axis \"\"\"\n\t\tif id in (Axes.ABS_Z, Axes.ABS_RZ):\n\t\t\t# Triggers\n\t\t\treturn int(max(TRIGGER_MIN, min(TRIGGER_MAX, value)))\n\t\tif id in (Axes.ABS_HAT0X, Axes.ABS_HAT0Y):\n\t\t\t# DPAD\n\t\t\treturn int(max(-1, min(1, value)))\n\t\t# Everything else\n\t\treturn int(max(OUTPUT_360_STICK_MIN, min(OUTPUT_360_STICK_MAX, value)))\n\t\n\t\n\tdef axis(self, mapper, position, what):\n\t\tp = float(position * self.speed - STICK_PAD_MIN) / (STICK_PAD_MAX - STICK_PAD_MIN)\n\t\tp = int((p * (self.max - self.min)) + self.min)\n\t\tp = AxisAction.clamp_axis(self.id, p)\n\t\tAxisAction.old_positions[self.id] = p\n\t\tmapper.gamepad.axisEvent(self.id, p)\n\t\tmapper.syn_list.add(mapper.gamepad)\n\t\n\t\n\tdef change(self, mapper, dx, dy, what):\n\t\t\"\"\" Called from CircularModifier \"\"\"\n\t\tp = AxisAction.old_positions[self.id]\n\t\tp = clamp(-STICK_PAD_MAX, p + dx, STICK_PAD_MAX)\n\t\tAxisAction.old_positions[self.id] = p\n\t\tself.axis(mapper, p, None)\n\t\n\t\n\tdef add(self, mapper, dx, dy):\n\t\t\"\"\" Called from BallModifier \"\"\"\n\t\tself.axis(mapper, clamp(STICK_PAD_MIN, dx, STICK_PAD_MAX), None)\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\tp = float(position * self.speed - TRIGGER_MIN) / (TRIGGER_MAX - TRIGGER_MIN)\n\t\tp = int((p * (self.max - self.min)) + self.min)\n\t\tp = AxisAction.clamp_axis(self.id, p)\n\t\tAxisAction.old_positions[self.id] = p\n\t\tmapper.gamepad.axisEvent(self.id, p)\n\t\tmapper.syn_list.add(mapper.gamepad)\n\n\nclass RAxisAction(AxisAction):\n\t\"\"\" Reversed AxisAction (outputs reversed values) \"\"\"\n\tCOMMAND = \"raxis\"\n\t\n\tdef __init__(self, id, min = None, max = None):\n\t\tAxisAction.__init__(self, id, min, max)\n\t\tself.min, self.max = self.max, self.min\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\taxis, neg, pos = AxisAction.get_axis_description(self.id)\n\t\tif context in (Action.AC_STICK, Action.AC_PAD):\n\t\t\txy = \"X\" if self.parameters[0] in AxisAction.X else \"Y\"\n\t\t\treturn _(\"%s %s (reversed)\") % (axis, xy)\n\t\treturn _(\"Reverse %s Axis\") % (axis,)\n\n\nclass HatAction(AxisAction):\n\t\"\"\"\n\tWorks as AxisAction, but has values preset to emulate emulate movement in\n\teither only positive or only negative half of range.\n\t\"\"\"\n\tCOMMAND = None\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\taxis, neg, pos = AxisAction.get_axis_description(self.id)\n\t\tif \"up\" in self.COMMAND or \"left\" in self.COMMAND:\n\t\t\treturn \"%s %s\" % (axis, neg)\n\t\telse:\n\t\t\treturn \"%s %s\" % (axis, pos)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"%s(%s)\" % (self.COMMAND, self.id)\n\n\nclass HatUpAction(HatAction):\n\tCOMMAND = \"hatup\"\n\tdef __init__(self, id, *a):\n\t\tHatAction.__init__(self, id, 0, STICK_PAD_MIN + 1)\n\nclass HatDownAction(HatAction):\n\tCOMMAND = \"hatdown\"\n\tdef __init__(self, id, *a):\n\t\tHatAction.__init__(self, id, 0, STICK_PAD_MAX - 1)\n\nclass HatLeftAction(HatAction):\n\tCOMMAND = \"hatleft\"\n\tdef __init__(self, id, *a):\n\t\tHatAction.__init__(self, id, 0, STICK_PAD_MIN + 1)\n\t\nclass HatRightAction(HatAction):\n\tCOMMAND = \"hatright\"\n\tdef __init__(self, id, *a):\n\t\tHatAction.__init__(self, id, 0, STICK_PAD_MAX - 1)\n\n\nclass WholeHapticAction(HapticEnabledAction):\n\t\"\"\"\n\tHelper class for actions that are generating haptic 'rolling clicks' as\n\tfinger moves over pad.\n\tMouseAction, CircularModifier, XYAction and BallModifier currently.\n\t\"\"\"\n\tdef __init__(self):\n\t\tHapticEnabledAction.__init__(self)\n\t\tself.reset_wholehaptic()\n\t\n\t\n\tdef change(self, mapper, dx, dy, what):\n\t\tself._ax += dx\n\t\tself._ay += dy\n\t\t\n\t\tdistance = sqrt(self._ax * self._ax + self._ay * self._ay)\n\t\tif distance > self.haptic.frequency:\n\t\t\tself._ax = self._ay = 0\n\t\t\tmapper.send_feedback(self.haptic)\t\n\t\n\t\n\tdef add(self, mapper, dx, dy):\n\t\tself.change(mapper, dx, dy, None)\n\t\n\t\n\tdef reset_wholehaptic(self):\n\t\tself._ax = self._ay = 0.0\n\n\nclass MouseAction(WholeHapticAction, Action):\n\t\"\"\"\n\tControlls mouse movement in either vertical or horizontal direction\n\tor scroll wheel.\n\t\"\"\"\n\tCOMMAND = \"mouse\"\n\tALIASES = (\"trackpad\", )\n\tHAPTIC_FACTOR = 75.0\t# Just magic number\n\t\n\tdef __init__(self, axis=None, speed=None):\n\t\tAction.__init__(self, *strip_none(axis, speed))\n\t\tWholeHapticAction.__init__(self)\n\t\tself._mouse_axis = axis or None\n\t\tself._old_pos = None\n\t\tif speed:\n\t\t\tself.speed = (speed, speed)\n\t\telse:\n\t\t\tself.speed = (1.0, 1.0)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn ( Action.MOD_SENSITIVITY | Action.MOD_SENS_Z | Action.MOD_ROTATE\n\t\t\t\t| Action.MOD_SMOOTH | Action.MOD_BALL | Action.MOD_FEEDBACK\n\t\t\t\t| Action.MOD_DEADZONE )\n\t\n\t\n\tdef get_previewable(self):\n\t\treturn True\n\t\n\t\n\tdef get_axis(self):\n\t\treturn self._mouse_axis\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tself.speed = (x, y)\n\t\n\t\n\tdef get_speed(self):\n\t\treturn self.speed\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif self._mouse_axis == Rels.REL_WHEEL:\n\t\t\treturn _(\"Wheel\")\n\t\telif self._mouse_axis == Rels.REL_HWHEEL:\n\t\t\treturn _(\"Horizontal Wheel\")\n\t\telif self._mouse_axis in (PITCH, YAW, ROLL, None):\n\t\t\treturn _(\"Mouse\")\n\t\telse:\n\t\t\treturn _(\"Mouse %s\") % (self._mouse_axis.name.split(\"_\", 1)[-1],)\n\t\n\t\n\tdef button_press(self, mapper):\n\t\t# This is generaly bad idea...\n\t\tif self._mouse_axis in (Rels.REL_WHEEL, Rels.REL_HWHEEL):\n\t\t\tself.change(mapper, 100000, 0, None)\n\t\telse:\n\t\t\tself.change(mapper, 100, 0, None)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\t# Nothing\n\t\tpass\n\t\n\t\n\tdef axis(self, mapper, position, what):\n\t\tself.change(mapper, position * MouseAbsAction.MOUSE_FACTOR, 0, what)\n\t\tmapper.force_event.add(FE_STICK)\n\t\n\t\n\tdef pad(self, mapper, position, what):\n\t\tif mapper.is_touched(what):\n\t\t\t# Initial pad touch\n\t\t\tif not mapper.was_touched(what):\n\t\t\t\tmapper.mouse.clearRemainders()\n\n\t\t\tif self._old_pos and mapper.was_touched(what):\n\t\t\t\td = position - self._old_pos[0]\n\t\t\t\tself.change(mapper, d, 0, what)\n\t\t\tself._old_pos = position, 0\n\t\telse:\n\t\t\t# Pad just released\n\t\t\tself._old_pos = None\n\t\n\t\n\tdef change(self, mapper, dx, dy, what):\n\t\tself.add(mapper, dx, dy)\n\t\n\t\n\tdef add(self, mapper, dx, dy):\n\t\t\"\"\" Called from BallModifier \"\"\"\n\t\tif self.haptic:\n\t\t\tWholeHapticAction.change(self, mapper, dx, dy, None)\n\t\t\n\t\tdx, dy = dx * self.speed[0], dy * self.speed[1]\n\t\tif self._mouse_axis is None:\n\t\t\tmapper.mouse.moveEvent(dx, dy, mapper.time_elapsed)\n\t\telif self._mouse_axis == Rels.REL_X:\n\t\t\tmapper.mouse_move(dx, 0)\n\t\telif self._mouse_axis == Rels.REL_Y:\n\t\t\tmapper.mouse_move(0, dx)\n\t\telif self._mouse_axis == Rels.REL_WHEEL:\n\t\t\tmapper.mouse_wheel(0, -dx)\n\t\telif self._mouse_axis == Rels.REL_HWHEEL:\n\t\t\tmapper.mouse_wheel(dx, 0)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\t#if what == STICK:\n\t\t#\tmapper.mouse_move(x * self.speed[0] * 0.01, y * self.speed[1] * 0.01)\n\t\t#\tmapper.force_event.add(FE_STICK)\n\t\tif ((what == STICK) or\n\t\t\t(what == RIGHT and mapper.controller_flags() & ControllerFlags.HAS_RSTICK)):\n\t\t\tratio_x = x / (STICK_PAD_MAX if x > 0 else STICK_PAD_MIN) * copysign(1, x)\n\t\t\tratio_y = y / (STICK_PAD_MAX if y > 0 else STICK_PAD_MIN) * copysign(1, y)\n\t\t\tmouse_dx = ratio_x * (mapper.time_elapsed * BASE_STICK_MOUSE_SPEED) * self.speed[0]\n\t\t\tmouse_dy = ratio_y * (mapper.time_elapsed * BASE_STICK_MOUSE_SPEED) * self.speed[1]\n\t\t\t#mapper.mouse_move_stick(x * self.speed[0] * 0.01, y * self.speed[1] * 0.01)\n\t\t\tmapper.mouse_move_stick(mouse_dx, mouse_dy)\n\t\t\tmapper.force_event.add(FE_PAD)\n\t\telse:\t# left or right pad\n\t\t\tif mapper.is_touched(what):\n\t\t\t\tif self._old_pos and mapper.was_touched(what):\n\t\t\t\t\tdx, dy = x - self._old_pos[0], self._old_pos[1] - y\n\t\t\t\t\tself.change(mapper, dx, dy, what)\n\t\t\t\tself._old_pos = x, y\n\t\t\telse:\n\t\t\t\t# Pad just released\n\t\t\t\tself._old_pos = None\n\t\n\t\n\tdef gyro(self, mapper, pitch, yaw, roll, *a):\n\t\tif self._mouse_axis == YAW:\n\t\t\tmapper.mouse_move(yaw * -self.speed[0], pitch * -self.speed[1])\n\t\telse:\n\t\t\tmapper.mouse_move(roll * -self.speed[0], pitch * -self.speed[1])\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\tdelta = position - old_position\n\t\tself.add(mapper, delta, delta) # add() will figure out the axis from the action parameters\n\nclass MouseAbsAction(Action):\n\t\"\"\"\n\tMaps gyro rotation or position on pad to immediate mouse movement, similary\n\tto how GyroAbsAction maps gyro rotation to gamepad stick.\n\t\n\tControlls mouse movement in either vertical or horizontal direction\n\tor scroll wheel.\n\t\"\"\"\n\tCOMMAND = \"mouseabs\"\n\tMOUSE_FACTOR = 0.005\t# Just random number to put default sensitivity into sane range\n\t\n\tdef __init__(self, axis = None):\n\t\tAction.__init__(self, *strip_none(axis))\n\t\tself._mouse_axis = axis\n\t\tself._old_pos = None\n\t\tself.speed = 1.0, 1.0\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn ( Action.MOD_SENSITIVITY | Action.MOD_SENS_Z | Action.MOD_DEADZONE )\n\t\n\t\n\tdef get_previewable(self):\n\t\treturn True\n\t\n\t\n\tdef get_axis(self):\n\t\treturn self._mouse_axis\n\t\n\t\n\tdef set_speed(self, x, y, *a):\n\t\tself.speed = x, y\n\t\n\t\n\tdef get_speed(self):\n\t\treturn self.speed\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif self._mouse_axis == Rels.REL_WHEEL:\n\t\t\treturn _(\"Wheel\")\n\t\telif self._mouse_axis == Rels.REL_HWHEEL:\n\t\t\treturn _(\"Horizontal Wheel\")\n\t\telif self._mouse_axis in (PITCH, YAW, ROLL, None):\n\t\t\treturn _(\"Mouse\")\n\t\telse:\n\t\t\treturn _(\"Mouse %s\") % (self._mouse_axis.name.split(\"_\", 1)[-1],)\n\t\n\t\n\tdef axis(self, mapper, position, what):\n\t\tmapper.force_event.add(FE_STICK)\n\t\t\n\t\tp = position * self.speed[0] * MouseAbsAction.MOUSE_FACTOR\n\t\tif self._mouse_axis == Rels.REL_X:\n\t\t\tmapper.mouse_move(p, 0)\n\t\telif self._mouse_axis == Rels.REL_Y:\n\t\t\tmapper.mouse_move(0, p)\n\t\telif self._mouse_axis == Rels.REL_WHEEL:\n\t\t\tmapper.mouse_wheel(0, -p)\n\t\telif self._mouse_axis == Rels.REL_HWHEEL:\n\t\t\tmapper.mouse_wheel(p, 0)\n\tpad = axis\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tdx = x * self.speed[0] * MouseAbsAction.MOUSE_FACTOR\n\t\tdy = y * self.speed[0] * MouseAbsAction.MOUSE_FACTOR\n\t\tmapper.mouse.moveEvent(dx, dy, mapper.time_elapsed)\n\n\nclass AreaAction(Action, SpecialAction, OSDEnabledAction):\n\t\"\"\"\n\tTranslates pad position to position in specified area of screen.\n\t\"\"\"\n\tSA = COMMAND = \"area\"\n\t\n\tdef __init__(self, x1, y1, x2, y2):\n\t\tAction.__init__(self, x1, y1, x2, y2)\n\t\tOSDEnabledAction.__init__(self)\n\t\t# Make sure that lower number is first - movement gets inverted otherwise\n\t\tif x2 < x1 : x1, x2 = x2, x1\n\t\tif y2 < y1 : y1, y2 = y2, y1\n\t\t# orig_position will store mouse position to return to when finger leaves pad\n\t\tself.orig_position = None\n\t\tself.coords = x1, y1, x2, y2\n\t\t# needs_query_screen is True if any coordinate has to be computed\n\t\tself.needs_query_screen = x1 < 0 or y1 < 0 or x2 < 0 or y2 < 0\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Mouse Region\")\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn OSDEnabledAction.get_compatible_modifiers(self)\n\t\n\t\n\tdef transform_coords(self, mapper):\n\t\t\"\"\"\n\t\tTransform coordinates specified as action arguments in whatever current\n\t\tclass represents into rectangle in pixels.\n\t\t\n\t\tOverrided by subclasses.\n\t\t\"\"\"\n\t\tif self.needs_query_screen:\n\t\t\tscreen = X.get_screen_size(mapper.get_xdisplay())\n\t\t\tx1, y1, x2, y2 = self.coords\n\t\t\tif x1 < 0 : x1 = screen[0] + x1\n\t\t\tif y1 < 0 : y1 = screen[1] + y1\n\t\t\tif x2 < 0 : x2 = screen[0] + x2\n\t\t\tif y2 < 0 : y2 = screen[1] + y2\n\t\t\treturn x1, y1, x2, y2\n\t\treturn self.coords\n\t\n\t\n\tdef transform_osd_coords(self, mapper):\n\t\t\"\"\"\n\t\tSame as transform_coords, but returns coordinates in screen space even\n\t\tif action sets mouse position relative to window.\n\t\t\n\t\tOverrided by subclasses.\n\t\t\"\"\"\n\t\treturn self.transform_coords(mapper)\n\t\n\t\n\tdef set_mouse(self, mapper, x, y):\n\t\t\"\"\"\n\t\tPerforms final mouse position setting.\n\t\tOverrided by subclasses.\n\t\t\"\"\"\n\t\tX.set_mouse_pos(mapper.get_xdisplay(), x, y)\n\t\n\t\n\tdef update_osd_area(self, area, mapper):\n\t\t\"\"\"\n\t\tUpdates area instance directly instead of calling daemon and letting\n\t\tit talking through socket.\n\t\t\"\"\"\n\t\tx1, y1, x2, y2 = self.transform_osd_coords(mapper)\n\t\tarea.update(int(x1), int(y1), int(x2-x1), int(y2-y1))\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif mapper.get_xdisplay() is None:\n\t\t\tlog.warning(\"XServer is not available, cannot use 'AreaAction\")\n\t\t\treturn\n\t\tif mapper.is_touched(what):\n\t\t\t# Store mouse position if pad was just touched\n\t\t\tif self.orig_position is None:\n\t\t\t\tif self.osd_enabled:\n\t\t\t\t\tx1, y1, x2, y2 = self.transform_osd_coords(mapper)\n\t\t\t\t\tself.execute(mapper, int(x1), int(y1), int(x2), int(y2))\n\t\t\t\tself.orig_position = X.get_mouse_pos(mapper.get_xdisplay())\n\t\t\t# Compute coordinates specified from other side of screen if needed\n\t\t\tx1, y1, x2, y2 = self.transform_coords(mapper)\n\t\t\t# Transform position on circne to position on rectangle\n\t\t\tx = x / float(STICK_PAD_MAX)\n\t\t\ty = y / float(STICK_PAD_MAX)\n\t\t\tx, y = circle_to_square(x, y)\n\t\t\t# Perform magic\n\t\t\tx = max(0, (x + 1.0) * 0.5)\n\t\t\ty = max(0, (1.0 - y) * 0.5)\n\t\t\tw = float(x2 - x1)\n\t\t\th = float(y2 - y1)\n\t\t\tx = int(x1 + w * x)\n\t\t\ty = int(y1 + h * y)\n\t\t\t# Set position\n\t\t\tself.set_mouse(mapper, x, y)\n\t\telif mapper.was_touched(what):\n\t\t\t# Pad just released\n\t\t\tX.set_mouse_pos(mapper.get_xdisplay(), *self.orig_position)\n\t\t\tif self.osd_enabled:\n\t\t\t\tself.execute_named(\"clear_osd\", mapper)\n\t\t\tself.orig_position = None\n\n\nclass RelAreaAction(AreaAction):\n\tCOMMAND = \"relarea\"\n\t\n\tdef transform_coords(self, mapper):\n\t\tscreen = X.get_screen_size(mapper.get_xdisplay())\n\t\tx1, y1, x2, y2 = self.coords\n\t\tx1 = screen[0] * x1\n\t\ty1 = screen[1] * y1\n\t\tx2 = screen[0] * x2\n\t\ty2 = screen[1] * y2\n\t\treturn x1, y1, x2, y2\n\n\nclass WinAreaAction(AreaAction):\n\tCOMMAND = \"winarea\"\n\t\n\tdef transform_coords(self, mapper):\n\t\tif self.needs_query_screen:\n\t\t\tw_size = X.get_window_size(mapper.get_xdisplay(), mapper.get_current_window())\n\t\t\tx1, y1, x2, y2 = self.coords\n\t\t\tif x1 < 0 : x1 = w_size[0] + x1\n\t\t\tif y1 < 0 : y1 = w_size[1] + y1\n\t\t\tif x2 < 0 : x2 = w_size[0] + x2\n\t\t\tif y2 < 0 : y2 = w_size[1] + y2\n\t\t\treturn x1, y1, x2, y2\n\t\treturn self.coords\n\t\n\t\n\tdef transform_osd_coords(self, mapper):\n\t\twx, wy, ww, wh = X.get_window_geometry(mapper.get_xdisplay(), mapper.get_current_window())\n\t\tx1, y1, x2, y2 = self.coords\n\t\tx1 = wx + x1 if x1 >= 0 else wx + ww + x1\n\t\ty1 = wy + y1 if y1 >= 0 else wy + wh + y1\n\t\tx2 = wx + x2 if x2 >= 0 else wx + ww + x2\n\t\ty2 = wy + y2 if y2 >= 0 else wy + wh + y2\n\t\treturn x1, y1, x2, y2\n\t\n\t\n\tdef set_mouse(self, mapper, x, y):\n\t\tX.set_mouse_pos(mapper.get_xdisplay(), x, y, mapper.get_current_window())\n\n\nclass RelWinAreaAction(WinAreaAction):\n\tCOMMAND = \"relwinarea\"\n\t\n\tdef transform_coords(self, mapper):\n\t\tw_size = X.get_window_size(mapper.get_xdisplay(), mapper.get_current_window())\n\t\tx1, y1, x2, y2 = self.coords\n\t\tx1 = w_size[0] * x1\n\t\ty1 = w_size[1] * y1\n\t\tx2 = w_size[0] * x2\n\t\ty2 = w_size[1] * y2\n\t\treturn x1, y1, x2, y2\n\t\n\t\n\tdef transform_osd_coords(self, mapper):\n\t\twx, wy, ww, wh = X.get_window_geometry(mapper.get_xdisplay(), mapper.get_current_window())\n\t\tx1, y1, x2, y2 = self.coords\n\t\tx1 = wx + float(ww) * x1\n\t\ty1 = wy + float(wh) * y1\n\t\tx2 = wx + float(ww) * x2\n\t\ty2 = wy + float(wh) * y2\n\t\treturn x1, y1, x2, y2\n\n\nclass GyroAction(Action):\n\t\"\"\" Uses *relative* gyroscope position as input for emulated axes \"\"\"\n\tCOMMAND = \"gyro\"\n\t\n\tdef __init__(self, axis1, axis2=None, axis3=None):\n\t\tAction.__init__(self, axis1, *strip_none(axis2, axis3))\n\t\tself.axes = [ axis1, axis2, axis3 ]\n\t\tself.speed = (1.0, 1.0, 1.0)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_SENSITIVITY | Action.MOD_SENS_Z\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tself.speed = (x, y, z)\n\t\n\t\n\tdef get_speed(self):\n\t\treturn self.speed\n\t\n\t\n\tdef gyro(self, mapper, *pyr):\n\t\tfor i in (0, 1, 2):\n\t\t\taxis = self.axes[i]\n\t\t\t# 'gyro' cannot map to mouse, but 'mouse' does that.\n\t\t\tif axis in Axes.__members__.values() or type(axis) == int:\n\t\t\t\tmapper.gamepad.axisEvent(axis, AxisAction.clamp_axis(axis, pyr[i] * self.speed[i] * -10))\n\t\t\t\tmapper.syn_list.add(mapper.gamepad)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name : return self.name\n\t\trv = []\n\t\t\n\t\tif self.axes[0] in Rels.__members__.values():\n\t\t\treturn _(\"Mouse\")\n\t\t\n\t\tfor x in self.axes:\n\t\t\tif x:\n\t\t\t\ts, trash, trash = AxisAction.get_axis_description(x)\n\t\t\t\tif s not in rv: rv.append(s)\n\t\treturn \"\\n\".join(rv)\n\n\nclass GyroAbsAction(HapticEnabledAction, GyroAction):\n\t\"\"\" Uses *absolute* gyroscope position as input for emulated axes \"\"\"\n\tCOMMAND = \"gyroabs\"\n\tMOUSE_FACTOR = 0.01\t# Just random number to put default sensitivity into sane range\n\t\n\tdef __init__(self, *blah):\n\t\tGyroAction.__init__(self, *blah)\n\t\tHapticEnabledAction.__init__(self)\n\t\tself.ir = [ 0, 0, None, 0 ]\t# Initial rotation, last has to be determined\n\t\tself._was_oor = False\n\t\tself._deadzone_fn = None\n\t\n\t\n\tdef reset(self):\n\t\tself.ir = [ None, None, None, None ]\t# Determine everything\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn ( HapticEnabledAction.get_compatible_modifiers(self)\n\t\t\t| GyroAction.get_compatible_modifiers(self)\n\t\t\t| Action.MOD_DEADZONE )\n\t\n\t\n\tdef get_previewable(self):\n\t\treturn True\n\t\n\tGYROAXES = (0, 1, 2)\n\tdef gyro(self, mapper, pitch, yaw, roll, q1, q2, q3, q4):\n\t\tif mapper.get_controller().flags & ControllerFlags.EUREL_GYROS:\n\t\t\tpyr = [q1 / 10430.37, q2 / 10430.37, q3 / 10430.37]\t# 2**15 / PI\n\t\telse:\n\t\t\tpyr = list(quat2euler(q1 / 32767.0, q2 / 32767.0, q3 / 32767.0, q4 / 32767.0))\n\t\tfor i in self.GYROAXES:\n\t\t\tself.ir[i] = self.ir[i] or pyr[i]\n\t\t\tpyr[i] = anglediff(self.ir[i], pyr[i]) * (2**15) * self.speed[2] * 2 / PI\n\t\tif self.haptic:\n\t\t\toor = False # oor - Out Of Range\n\t\t\tfor i in self.GYROAXES:\n\t\t\t\tpyr[i] = int(pyr[i])\n\t\t\t\tif pyr[i] > STICK_PAD_MAX:\n\t\t\t\t\tpyr[i] = STICK_PAD_MAX\n\t\t\t\t\toor = True\n\t\t\t\telif pyr[i] < STICK_PAD_MIN:\n\t\t\t\t\tpyr[i] = STICK_PAD_MIN\n\t\t\t\t\toor = True\n\t\t\tif oor:\n\t\t\t\tif not self._was_oor:\n\t\t\t\t\tmapper.send_feedback(self.haptic)\n\t\t\t\t\tself._was_oor = True\n\t\t\telse:\n\t\t\t\tself._was_oor = False\n\t\telse:\n\t\t\tfor i in self.GYROAXES:\n\t\t\t\tpyr[i] = int(clamp(STICK_PAD_MIN, pyr[i], STICK_PAD_MAX))\n\t\tfor i in self.GYROAXES:\n\t\t\taxis = self.axes[i]\n\t\t\tif axis in Axes.__members__.values() or type(axis) == int:\n\t\t\t\tval = AxisAction.clamp_axis(axis, pyr[i] * self.speed[i])\n\t\t\t\tif self._deadzone_fn:\n\t\t\t\t\tval, trash = self._deadzone_fn(val, 0, STICK_PAD_MAX)\n\t\t\t\t\tval = int(val)\n\t\t\t\tmapper.gamepad.axisEvent(axis, val)\n\t\t\t\tmapper.syn_list.add(mapper.gamepad)\n\t\t\telif axis == Rels.REL_X:\n\t\t\t\tmapper.mouse_move(AxisAction.clamp_axis(axis, pyr[i] * GyroAbsAction.MOUSE_FACTOR * self.speed[i]), 0)\n\t\t\telif axis == Rels.REL_Y:\n\t\t\t\tmapper.mouse_move(0, AxisAction.clamp_axis(axis, pyr[i] * GyroAbsAction.MOUSE_FACTOR * self.speed[i]))\n\n\nclass ResetGyroAction(Action):\n\t\"\"\"\n\tAsks mapper to search for all GyroAbsActions in profile and adjust offsets\n\tso current pad orientation is treated as neutral.\n\t\"\"\"\n\tCOMMAND = \"resetgyro\"\n\t\n\tdef button_press(self, mapper):\n\t\tmapper.reset_gyros()\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name : return self.name\n\t\treturn _(\"Recenter Gyro\")\n\n\nclass MultichildAction(Action):\n\t\"\"\" Mixin with nice looking to_string() method \"\"\"\n\t\n\tdef compress(self):\n\t\tself.actions = [ x.compress() for x in self.actions ]\n\t\treturn self\n\t\n\t\n\tdef get_child_actions(self):\n\t\treturn self.actions\n\t\n\t\n\tdef cancel(self, mapper):\n\t\tfor a in self.actions:\n\t\t\ta.cancel(mapper)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0, prefixparams=\"\"):\n\t\tif multiline:\n\t\t\trv = [ (\" \" * pad) + self.COMMAND + \"(\" + prefixparams.strip() ]\n\t\t\tpad += 2\n\t\t\tfor a in strip_none(*self.actions):\n\t\t\t\trv += [ a.to_string(True, pad) + \",\"]\n\t\t\tif rv[-1].endswith(\",\"):\n\t\t\t\trv[-1] = rv[-1][0:-1]\n\t\t\tpad -= 2\n\t\t\trv += [ (\" \" * pad) + \")\" ]\n\t\t\treturn \"\\n\".join(rv)\n\t\treturn self.COMMAND + \"(\" + prefixparams + (\", \".join([\n\t\t\tx.to_string() if x is not None else \"None\"\n\t\t\tfor x in strip_none(*self.actions)\n\t\t])) + \")\"\n\n\nclass TiltAction(MultichildAction):\n\t\"\"\"\n\tActivates one of 6 defined actions based on direction in\n\twhich controller is tilted or rotated.\n\t\"\"\"\n\tCOMMAND = \"tilt\"\n\tMIN = 0.75\n\t\n\tdef __init__(self, *actions):\n\t\t\"\"\"\n\t\tOrder of actions:\n\t\t - Front faces down\n\t\t - Front faces up\n\t\t - Tilted left\n\t\t - Tilted right\n\t\t - Rotated left\n\t\t - Rotated right\n\t\t\"\"\"\n\t\tMultichildAction.__init__(self, *strip_none(*actions))\n\t\tself.actions = ensure_size(6, actions, NoAction())\n\t\tself.states = [ None, None, None, None, None, None ]\n\t\tself.speed = (1.0, 1.0, 1.0)\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tself.speed = (x, y, z)\n\t\n\t\n\tdef get_speed(self):\n\t\treturn self.speed\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_SENSITIVITY | Action.MOD_SENS_Z\n\t\n\t\n\tdef gyro(self, mapper, *pyr):\n\t\tq1, q2, q3, q4 = pyr[-4:]\n\t\tpyr = quat2euler(q1 / 32767.0, q2 / 32767.0, q3 / 32767.0, q4 / 32767.0)\n\t\tfor j in (0, 1, 2):\n\t\t\ti = j * 2\n\t\t\tif self.actions[i]:\n\t\t\t\tif pyr[j] < TiltAction.MIN * -1 / self.speed[j]:\n\t\t\t\t\t# Side faces down\n\t\t\t\t\tif not self.states[i]:\n\t\t\t\t\t\t# print(self.actions[i])\n\t\t\t\t\t\tself.actions[i].button_press(mapper)\n\t\t\t\t\t\tself.states[i] = True\n\t\t\t\telif self.states[i]:\n\t\t\t\t\t# Side no longer faces down\n\t\t\t\t\tself.actions[i].button_release(mapper)\n\t\t\t\t\tself.states[i] = False\n\t\t\tif self.actions[i+1]:\n\t\t\t\tif pyr[j] > TiltAction.MIN / self.speed[j]:\n\t\t\t\t\t# Side faces up\n\t\t\t\t\tif not self.states[i+1]:\n\t\t\t\t\t\tself.actions[i+1].button_press(mapper)\n\t\t\t\t\t\tself.states[i+1] = True\n\t\t\t\telif self.states[i+1]:\n\t\t\t\t\t# Side no longer faces up\n\t\t\t\t\tself.actions[i+1].button_release(mapper)\n\t\t\t\t\tself.states[i+1] = False\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\t\"\"\" Called when decoding profile from json \"\"\"\n\t\targs = [ parser.from_json_data(x) for x in data[TiltAction.COMMAND] ]\n\t\treturn TiltAction(*args)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name : return self.name\n\t\treturn _(\"Tilt\")\n\n\nclass TrackballAction(Action):\n\t\"\"\"\n\tball(trackpad); Never actually instantiated - Exists only to provide\n\tbackwards compatibility\n\t\"\"\"\n\tCOMMAND = \"trackball\"\n\t\n\tdef __new__(cls, speed=None):\n\t\tfrom scc.modifiers import BallModifier\n\t\treturn BallModifier(MouseAction(speed=speed))\n\n\nclass ButtonAction(HapticEnabledAction, Action):\n\t\"\"\"\n\tAction that outputs as button press and release.\n\tButton can be gamepad button, mouse button or keyboard key.\n\t\"\"\"\n\tCOMMAND = \"button\"\n\tSPECIAL_NAMES = {\n\t\tKeys.BTN_LEFT\t: \"Mouse Left\",\n\t\tKeys.BTN_MIDDLE\t: \"Mouse Middle\",\n\t\tKeys.BTN_RIGHT\t: \"Mouse Right\",\n\t\tKeys.BTN_SIDE\t: \"Mouse 8\",\n\t\tKeys.BTN_EXTRA\t: \"Mouse 9\",\n\n\t\tKeys.BTN_TR\t\t: \"Right Bumper\",\n\t\tKeys.BTN_TL\t\t: \"Left Bumper\",\n\t\tKeys.BTN_THUMBL\t: \"LStick Click\",\n\t\tKeys.BTN_THUMBR\t: \"RStick Click\",\n\t\tKeys.BTN_START\t: \"Start >\",\n\t\tKeys.BTN_SELECT\t: \"< Select\",\n\t\tKeys.BTN_A\t\t: \"A Button\",\n\t\tKeys.BTN_B\t\t: \"B Button\",\n\t\t# Work around busted Linux btn aliases\n\t\tKeys.BTN_NORTH\t: \"X Button\",\n\t\tKeys.BTN_WEST\t: \"Y Button\",\n\t\t\n\t\tKeys.KEY_PREVIOUSSONG\t: \"<< Song\",\n\t\tKeys.KEY_STOP\t\t\t: \"Stop\",\n\t\tKeys.KEY_PLAYPAUSE\t\t: \"Play/Pause\",\n\t\tKeys.KEY_NEXTSONG\t\t: \"Song >>\",\n\t\tKeys.KEY_VOLUMEDOWN\t\t: \"- Volume\",\n\t\tKeys.KEY_VOLUMEUP\t\t: \"+ Volume\"\n\t}\n\tMODIFIERS_NAMES = {\n\t\tKeys.KEY_LEFTSHIFT\t: \"Shift\",\n\t\tKeys.KEY_LEFTCTRL\t: \"Ctrl\",\n\t\tKeys.KEY_LEFTMETA\t: \"Meta\",\n\t\tKeys.KEY_LEFTALT\t: \"Alt\",\n\t\tKeys.KEY_RIGHTSHIFT\t: \"Shift\",\n\t\tKeys.KEY_RIGHTCTRL\t: \"Ctrl\",\n\t\tKeys.KEY_RIGHTMETA\t: \"Meta\",\n\t\tKeys.KEY_RIGHTALT\t: \"Alt\"\n\t}\n\tCIRCULAR_INTERVAL = 1000\n\tSTICK_DEADZONE = 100\n\t\n\tdef __init__(self, button1, button2 = None, minustrigger = None, plustrigger = None):\n\t\tAction.__init__(self, button1, *strip_none(button2, minustrigger, plustrigger))\n\t\tHapticEnabledAction.__init__(self)\n\t\tself.button = button1 or None\n\t\tself.button2 = button2 or None\n\t\t# minustrigger and plustrigger are not used anymore, __init__ takes\n\t\t# them only for backwards compatibility.\n\t\t# TODO: Remove backwards compatibility\n\t\tself._change = 0\n\t\tself._pressed_key = None\n\t\tself._released = True\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name:\n\t\t\treturn self.name\n\t\telif self.button is Rels.REL_WHEEL:\n\t\t\tif len(self.parameters) < 2 or self.parameters[1] > 0:\n\t\t\t\treturn _(\"Wheel UP\")\n\t\t\telse:\n\t\t\t\treturn _(\"Wheel DOWN\")\n\t\telse:\n\t\t\trv = [ ]\n\t\t\tfor x in (self.button, self.button2):\n\t\t\t\tif x:\n\t\t\t\t\trv.append(ButtonAction.describe_button(x, context=context))\n\t\t\treturn \", \".join(rv)\n\t\n\t\n\t@staticmethod\n\tdef describe_button(button, context=Action.AC_BUTTON):\n\t\tif button in ButtonAction.SPECIAL_NAMES:\n\t\t\treturn _(ButtonAction.SPECIAL_NAMES[button])\n\t\telif button in MOUSE_BUTTONS:\n\t\t\treturn _(\"Mouse %s\") % (button,)\n\t\telif context == Action.AC_OSK:\n\t\t\tif button in ButtonAction.MODIFIERS_NAMES:\n\t\t\t\treturn _(ButtonAction.MODIFIERS_NAMES[button])\n\t\t\telif button in Keys.__members__.values():\n\t\t\t\treturn button.name.split(\"_\", 1)[-1].title()\n\t\t\treturn \"\"\n\t\telif button is None: # or isinstance(button, NoAction):\n\t\t\treturn \"None\"\n\t\telif button in Keys.__members__.values():\n\t\t\treturn button.name.split(\"_\", 1)[-1]\n\t\telse:\n\t\t\treturn _(\"Button %i\") % (button,)\n\t\n\t\n\tdef describe_short(self):\n\t\t\"\"\"\n\t\tUsed when multiple ButtonActions are chained together, for\n\t\tcombinations like Alt+TAB\n\t\t\"\"\"\n\t\tif self.button in self.MODIFIERS_NAMES:\n\t\t\t# Modifiers are special case here\n\t\t\treturn self.MODIFIERS_NAMES[self.button]\n\t\treturn self.describe(Action.AC_BUTTON)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\t# Allows feedback and OSD\n\t\treturn Action.MOD_OSD | HapticEnabledAction.get_compatible_modifiers(self)\n\t\n\t\n\t@staticmethod\n\tdef _button_press(mapper, button, immediate=False, haptic=None):\n\t\tif button in mapper.pressed and mapper.pressed[button] > 0:\n\t\t\t# Virtual button is already pressed - generate release event first\n\t\t\tpc = mapper.pressed[button]\n\t\t\tButtonAction._button_release(mapper, button, immediate)\n\t\t\t# ... then inrease 'press counter' and generate press event as usual\n\t\t\tmapper.pressed[button] = pc + 1\n\t\telse:\n\t\t\tmapper.pressed[button] = 1\n\t\t\n\t\tif button in MOUSE_BUTTONS:\n\t\t\tmapper.mouse.keyEvent(button, 1)\n\t\t\tmapper.syn_list.add(mapper.mouse)\n\t\telif button in GAMEPAD_BUTTONS:\n\t\t\tmapper.gamepad.keyEvent(button, 1)\n\t\t\tmapper.syn_list.add(mapper.gamepad)\n\t\telif immediate:\n\t\t\tmapper.keyboard.keyEvent(button, 1)\n\t\t\tmapper.syn_list.add(mapper.keyboard)\n\t\telse:\n\t\t\tmapper.keypress_list.append(button)\n\t\tif haptic:\n\t\t\tmapper.send_feedback(haptic)\n\t\n\t\n\t@staticmethod\n\tdef _button_release(mapper, button, immediate=False):\n\t\tif button in mapper.pressed:\n\t\t\tif mapper.pressed[button] > 1:\n\t\t\t\t# More than one action pressed this virtual button - decrease\n\t\t\t\t# counter, but don't release button yet\n\t\t\t\tmapper.pressed[button] -= 1\n\t\t\t\treturn\n\t\t\telse:\n\t\t\t\t# This is last action that kept virtual button held\n\t\t\t\tdel mapper.pressed[button]\n\t\t\n\t\tif button in MOUSE_BUTTONS:\n\t\t\tmapper.mouse.keyEvent(button, 0)\n\t\t\tmapper.syn_list.add(mapper.mouse)\n\t\telif button in GAMEPAD_BUTTONS:\n\t\t\tmapper.gamepad.keyEvent(button, 0)\n\t\t\tmapper.syn_list.add(mapper.gamepad)\n\t\telif immediate:\n\t\t\tmapper.keyboard.keyEvent(button, 0)\n\t\t\tmapper.syn_list.add(mapper.keyboard)\n\t\telse:\n\t\t\tmapper.keyrelease_list.append(button)\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tButtonAction._button_press(mapper, self.button, haptic=self.haptic)\n\t\tif self.haptic:\n\t\t\tmapper.send_feedback(self.haptic)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tButtonAction._button_release(mapper, self.button)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif what == STICK:\n\t\t\t# Stick used used as one big button (probably as part of ring bindings)\n\t\t\tif abs(x) < ButtonAction.STICK_DEADZONE and abs(y) < ButtonAction.STICK_DEADZONE:\n\t\t\t\tif self._pressed_key == self.button:\n\t\t\t\t\tself.button_release(mapper)\n\t\t\t\t\tself._pressed_key = None\n\t\t\telif self._pressed_key != self.button:\n\t\t\t\tself.button_press(mapper)\n\t\t\t\tself._pressed_key = self.button\n\t\t\treturn\n\t\telif what in (LEFT, RIGHT):\n\t\t\t# Possibly special case, pressing with click() on entire pad\n\t\t\tif mapper.is_pressed(what) and not mapper.was_pressed(what):\n\t\t\t\treturn self.button_press(mapper)\n\t\t\telif not mapper.is_pressed(what) and mapper.was_pressed(what):\n\t\t\t\treturn self.button_release(mapper)\n\t\t# Entire pad used as one big button\n\t\tif mapper.is_touched(what) and not mapper.was_touched(what):\n\t\t\t# Touched the pad\n\t\t\tself.button_press(mapper)\n\t\tif mapper.was_touched(what) and not mapper.is_touched(what):\n\t\t\t# Released the pad\n\t\t\tself.button_release(mapper)\n\t\n\t\n\tdef axis(self, mapper, position, what):\n\t\t# Choses which key or button should be pressed or released based on\n\t\t# current stick position.\n\t\t\n\t\t# TODO: Remove this, convert it to DPAD internally\n\t\tif self._pressed_key == self.button and position > STICK_PAD_MIN_HALF:\n\t\t\tButtonAction._button_release(mapper, self.button)\n\t\t\tself._pressed_key = None\n\t\telif self._pressed_key != self.button and position <= STICK_PAD_MIN_HALF:\n\t\t\tButtonAction._button_press(mapper, self.button)\n\t\t\tself._pressed_key = self.button\n\t\tif self.button2 is not None:\n\t\t\tif self._pressed_key == self.button2 and position < STICK_PAD_MAX_HALF:\n\t\t\t\tButtonAction._button_release(mapper, self.button2)\n\t\t\t\tself._pressed_key = None\n\t\t\telif self._pressed_key != self.button2 and position >= STICK_PAD_MAX_HALF:\n\t\t\t\tButtonAction._button_press(mapper, self.button2)\n\t\t\t\tself._pressed_key = self.button2\n\t\n\t\n\tdef trigger(self, mapper, p, old_p):\n\t\t# Choses which key or button should be pressed or released based on\n\t\t# current trigger position.\n\t\t# TODO: Remove this, call to TriggerAction instead\n\t\tif self.button2 is None:\n\t\t\tif p >= TRIGGER_HALF and old_p < TRIGGER_HALF:\n\t\t\t\tButtonAction._button_press(mapper, self.button, haptic=self.haptic)\n\t\t\telif p < TRIGGER_HALF and old_p >= TRIGGER_HALF:\n\t\t\t\tButtonAction._button_release(mapper, self.button)\n\t\telse:\n\t\t\tif p >= TRIGGER_HALF and p < TRIGGER_CLICK:\n\t\t\t\tif self._pressed_key != self.button and self._released:\n\t\t\t\t\tButtonAction._button_press(mapper, self.button)\n\t\t\t\t\tself._pressed_key = self.button\n\t\t\t\t\tself._released = False\n\t\t\telse:\n\t\t\t\tif self._pressed_key == self.button:\n\t\t\t\t\tButtonAction._button_release(mapper, self.button)\n\t\t\t\t\tself._pressed_key = None\n\t\t\tif p > TRIGGER_CLICK and old_p < TRIGGER_CLICK:\n\t\t\t\tif self._pressed_key != self.button2:\n\t\t\t\t\tif self._pressed_key is not None:\n\t\t\t\t\t\tButtonAction._button_release(mapper, self._pressed_key)\n\t\t\t\t\tButtonAction._button_press(mapper, self.button2, haptic=self.haptic)\n\t\t\t\t\tself._pressed_key = self.button2\n\t\t\t\t\tself._released = False\n\t\t\telse:\n\t\t\t\tif self._pressed_key == self.button2:\n\t\t\t\t\tButtonAction._button_release(mapper, self.button2)\n\t\t\t\t\tself._pressed_key = None\n\t\t\n\t\tif p <= TRIGGER_MIN:\n\t\t\tself._released = True\n\t\n\t\n\tdef change(self, mapper, dx, dy, what):\n\t\t\"\"\" Makes sense with circular() modifier \"\"\"\n\t\tself._change += dx\n\t\tif self._change < -ButtonAction.CIRCULAR_INTERVAL:\n\t\t\tself._change += ButtonAction.CIRCULAR_INTERVAL\n\t\t\tif self.button:\n\t\t\t\tButtonAction._button_press(mapper, self.button)\n\t\t\t\tButtonAction._button_release(mapper, self.button)\n\t\telif self._change > ButtonAction.CIRCULAR_INTERVAL:\n\t\t\tself._change -= ButtonAction.CIRCULAR_INTERVAL\n\t\t\tif self.button2:\n\t\t\t\tButtonAction._button_press(mapper, self.button2)\n\t\t\t\tButtonAction._button_release(mapper, self.button2)\n\n\nclass MultiAction(MultichildAction):\n\t\"\"\"\n\tTwo or more actions executed at once.\n\tGenerated when parsing 'and'\n\t\"\"\"\n\tCOMMAND = None\n\tPROFILE_KEYS = \"actions\",\n\tPROFILE_KEY_PRIORITY = -20\t# First possible\n\t\n\tdef __init__(self, *actions):\n\t\tself.actions = []\n\t\tself.name = None\n\t\tself._add_all(actions)\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\t\"\"\" Called when decoding profile from json \"\"\"\n\t\treturn MultiAction.make(*[\n\t\t\tparser.from_json_data(a) for a in data['actions']\n\t\t])\n\t\n\t\n\t@staticmethod\n\tdef make(*a):\n\t\t\"\"\"\n\t\tConnects two or more actions, ignoring NoActions.\n\t\tReturns resulting MultiAction or just one action if every other\n\t\tparameter is NoAction or only one parameter was passed.\n\t\tReturns NoAction() if there are no parameters,\n\t\tor every parameter is NoAction.\n\t\t\"\"\"\n\t\ta = [ a for a in a if a.strip() ]\t# 8-)\n\t\t# (^^ NoAction is eveluated as False)\n\t\tif len(a) == 0:\n\t\t\treturn NoAction()\n\t\tif len(a) == 1:\n\t\t\treturn a[0]\n\t\treturn MultiAction(*a)\n\t\n\t\n\tdef _add_all(self, actions):\n\t\tfor x in actions:\n\t\t\tif type(x) == list:\n\t\t\t\tself._add_all(x)\n\t\t\telif x:\n\t\t\t\tself._add(x)\n\t\n\t\n\tdef _add(self, action):\n\t\tif action.__class__ == self.__class__:\t# I don't want subclasses here\n\t\t\tself._add_all(action.actions)\n\t\telse:\n\t\t\tself.actions.append(action)\n\t\t\tif action.name : self.name = action.name\n\t\n\t\n\tdef deduplicate(self):\n\t\t\"\"\"\n\t\tRemoves duplicate actions. This is done by comparing string\n\t\trepresentations, so it's slow and ususally unnecesary, but can be\n\t\tusefull when importing.\n\t\t\n\t\tReturns new MultiAction, or, if only one action is left, returns\n\t\tthat last action.\n\t\t\"\"\"\n\t\tactions, strings = [], []\n\t\t# TODO: Action comparison, don't use strings\n\t\tfor x in self.actions:\n\t\t\ts = x.to_string()\n\t\t\tif s in strings: continue\n\t\t\tactions.append(x)\n\t\t\tstrings.append(s)\n\t\tif len(actions) == 0:\n\t\t\t# Impossible\n\t\t\treturn NoAction()\n\t\telif len(actions) == 1:\n\t\t\treturn actions[0]\n\t\telse:\n\t\t\treturn MultiAction.make(*actions)\n\t\n\t\n\tdef compress(self):\n\t\tnw = [ x.compress() for x in self.actions ]\n\t\tself.actions = nw\n\t\treturn self\n\t\n\t\n\tdef set_haptic(self, hapticdata):\n\t\tfor a in self.actions:\n\t\t\tif a and hasattr(a, \"set_haptic\"):\n\t\t\t\t# Only first feedback-supporting action should do feedback\n\t\t\t\ta.set_haptic(hapticdata)\n\t\t\t\treturn\n\t\n\t\n\tdef get_haptic(self):\n\t\tfor a in self.actions:\n\t\t\tif a and hasattr(a, \"set_haptic\"):\n\t\t\t\treturn a.get_haptic()\n\t\treturn None\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tfor a in self.actions:\n\t\t\tif hasattr(a, \"set_speed\"):\n\t\t\t\ta.set_speed(x, y, z)\n\t\n\t\n\tdef get_speed(self):\n\t\tfor a in self.actions:\n\t\t\tif hasattr(a, \"set_speed\"):\n\t\t\t\treturn a.get_speed()\n\t\treturn (1.0,)\n\t\n\t\n\tdef is_key_combination(self):\n\t\t\"\"\" Returns True if all child actions are ButtonActions \"\"\"\n\t\tif len(self.actions) == 0:\n\t\t\treturn False\n\t\tfor x in self.actions:\n\t\t\tif not isinstance(x, ButtonAction):\n\t\t\t\treturn False\n\t\treturn True\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif self.is_key_combination():\n\t\t\trv = []\n\t\t\tfor a in self.actions:\n\t\t\t\tif isinstance(a, ButtonAction):\n\t\t\t\t\trv.append(a.describe_short())\n\t\t\treturn \"+\".join(rv)\n\t\tif len(self.actions) >= 2 and isinstance(self.actions[1], RingAction):\n\t\t\t# Special case, should be multiline\n\t\t\treturn \"\\n\".join([ x.describe(context) for x in self.actions ])\n\t\treturn \" and \".join([ x.describe(context) for x in self.actions ])\n\t\n\t\n\tdef execute(self, event):\n\t\trv = False\n\t\tfor a in self.actions:\n\t\t\trv = a.execute(event)\n\t\treturn rv\n\t\n\t\n\tdef button_press(self, *p):\n\t\tfor a in self.actions: a.button_press(*p)\n\t\n\tdef button_release(self, *p):\n\t\tfor a in self.actions: a.button_release(*p)\n\t\n\tdef axis(self, *p):\n\t\tfor a in self.actions: a.axis(*p)\n\t\n\tdef pad(self, *p):\n\t\tfor a in self.actions: a.pad(*p)\n\t\n\tdef add(self, *p):\n\t\tfor a in self.actions: a.add(*p)\n\t\n\tdef change(self, *p):\n\t\tfor a in self.actions: a.change(*p)\n\t\n\tdef gyro(self, *p):\n\t\tfor a in self.actions: a.gyro(*p)\n\t\n\tdef whole(self, *p):\n\t\tfor a in self.actions: a.whole(*p)\n\t\n\tdef trigger(self, *p):\n\t\tfor a in self.actions: a.trigger(*p)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \" and \".join([ x.to_string() for x in self.actions ])\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<[ %s ]>\" % (\" and \".join([ str(x) for x in self.actions ]), )\n\t\n\t__repr__ = __str__\n\n\nclass DPadAction(MultichildAction, HapticEnabledAction):\n\tCOMMAND = \"dpad\"\n\tPROFILE_KEY_PRIORITY = -10\t# First possible\n\t\n\tDEFAULT_DIAGONAL_RANGE = 45\n\tMIN_DISTANCE_P2 = 2000000\t# Power of 2 from minimal distance that finger\n\t\t\t\t\t\t\t\t# has to be from center\n\t\n\tSIDE_NONE = (None, None)\n\tSIDES = (\n\t\t# Just list of magic numbers that would have\n\t\t# to be computed on the fly otherwise\n\t\t# 0 - up, 1 - down, 2 - left, 3 - right\n\t\t( None, 1 ),\t\t# Index 0, down\n\t\t( 2, 1 ),\t\t\t# Index 1, down-left\n\t\t( 2, None),\t\t\t# Index 2, left\n\t\t( 2, 0 ),\t\t\t# Index 3, up-left\n\t\t( None, 0 ),\t\t# Index 4, up\n\t\t( 3, 0 ),\t\t\t# Index 5, up-right\n\t\t( 3, None ),\t\t# Index 6, right\n\t\t( 3, 1 ),\t\t\t# Index 7, down-right\n\t\t( None, 1 ),\t\t# Index 8, same as 0\n\t)\n\t\n\tdef __init__(self, *actions):\n\t\tMultichildAction.__init__(self, *actions)\n\t\tHapticEnabledAction.__init__(self)\n\t\tself.diagonal_rage = DPadAction.DEFAULT_DIAGONAL_RANGE\n\t\tif len(actions) > 0 and type(actions[0]) in (int, float):\n\t\t\tself.diagonal_rage = clamp(1, int(actions[0]), 89)\n\t\t\tactions = actions[1:]\n\t\tself.actions = self._ensure_size(actions)\n\t\tself.dpad_state = [ None, None ]\t# X, Y\n\t\tself.side_before = None\n\t\t# Generate mapping of angle range -> index\n\t\tself.ranges = []\n\t\tnormal_range = 90 - self.diagonal_rage\n\t\ti = 360-normal_range / 2\n\t\tfor x in range(0, 9):\n\t\t\tr = normal_range if x % 2 == 0 else self.diagonal_rage\n\t\t\ti, j = (i + r) % 360, i\n\t\t\tself.ranges.append(( j, i, x % 8 ))\n\t\n\t\n\tdef _ensure_size(self, actions):\n\t\treturn ensure_size(4, actions, NoAction())\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\t\"\"\" Called when decoding profile from json \"\"\"\n\t\targs = [ parser.from_json_data(x) for x in data[DPadAction.COMMAND] ]\n\t\tif len(args) > 4:\n\t\t\ta = DPad8Action(*args)\n\t\telse:\n\t\t\ta = DPadAction(*args)\n\t\treturn a\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0, prefixparams=\"\"):\n\t\tif self.diagonal_rage != DPadAction.DEFAULT_DIAGONAL_RANGE:\n\t\t\treturn MultichildAction.to_string(self, multiline, pad,\n\t\t\t\t\tprefixparams = \"%s, \" % (self.diagonal_rage, ))\n\t\treturn MultichildAction.to_string(self, multiline, pad)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn (Action.MOD_CLICK | Action.MOD_ROTATE\n\t\t\t| Action.MOD_DEADZONE | Action.MOD_FEEDBACK )\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\t# Two special, most used cases of dpad\n\t\twsad = [ a.button for a in self.actions if isinstance(a, ButtonAction) ]\n\t\tif len(wsad) == 4:\n\t\t\tif wsad == [Keys.KEY_UP, Keys.KEY_DOWN, Keys.KEY_LEFT, Keys.KEY_RIGHT]:\n\t\t\t\treturn _(\"Arrows\")\n\t\t\tif wsad == [Keys.KEY_W, Keys.KEY_S, Keys.KEY_A, Keys.KEY_D]:\n\t\t\t\treturn _(\"WSAD\")\n\t\treturn \"DPad\"\n\t\n\t\n\tdef compute_side(self, x, y):\n\t\t\"\"\" Computes which sides of dpad are supposed to be active \"\"\"\n\t\t## dpad(up, down, left, right)\n\t\t## dpad8(up, down, left, right, upleft, upright, downleft, downright)\n\t\tside = self.SIDE_NONE\n\t\tif x*x + y*y > self.MIN_DISTANCE_P2:\n\t\t\t# Compute angle from center of pad to finger position\n\t\t\tangle = (atan2(x, y) * 180.0 / PI) + 180\n\t\t\t# Translate it to index\n\t\t\tindex = 0\n\t\t\tfor a1, a2, i in self.ranges:\n\t\t\t\tif angle >= a1 and angle < a2:\n\t\t\t\t\tindex = i\n\t\t\t\t\tbreak\n\t\t\tside = self.SIDES[index]\n\t\treturn side\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif self.haptic:\n\t\t\t# Called like this just so there is not same code on two places\n\t\t\tside = self.whole_blocked(mapper, x, y, what)\n\t\telse:\n\t\t\tside = self.compute_side(x, y)\n\t\t\n\t\tfor i in (0, 1):\n\t\t\tif side[i] != self.dpad_state[i] and self.dpad_state[i] is not None:\n\t\t\t\tif self.actions[self.dpad_state[i]] is not None:\n\t\t\t\t\tself.actions[self.dpad_state[i]].button_release(mapper)\n\t\t\t\tself.dpad_state[i] = None\n\t\t\tif side[i] is not None and side[i] != self.dpad_state[i]:\n\t\t\t\tif self.actions[side[i]] is not None:\n\t\t\t\t\tself.actions[side[i]].button_press(mapper)\n\t\t\t\tself.dpad_state[i] = side[i]\n\t\n\t\n\tdef whole_blocked(self, mapper, x, y, what):\n\t\tif self.haptic:\n\t\t\tside = self.compute_side(x, y)\n\t\t\tif self.side_before != side:\n\t\t\t\tself.side_before = side\n\t\t\t\tmapper.send_feedback(self.haptic)\n\t\t\treturn side\n\t\treturn None\n\t\n\t\n\tdef change(self, mapper, dx, dy, what):\n\t\tself.whole(mapper, dx, -dy, what)\n\n\nclass DPad8Action(DPadAction):\n\tCOMMAND = \"dpad8\"\n\tPROFILE_KEYS = ()\n\t\n\tSIDE_NONE = None\n\tSIDES = (\n\t\t# Another list of magic numbers that would have`\n\t\t# to be computed on the fly otherwise\n\t\t1,\t# index 0 - down\n\t\t6,\t# index 1 - down-left\n\t\t2,\t# index 2 - left\n\t\t4,\t# index 3 - up-left\n\t\t0,\t# index 4 - up\n\t\t5,\t# index 5 - up-right\n\t\t3,\t# index 6 - right\n\t\t7,\t# index 7 - downright\n\t\t1,\t# index 8 - same as 0\n\t)\n\t\n\t\n\tdef _ensure_size(self, actions):\n\t\treturn ensure_size(8, actions, NoAction())\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn \"8-Way DPad\"\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tside = self.compute_side(x, y)\n\t\t\n\t\t# 8-way dpad presses only one button at time, so only one index\n\t\t# in dpad_state is used.\n\t\tif side != self.dpad_state[0]:\n\t\t\tif self.dpad_state[0] is not None:\n\t\t\t\tself.actions[self.dpad_state[0]].button_release(mapper)\n\t\t\tif side is not None:\n\t\t\t\tself.actions[side].button_press(mapper)\n\t\t\tself.dpad_state[0] = side\n\n\nclass RingAction(MultichildAction):\n\t\"\"\"\n\tRing action splits pad into \"inner\" and \"outer\" ring and allow binding\n\ttwo different child actions in same way as DPadAction does.\n\t\n\tCombining RingAction with two DPad8Actions allows to assign\n\tup to 16 different bindings to one pad.\n\t\"\"\"\n\tCOMMAND = \"ring\"\n\tPROFILE_KEY_PRIORITY = -10\t# First possible\n\tDEFAULT_RADIUS = 0.5\n\t\n\t\n\tdef __init__(self, *params):\n\t\t# 1st parameter may be inner ring radius (0.1 to 0.9), defaults to 50%\n\t\t# of pad diameter.\n\t\tself.radius = RingAction.DEFAULT_RADIUS\n\t\tif len(params) > 1 and type(params[0]) in (int, float):\n\t\t\tself.radius = float(params[0])\n\t\t\tparams = params[1:]\n\t\tMultichildAction.__init__(self, *params)\n\t\tself.actions = ensure_size(2, params, NoAction())\n\t\tself.inner, self.outer = self.actions\n\t\tself._radius_m = STICK_PAD_MAX * self.radius\t# radius, multiplied\n\t\tself._active = NoAction()\n\t\n\t\n\tdef compress(self):\n\t\tself.inner = self.inner.compress()\n\t\tself.outer = self.outer.compress()\n\t\treturn self\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\t\"\"\" Called when decoding profile from json \"\"\"\n\t\targs, data = [], data[RingAction.COMMAND]\n\t\tif 'radius' in data: args.append(float(data['radius']))\n\t\targs.append(parser.from_json_data(data['inner']) if 'inner' in data else NoAction())\n\t\targs.append(parser.from_json_data(data['outer']) if 'outer' in data else NoAction())\n\t\treturn RingAction(*args)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn 0\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tlines = [ x.describe(Action.AC_BUTTON) for x in self.actions if x ]\n\t\tif any([\"\\n\" in l for l in lines ]):\n\t\t\treturn \" / \".join([ l for l in lines ])\n\t\telse:\n\t\t\treturn \"\\n\".join([ l for l in lines ])\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tif self.radius != RingAction.DEFAULT_RADIUS:\n\t\t\treturn MultichildAction.to_string(self, multiline, pad, \"%s, \" % (self.radius,))\n\t\telse:\n\t\t\treturn MultichildAction.to_string(self, multiline, pad)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif what == STICK or mapper.is_touched(what):\n\t\t\tangle = atan2(x, y)\n\t\t\tdistance = sqrt(x*x + y*y)\n\t\t\tif distance < self._radius_m:\n\t\t\t\t# Inner radius\n\t\t\t\taction = self.inner\n\t\t\t\tdistance /= self.radius\n\t\t\telse:\n\t\t\t\taction = self.outer\n\t\t\t\tdistance = (distance - self._radius_m) / (1.0 - self.radius)\n\t\t\tx = distance * sin(angle)\n\t\t\ty = distance * cos(angle)\n\t\t\t\n\t\t\tif action == self._active:\n\t\t\t\taction.whole(mapper, x, y, what)\n\t\t\telif what == STICK:\n\t\t\t\t# Stck crossed radius border, so active action is changing.\n\t\t\t\t# Simulate centering stick for former...\n\t\t\t\tself._active.whole(mapper, 0, 0, what)\n\t\t\t\t# ... and moving it back for new active child action\n\t\t\t\taction.whole(mapper, x, y, what)\n\t\t\t\tself._active = action\n\t\t\telse:\n\t\t\t\t# Finger crossed radius border, so active action is changing.\n\t\t\t\t# Simulate releasing pad for former...\n\t\t\t\tmapper.set_button(what, False)\n\t\t\t\tself._active.whole(mapper, 0, 0, what)\n\t\t\t\t# ... and touching it for new active child action\n\t\t\t\twas = mapper.was_touched(what)\n\t\t\t\tmapper.set_button(what, True)\n\t\t\t\tmapper.set_was_pressed(what, False)\n\t\t\t\taction.whole(mapper, x, y, what)\n\t\t\t\tmapper.set_was_pressed(what, was)\n\t\t\t\tself._active = action\n\t\telif mapper.was_touched(what):\n\t\t\t# Pad just released\n\t\t\tself._active.whole(mapper, x, y, what)\n\t\t\tself._active = NoAction()\n\t\telif self._active and what == STICK and x == 0 and y == 0:\n\t\t\t# Stick is centered\n\t\t\tself._active.whole(mapper, x, y, what)\n\t\t\tself._active = NoAction()\n\n\nclass XYAction(WholeHapticAction, Action):\n\t\"\"\"\n\tUsed for sticks and pads when actions for X and Y axis are different.\n\t\"\"\"\n\tCOMMAND = \"XY\"\n\tPROFILE_KEYS = (\"X\", \"Y\")\n\tPROFILE_KEY_PRIORITY = -10\t# First possible, but not before MultiAction\n\tSTICK_REPEAT_INTERVAL = 0.01\n\tSTICK_REPEAT_MIN = 10\n\t\n\tdef __init__(self, x=None, y=None):\n\t\tAction.__init__(self, *strip_none(x, y))\n\t\tWholeHapticAction.__init__(self)\n\t\tself.x = x or NoAction()\n\t\tself.y = y or NoAction()\n\t\tself.actions = (self.x, self.y)\n\t\tself._old_distance = 0\n\t\tself._old_pos = None\n\t\tif hasattr(self.x, \"add\") or hasattr(self.y, \"add\"):\n\t\t\tself.add = self._add\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\tmods = ( Action.MOD_FEEDBACK | Action.MOD_SENSITIVITY\n\t\t\t| Action.MOD_ROTATE | Action.MOD_SMOOTH\n\t\t\t| self.x.get_compatible_modifiers()\n\t\t\t| self.y.get_compatible_modifiers()\n\t\t)\n\t\tif isinstance(self.x, AxisAction) and isinstance(self.y, AxisAction):\n\t\t\tif self.x.get_axis() in (Axes.ABS_X, Axes.ABS_Y, Axes.ABS_RX, Axes.ABS_RY):\n\t\t\t\tmods = (mods | Action.MOD_BALL) & ~Action.MOD_SMOOTH\n\t\treturn mods\n\t\n\t\n\tdef get_child_actions(self):\n\t\treturn self.x, self.y\n\t\n\t\n\t@staticmethod\n\tdef decode(data, action, parser, *a):\n\t\t\"\"\" Called when decoding profile from json \"\"\"\n\t\tx = parser.from_json_data(data[\"X\"]) if \"X\" in data else NoAction()\n\t\ty = parser.from_json_data(data[\"Y\"]) if \"Y\" in data else NoAction()\n\t\treturn XYAction(x, y)\n\t\n\t\n\tdef compress(self):\n\t\tself.x = self.x.compress()\n\t\tself.y = self.y.compress()\n\t\treturn self\n\t\n\t\n\tdef set_haptic(self, hapticdata):\n\t\tsupports = False\n\t\tif hasattr(self.x, \"set_haptic\"):\n\t\t\tself.x.set_haptic(hapticdata)\n\t\t\tsupports = True\n\t\tif hasattr(self.y, \"set_haptic\"):\n\t\t\tself.y.set_haptic(hapticdata)\n\t\t\tsupports = True\n\t\tif not supports:\n\t\t\t# Child action has no feedback support, do feedback here\n\t\t\tself.haptic = hapticdata\n\t\t\tself.big_click = hapticdata * 4\n\t\n\t\n\tdef get_haptic(self):\n\t\tif hasattr(self.x, \"set_haptic\"):\n\t\t\treturn self.x.get_haptic()\n\t\tif hasattr(self.y, \"set_haptic\"):\n\t\t\treturn self.y.get_haptic()\n\t\treturn self.haptic\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tif hasattr(self.x, \"set_speed\"):\n\t\t\tself.x.set_speed(x, 1, 1)\n\t\tif hasattr(self.y, \"set_speed\"):\n\t\t\tself.y.set_speed(y, 1, 1)\n\t\n\t\n\tdef get_speed(self):\n\t\trv = [0, 0]\n\t\tif hasattr(self.x, \"set_speed\"): rv[0] = self.x.get_speed()[0]\n\t\tif hasattr(self.y, \"set_speed\"): rv[1] = self.y.get_speed()[0]\n\t\treturn tuple(rv)\n\t\n\t\n\tdef get_previewable(self):\n\t\treturn self.x.get_previewable() and self.y.get_previewable()\n\t\n\t\n\tdef _add(self, mapper, x, y):\n\t\t\"\"\" Not always available \"\"\"\n\t\tif self.haptic:\n\t\t\tWholeHapticAction.add(self, mapper, x, y)\n\t\tif hasattr(self.x, \"add\"):\n\t\t\tself.x.add(mapper, x, 0)\n\t\tif hasattr(self.y, \"add\"):\n\t\t\tself.y.add(mapper, -y, 0)\n\t\tif self.haptic:\n\t\t\tWholeHapticAction.add(self, mapper, x, y)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif self.haptic:\n\t\t\tdistance = sqrt(x*x + y*y)\n\t\t\tis_close = distance > STICK_PAD_MAX * 2 / 3\n\t\t\twas_close = self._old_distance > STICK_PAD_MAX * 2 / 3\n\t\t\tif self._old_pos:\n\t\t\t\tWholeHapticAction.add(self, mapper,\n\t\t\t\t\tx - self._old_pos[0], y - self._old_pos[1])\n\t\t\tif is_close != was_close:\n\t\t\t\tmapper.send_feedback(self.big_click)\n\t\t\t\n\t\t\tself._old_distance = distance\n\t\t\tif mapper.is_touched(what):\n\t\t\t\tself._old_pos = x, y\n\t\t\telse:\n\t\t\t\tself._old_pos = None\n\t\t\n\t\tif mapper.controller_flags() & ControllerFlags.HAS_RSTICK and what == RIGHT:\n\t\t\tself.x.axis(mapper, x, what)\n\t\t\tself.y.axis(mapper, y, what)\n\t\t\tmapper.force_event.add(FE_PAD)\n\t\telif what in (LEFT, RIGHT, CPAD):\n\t\t\tself.x.pad(mapper, x, what)\n\t\t\tself.y.pad(mapper, y, what)\n\t\telse:\n\t\t\tself.x.axis(mapper, x, what)\n\t\t\tself.y.axis(mapper, y, what)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\trv = []\n\t\tif isinstance(self.x, AxisAction) and isinstance(self.y, AxisAction):\n\t\t\tif (self.x.id, self.y.id) in AxisAction.AXES_PAIRS:\n\t\t\t\t# Special cases for default stick bindings\n\t\t\t\tdesc, trash, trash = AxisAction.get_axis_description(self.x.id)\n\t\t\t\treturn desc\n\t\tif self.x: rv.append(self.x.describe(context))\n\t\tif self.y: rv.append(self.y.describe(context))\n\t\tif context in (Action.AC_STICK, Action.AC_PAD):\n\t\t\treturn \"\\n\".join(rv)\n\t\treturn \" \".join(rv)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tif multiline:\n\t\t\trv = [ (\" \" * pad) + self.COMMAND + \"(\" ]\n\t\t\trv += self.x.to_string(True, pad + 2).split(\"\\n\")\n\t\t\trv += [ (\" \" * pad) + \",\" ]\n\t\t\trv += self.y.to_string(True, pad + 2).split(\"\\n\")\n\t\t\trv += [ (\" \" * pad) + \")\" ]\n\t\t\treturn \"\\n\".join(rv)\n\t\telif self.y:\n\t\t\treturn self.COMMAND + \"(\" + (\", \".join([ x.to_string() for x in (self.x, self.y) ])) + \")\"\n\t\telse:\n\t\t\treturn self.COMMAND + \"(\" + self.x.to_string() + \")\"\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<%s %s >\" % (self.COMMAND, \", \".join([ str(x) for x in self.actions ]), )\n\n\t__repr__ = __str__\n\n\nclass RelXYAction(XYAction):\n\t\"\"\"\n\tXYAction with center positioned wherever finger touched first.\n\tSee https://github.com/kozec/sc-controller/issues/390\n\t\"\"\"\n\tCOMMAND = \"relXY\"\n\t\n\tdef __init__(self, *a, **b):\n\t\tXYAction.__init__(self, *a, **b)\n\t\tself.origin_x, self.origin_y = 0, 0\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Joystick Camera\")\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif what in (LEFT, RIGHT, CPAD):\n\t\t\tif not mapper.is_touched(what):\n\t\t\t\treturn XYAction.whole(self, mapper, 0, 0, what)\n\t\t\telif not mapper.was_touched(what):\n\t\t\t\tself.origin_x, self.origin_y = x, y\n\t\t\tx -= self.origin_x\n\t\t\ty -= self.origin_y\n\t\t\treturn XYAction.whole(self, mapper, x, y, what)\n\t\tXYAction.whole(self, mapper, x, y, what)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn (XYAction.get_compatible_modifiers(self) & ~Action.MOD_BALL)\n\n\nclass TriggerAction(Action, HapticEnabledAction):\n\t\"\"\"\n\tUsed for sticks and pads when actions for X and Y axis are different.\n\t\"\"\"\n\tCOMMAND = \"trigger\"\n\tPROFILE_KEYS = \"levels\",\n\tPROFILE_KEY_PRIORITY = -5\n\t\n\tdef __init__(self, press_level, *params):\n\t\tAction.__init__(self, press_level, *params)\n\t\tHapticEnabledAction.__init__(self)\n\t\tself.press_level = int(press_level)\n\t\tif len(params) == 1:\n\t\t\tself.release_level = press_level\n\t\t\tself.action = params[0]\n\t\telif len(params) == 2:\n\t\t\tself.release_level = params[0]\n\t\t\tself.action = params[1]\n\t\telse:\n\t\t\traise TypeError(\"Invalid number of parameters\")\n\t\tself.pressed = False\n\t\t# Having AxisAction as child of TriggerAction is special case,\n\t\t# child action recieves trigger events instead of button presses\n\t\t# and button_releases.\n\t\tself.child_is_axis = isinstance(self.action.strip(), AxisAction)\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\t\"\"\" Called when decoding profile from json \"\"\"\n\t\tpress_level, release_level = data[TriggerAction.PROFILE_KEYS[0]]\n\t\treturn TriggerAction(press_level, release_level, a)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_FEEDBACK\n\t\n\t\n\tdef compress(self):\n\t\tself.action = self.action.compress()\n\t\treturn self\n\t\n\t\n\tdef _press(self, mapper):\n\t\t\"\"\" Called when trigger level enters active zone \"\"\"\n\t\tself.pressed = True\n\t\tif self.haptic:\n\t\t\tmapper.send_feedback(self.haptic)\n\t\tif not self.child_is_axis:\n\t\t\tself.action.button_press(mapper)\n\t\n\t\n\tdef _release(self, mapper, old_position):\n\t\t\"\"\" Called when trigger level leaves active zone \"\"\"\n\t\tself.pressed = False\n\t\tif self.child_is_axis:\n\t\t\tself.action.trigger(mapper, 0, old_position)\n\t\telse:\n\t\t\tself.action.button_release(mapper)\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\t# There are 3 modes that TriggerAction can work in\n\t\tif self.release_level > self.press_level:\n\t\t\t# Mode 1, action is 'pressed' if current level is\n\t\t\t# between press_level and release_level.\n\t\t\tif not self.pressed and position >= self.press_level and old_position < self.press_level:\n\t\t\t\tself._press(mapper)\n\t\t\telif self.pressed and position > self.release_level and old_position <= self.release_level:\n\t\t\t\tself._release(mapper, old_position)\n\t\t\telif self.pressed and position < self.press_level and old_position >= self.press_level:\n\t\t\t\tself._release(mapper, old_position)\n\t\tif self.release_level == self.press_level:\n\t\t\t# Mode 2, there is only press_level and action is 'pressed'\n\t\t\t# while current level is above it.\n\t\t\tif not self.pressed and position >= self.press_level and old_position < self.press_level:\n\t\t\t\tself._press(mapper)\n\t\t\telif self.pressed and position < self.press_level and old_position >= self.press_level:\n\t\t\t\tself._release(mapper, old_position)\n\t\tif self.release_level < self.press_level:\n\t\t\t# Mode 3, action is 'pressed' if current level is above 'press_level'\n\t\t\t# and then released when it returns beyond 'release_level'.\n\t\t\tif not self.pressed and position >= self.press_level and old_position < self.press_level:\n\t\t\t\tself._press(mapper)\n\t\t\telif self.pressed and position < self.release_level and old_position >= self.release_level:\n\t\t\t\tself._release(mapper, old_position)\n\t\tif self.child_is_axis and self.pressed:\n\t\t\tself.action.trigger(mapper, position, old_position)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn self.action.describe(context)\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Trigger %s-%s %s >\" % (self.press_level, self.release_level, self.action)\n\t\n\t__repr__ = __str__\n\nclass HipfireAction(Action, HapticEnabledAction):\n\t\"\"\"\n\tHip fire style trigger setting the two ranges for two different actions\n\tallowing activating the fully pressed action without activating partially pressed one\n\t\"\"\"\n\n\tCOMMAND = \"hipfire\"\n\tPROFILE_KEYS = \"levels\",\n\tDEFAULT_TIMEOUT = 0.15\n\tDEFAULT_PARTIALPRESS_LEVEL = 50\n\tDEFAULT_FULLPRESS_LEVEL = 254\n\tDEFAULT_MODE = HIPFIRE_NORMAL\n\tTIMEOUT_KEY = \"time\"\n\tPROFILE_KEY_PRIORITY = -5\n\t\n\tdef __init__(self, *params):\n\t\tAction.__init__(self, *params)\n\t\tHapticEnabledAction.__init__(self)\n\t\t# set default values in case is not provided\n\t\tself.partialpress_level = HipfireAction.DEFAULT_PARTIALPRESS_LEVEL\n\t\tself.fullpress_level = HipfireAction.DEFAULT_FULLPRESS_LEVEL\n\t\tself.mode = HipfireAction.DEFAULT_MODE\n\t\tself.timeout = HipfireAction.DEFAULT_TIMEOUT\n\n\t\tif len(params) >= 2:\n\t\t\tif type(params[0]) in (int, float):\n\t\t\t\tself.partialpress_level = int(params[0])\n\t\t\t\tif type(params[1]) in (int, float):\n\t\t\t\t\tself.fullpress_level = int(params[1])\n\t\t\t\t\tparams = params[2:]\n\t\t\t\telse:\n\t\t\t\t\tparams = params[1:]\n\n\t\t\tself.partialpress_action = params[0]\n\t\t\tself.fullpress_action = params[1]\n\t\t\tif len(params) >= 3:\n\t\t\t\tself.mode = params[2]\n\t\t\tif len(params) == 4:\n\t\t\t\tself.timeout = params[3]\n\t\telse:\n\t\t\traise TypeError(\"Invalid number of parameters\")\n\n\t\tif self.mode not in (HIPFIRE_NORMAL, HIPFIRE_EXCLUSIVE, HIPFIRE_SENSIBLE):\n\t\t\traise ValueError(\"Invalid hipfire mode\")\n\t\tself.partialpress_active = False\n\t\tself.range = \"None\"\n\t\tself.waiting_task = None\n\t\tself.sensible_state = \"READY\"\n\t\tself._partialpress_level = self.partialpress_level\n\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\targs = [ parser.from_json_data(data[HipfireAction.COMMAND]), a ]\n\t\ta = HipfireAction(*args)\n\t\tif HipfireAction.TIMEOUT_KEY in data:\n\t\t\ta.timeout = data[HipfireAction.TIMEOUT_KEY]\n\t\treturn a\n\n\t\t\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_FEEDBACK\n\t\n\t\n\tdef compress(self):\n\t\tself.partialpress_action = self.partialpress_action.compress()\n\t\tself.fullpress_action = self.fullpress_action.compress()\n\t\treturn self\n\t\n\tdef on_timeout(self, mapper, *a):\n\t\tif self.waiting_task:\n\t\t\tself.waiting_task = None\n\t\t\tif self.range == \"PARTIALPRESS\":\n\t\t\t\t# Timeouted while inside partial press range\n\t\t\t\tif self.haptic:\n\t\t\t\t\tmapper.send_feedback(self.haptic)\n\t\t\t\tself._partial_press(mapper)\n\t\n\tdef _partial_press(self, mapper):\n\t\tself.partialpress_active = True\n\t\tif self.haptic:\n\t\t\tmapper.send_feedback(self.haptic)\n\t\tself.partialpress_action.button_press(mapper)\n\t\n\t\n\tdef _partial_release(self, mapper):\n\t\tself.partialpress_active = False\n\t\tif self.haptic:\n\t\t\tmapper.send_feedback(self.haptic)\n\t\tself.partialpress_action.button_release(mapper)\n\t\n\tdef _full_press(self, mapper):\n\t\tif self.haptic:\n\t\t\tmapper.send_feedback(self.haptic)\n\t\tself.fullpress_action.button_press(mapper)\n\t\n\tdef _full_release(self, mapper):\n\t\tif self.haptic:\n\t\t\tmapper.send_feedback(self.haptic)\n\t\tself.fullpress_action.button_release(mapper)\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\t# Checks the current position of the trigger and apply the action based on three possible range: [None, PARTIALPRESS, FULLPRESS]\n\n\t\t# Checks full press first to prevent unnecessary conditional evaluation\n\t\tif position >= self.fullpress_level and old_position < self.fullpress_level:\n\t\t\tself.range = \"FULLPRESS\"\n\t\t\t# Entered now in full press range and activate fully pressed action\n\t\t\t\n\t\t\t# Checks if it's in exclusive mode and if partial press is active before activating\n\t\t\tif (self.mode == HIPFIRE_EXCLUSIVE) and self.partialpress_active: return\n\n\t\t\tself._full_press(mapper)\n\t\t\t# Cancel any pending timer to prevent partially pressed action from activating\n\t\t\tif self.waiting_task:\n\t\t\t\tmapper.cancel_task(self.waiting_task)\n\t\t\t\tself.waiting_task = None\n\t\t\n\t\telif position < self.fullpress_level and old_position >= self.fullpress_level:\n\t\t\tself.range = \"PARTIALPRESS\"\n\t\t\t# left the full press range and released the fully pressed action\n\t\t\tself._full_release(mapper)\t\n\t\t\n\t\telif position >= self.partialpress_level:\n\t\t\tself.range = \"PARTIALPRESS\"\n\t\t\t# Entered now in partial press range and should start the timer\n\t\t\t# normal behavior. without the sensible trigger \n\t\t\tif old_position < self.partialpress_level:\n\t\t\t\t# Cancels previous timer\n\t\t\t\tif self.waiting_task:\n\t\t\t\t\tmapper.cancel_task(self.waiting_task)\n\t\t\t\t\tself.waiting_task = None\n\t\t\t\t# Start the timer to execute the action if the full press range is not reached before timeout\n\t\t\t\tself.waiting_task = mapper.schedule(self.timeout, self.on_timeout)\n\n\t\t\t# Spliting conditional for treating the sensible mode\n\t\t\t# in this mode after reaching the partial press level, releasing the trigger a little will cause it to deactivate the action\n\t\t\t# allowing fast repeatly presses without needing to release the trigger the all the way back\n\t\t\tif self.mode == HIPFIRE_SENSIBLE:\n\t\t\t\tif position > old_position and self.sensible_state == \"READY\":\n\t\t\t\t\t## Create the new partial press point while pressing the trigger and the trigger is in its initial state\n\t\t\t\t\tself.new_partialpress_level = max(old_position, self.new_partialpress_level) - 45 # using a arbitrary value just for tests\n\t\t\t\t\n\t\t\t\tif self.sensible_state != \"RELEASED\" and position < self.new_partialpress_level and old_position >= self.new_partialpress_level:\n\t\t\t\t\t# Leaving the sensible range deactivating the action if it's already activated otherwise just schedule a short press\n\t\t\t\t\tself.sensible_state = \"RELEASED\"\n\t\t\t\t\tif self.waiting_task:\n\t\t\t\t\t\tmapper.cancel_task(self.waiting_task)\n\t\t\t\t\t\tself.waiting_task = None\n\t\t\t\t\t\tself._partial_press(mapper)\n\t\t\t\t\t\tmapper.schedule(0.02, self._partial_release)\n\t\t\t\t\telse:\n\t\t\t\t\t\tself._partial_release(mapper)\n\n\t\t\t\telif self.sensible_state != \"PRESSED\" and position >= self.new_partialpress_level and old_position < self.new_partialpress_level:\n\t\t\t\t\t# Activate the action (schedule) again without needed to release the action until the partial press level\n\t\t\t\t\tself.sensible_state = \"PRESSED\"\n\t\t\t\t\tif self.waiting_task:\n\t\t\t\t\t\tmapper.cancel_task(self.waiting_task)\n\t\t\t\t\t\tself.waiting_task = None\n\t\t\t\t\t#start the timer to execute the action if the full press is not reached before timeout\n\t\t\t\t\tself.waiting_task = mapper.schedule(self.timeout, self.on_timeout)\n\n\t\t## Normal release of the partial press, deactivates the partially pressed action if it was active or if the time was still going schedule a short press \n\t\telif position < self.partialpress_level and old_position >= self.partialpress_level:\n\t\t\tself.range = \"NONE\"\n\t\t\tif self.waiting_task:\n\t\t\t\tmapper.cancel_task(self.waiting_task)\n\t\t\t\tself.waiting_task = None\n\t\t\t\tself._partial_press(mapper)\n\t\t\t\tmapper.schedule(0.02, self._partial_release)\n\t\t\telse:\n\t\t\t\tself._partial_release(mapper)\n\n\t\t\t# reset the sensible state\n\t\t\tself.sensible_state = \"READY\"\n\t\t\tself.new_partialpress_level = self.partialpress_level\n\n\t\n\tdef describe(self, context):\n\t\tl = [ ]\n\t\tif self.partialpress_action:\n\t\t\tl += [ self.partialpress_action ]\n\t\tif self.fullpress_action:\n\t\t\tl += [ self.fullpress_action ]\n\t\treturn \"\\n\".join([ x.describe(context) for x in l ])\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Hipfire %s-%s %s %s %s >\" % (self.partialpress_level, self.fullpress_level, self.partialpress_action, self.fullpress_action, self.mode)\n\t\n\t__repr__ = __str__\n\n\nclass NoAction(Action):\n\t\"\"\"\n\tParsed from None.\n\tSingleton, treated as False in boolean ops.\n\t\"\"\"\n\tCOMMAND = \"None\"\n\tALIASES = (None, )\n\t_singleton = None\n\t\n\tdef __new__(cls):\n\t\tif cls._singleton is None:\n\t\t\tcls._singleton = object.__new__(cls)\n\t\treturn cls._singleton\n\t\n\t\n\tdef __bool__(self):\n\t\treturn False\n\n\t__nonzero__ = __bool__\n\t\n\t\n\tdef encode(self):\n\t\treturn { }\n\t\n\t\n\tdef button_press(self, *a):\n\t\tpass\n\t\n\tdef button_release(self, *a):\n\t\tpass\n\t\n\tdef axis(self, *a):\n\t\tpass\n\t\n\tdef whole(self, *a):\n\t\tpass\n\t\n\tdef trigger(self, *a):\n\t\tpass\n\t\n\t\n\tdef describe(self, context):\n\t\treturn _(\"(not set)\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"None\"\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"NoAction\"\n\n\t__repr__ = __str__\n\n\ndef strip_none(*lst):\n\t\"\"\" Returns lst without trailing None's and NoActions \"\"\"\n\twhile len(lst) and (lst[-1] is None or isinstance(lst[-1], NoAction)):\n\t\tlst = lst[0:-1]\n\treturn lst\n\n\n# Register actions from current module\nAction.register_all(sys.modules[__name__])\n\n# Import important action modules and register actions from them.\n# (needs to be done at end as all this imports Action class from this module)\nimport scc.macros\nAction.register_all(sys.modules['scc.macros'])\nimport scc.modifiers\nAction.register_all(sys.modules['scc.modifiers'])\nimport scc.special_actions\nAction.register_all(sys.modules['scc.special_actions'])\n"
  },
  {
    "path": "scc/aliases.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Aliases\n\nThis module generates Keys.BTN_x and Axes.AXIS_x aliases when imported\n\"\"\"\n\nfrom scc.uinput import Axes, Keys\n\nALL_BUTTONS = ( Keys.BTN_START, Keys.BTN_MODE, Keys.BTN_SELECT, Keys.BTN_A,\n\tKeys.BTN_B, Keys.BTN_X, Keys.BTN_Y, Keys.BTN_TL, Keys.BTN_TR,\n\tKeys.BTN_THUMBL, Keys.BTN_THUMBR, Keys.BTN_WHEEL, Keys.BTN_GEAR_DOWN,\n\tKeys.BTN_GEAR_UP, Keys.KEY_OK, Keys.KEY_SELECT, Keys.KEY_GOTO,\n\tKeys.KEY_CLEAR, Keys.KEY_OPTION, Keys.KEY_INFO, Keys.KEY_TIME,\n\tKeys.KEY_VENDOR, Keys.KEY_ARCHIVE, Keys.KEY_PROGRAM, Keys.KEY_CHANNEL,\n\tKeys.KEY_FAVORITES, Keys.KEY_EPG )\n\nALL_AXES = ( Axes.ABS_X, Axes.ABS_Y, Axes.ABS_RX, Axes.ABS_RY, Axes.ABS_Z,\n\tAxes.ABS_RZ, Axes.ABS_HAT0X, Axes.ABS_HAT0Y, Axes.ABS_HAT1X, Axes.ABS_HAT1Y,\n\tAxes.ABS_HAT2X, Axes.ABS_HAT2Y, Axes.ABS_HAT3X, Axes.ABS_HAT3Y,\n\tAxes.ABS_PRESSURE, Axes.ABS_DISTANCE, Axes.ABS_TILT_X, Axes.ABS_TILT_Y,\n\tAxes.ABS_TOOL_WIDTH, Axes.ABS_VOLUME, Axes.ABS_MISC )\n\nfor i in range(0, len(ALL_BUTTONS)):\n\tsetattr(Keys, \"BTN%i\" % (i,), ALL_BUTTONS[i])\n\nfor i in range(0, len(ALL_AXES)):\n\tsetattr(Axes, \"ABS%i\" % (i,), ALL_AXES[i])\n"
  },
  {
    "path": "scc/c_branch.h",
    "content": "/**\n * Glue between code from future and current stuff in python\n */\n#pragma once\n#include \"Python.h\"\n#include <stdbool.h>\n#include <stdint.h>\n\n#define LERROR(fmt, ...) do { fprintf(stderr, \"E \" LOG_TAG \" \" fmt, ##__VA_ARGS__); fprintf(stderr, \"\\n\"); fflush(stderr); } while(0)\n#define WARN(fmt, ...) do { fprintf(stderr, \"W \" LOG_TAG \" \" fmt, ##__VA_ARGS__); fprintf(stderr, \"\\n\"); fflush(stderr); } while(0)\n#define DEBUG(fmt, ...) do { fprintf(stdout, \"D \" LOG_TAG \" \" fmt, ##__VA_ARGS__); fprintf(stdout, \"\\n\"); fflush(stdout); } while(0)\n#define LOG(fmt, ...) do { fprintf(stdout, \"L \" LOG_TAG \" \" fmt, ##__VA_ARGS__); fprintf(stdout, \"\\n\"); fflush(stdout); } while(0)\n\ntypedef uint64_t monotime_t;\n\n/** Returns current value of CLOCK_MONOTONIC converted to number of milliseconds */\ninline static uint64_t mono_time_ms() {\n\tstatic struct timespec t;\n\tclock_gettime(CLOCK_MONOTONIC, &t);\n\treturn t.tv_sec * 1000 + (t.tv_nsec / 10e5);\n}\n\n"
  },
  {
    "path": "scc/cemuhook_server.c",
    "content": "/**\n * SC-Controller - Daemon - CemuHookUDP motion provider\n *\n * Accepts all connections from clients and sends data captured\n * by 'cemuhook' actions to them.\n *\n * This code is also used as library in Python code in master branch.\n */\n\n#define LOG_TAG \"CemuHook\"\n#ifndef PYTHON\n\t#include \"scc/utils/logging.h\"\n\t#include \"scc/utils/strbuilder.h\"\n\t#include \"scc/utils/iterable.h\"\n\t#include \"scc/utils/assert.h\"\n\t#include \"scc/utils/math.h\"\n\t#include \"scc/utils/list.h\"\n\t#include \"scc/tools.h\"\n\t#include \"daemon.h\"\n#else\t// PYTHON\n\t#undef LOG_TAG\n\t#define LOG_TAG \"CemuHook     \"\n\t#include \"c_branch.h\"\n#endif\t// PYTHON\n#ifdef _WIN32\n\t#error \"Implement me!\"\n#else\t// _WIN32\n\t#include <netinet/in.h>\n\t#include <sys/socket.h>\n\t#include <arpa/inet.h>\n\t#include <sys/types.h>\n\t#define SOCKETERROR  \": %s\", strerror(errno)\n#endif\t// _WIN32\n#include <stdbool.h>\n#include <stdint.h>\n#include <string.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <errno.h>\n#include <zlib.h>\n\n#define BUFFER_SIZE\t\t\t\t\t1024\n#define MAX_PROTO_VERSION\t\t\t1001\n#define CLIENT_TIMEOUT\t\t\t\t(5 * 1000)\n#define CEMUHOOK_MODULE_VERSION\t\t1\ntypedef struct CEHClient {\n\tstruct sockaddr_in\taddress;\n\tmonotime_t\t\t\tlast_seen;\n\tuint32_t\t\t\tnext_packet_no;\n} CEHClient;\nstatic uint32_t next_id = 1;\n#ifndef PYTHON\nstatic LIST_TYPE(CEHClient) clients;\nstatic int sock;\n#else\n#define CLIENT_LIMIT\t\t\t\t10\nstatic CEHClient clients[CLIENT_LIMIT];\n#endif\n\n\ntypedef enum {\n\tDSUC_VERSIONREQ =\t0x100000,\n\tDSUS_VERSIONRSP =\t0x100000,\n\tDSUC_LISTPORTS =\t0x100001,\n\tDSUS_PORTINFO =\t\t0x100001,\n\tDSUC_PADDATAREQ =\t0x100002,\n\tDSUS_PADDATARSP =\t0x100002,\n} MessageType;\n\nstruct __attribute__((packed)) PortInfo {\n\tuint8_t\t\t\tpad_id;\n\tuint8_t\t\t\tstate;\n\tuint8_t\t\t\tmodel;\n\tuint8_t\t\t\tconnection_type;\n\tuint8_t\t\t\tmac[6];\n\tuint8_t\t\t\tbattery;\n\tuint8_t\t\t\tactive;\n};\n\nstruct __attribute__((packed)) Message {\n\tchar\t\t\t\t\t\theader[4];\t\t\t// 0B\n\tuint16_t\t\t\t\t\tprotocol_version;\t// 4B\n\tuint16_t\t\t\t\t\tpacket_size;\t\t// 6B\n\tuint32_t\t\t\t\t\tcrc;\t\t\t\t// 8B\n\tuint32_t\t\t\t\t\tmsg_id;\t\t\t\t// 12B\n\tMessageType\t\t\t\t\tmessage_type;\t\t// 16B\n\tunion {\t\t\t\t\t\t\t\t\t\t\t// 20B\n\t\tstruct {\n\t\t\tint32_t\t\t\t\tcount;\n\t\t\tuint8_t\t\t\t\tids[4];\n\t\t} list_ports;\n\t\tstruct {\n\t\t\tuint8_t\t\t\t\tflags;\n\t\t\tuint8_t\t\t\t\tid;\n\t\t\tuint8_t\t\t\t\tmac[6];\n\t\t} pad_data_req;\n\t\tstruct {\n\t\t\tuint16_t\t\t\tmax_version;\n\t\t\tuint16_t\t\t\tmin_version;\n\t\t} version_info;\n\t\tstruct PortInfo\t\t\tport_info;\t\t\t// size = 12B\n\t\tstruct {\n\t\t\tstruct PortInfo\t\tpad_info;\n\t\t\tuint32_t\t\t\tpacket_number;\n\t\t\tuint8_t\t\t\t\tbuttons1;\n\t\t\tuint8_t\t\t\t\tbuttons2;\n\t\t\tuint8_t\t\t\t\tbutton_ps;\n\t\t\tuint8_t\t\t\t\tbutton_touch;\n\t\t\tuint8_t\t\t\t\tsticks[4];\t\t\t// LX, LY, RX, RY\n\t\t\tuint8_t\t\t\t\tanalog_buttons[12];\n\t\t\tstruct {\n\t\t\t\tuint8_t\t\t\tactive;\n\t\t\t\tuint8_t\t\t\tid;\n\t\t\t\tuint16_t\t\tx;\n\t\t\t\tuint16_t\t\ty;\n\t\t\t} touch_point[2];\n\t\t\tuint64_t\t\t\tmotion_timestamp;\n\t\t\tstruct {\n\t\t\t\tfloat\t\t\tx;\n\t\t\t\tfloat\t\t\ty;\n\t\t\t\tfloat\t\t\tz;\n\t\t\t\tfloat\t\t\tpitch;\n\t\t\t\tfloat\t\t\tyaw;\n\t\t\t\tfloat\t\t\troll;\n\t\t\t} accel;\n\t\t} pad_data;\n\t};\n};\n\nstatic void send_msg(int fd, struct sockaddr_in* target, struct Message* msg, MessageType type, uint16_t payload_size) {\n\tsize_t size = 20 + payload_size;\n\tmemcpy(msg->header, \"DSUS\", 4);\n\tmsg->protocol_version = MAX_PROTO_VERSION;\n\tmsg->packet_size = 4 + payload_size;\n\tmsg->message_type = type;\n\tmsg->msg_id = next_id ++;\n\tmsg->crc = 0;\n\t\n\tuLong crc = crc32(0, (const Bytef*)msg, size);\n\tmsg->crc = crc;\n\t\n\tssize_t r = sendto(fd, (char*)msg, size, 0, (struct sockaddr*)target, sizeof(struct sockaddr_in));\n\tif (r < 0) LERROR(\"sendto failed: \" SOCKETERROR);\n}\n\nstatic void fill_port_info(struct PortInfo* pi, uint16_t id, uint8_t active) {\n\tstatic uint8_t mac[6] = { 0x05, 0x0C, 0x0C, 0x00, 0x00, 0x01 };\n\tpi->pad_id = id;\n\tpi->state = (id == 0) ? 0x02 : 0x00;\t// Connected : Disconnected\n\tpi->connection_type = 0x01;\t\t\t\t// Usb\n\tpi->model = 0x02;\t\t\t\t\t\t// DS4\n\tpi->battery = 0x04;\t\t\t\t\t\t// High\n\tpi->active = active;\n\tmemcpy(pi->mac, mac, 5);\n\tpi->mac[5] = 1 + id;\n}\n\nstatic void send_gyro_data(int fd, CEHClient* target, uint16_t id, float data[6], uint64_t timestamp) {\n\tstruct Message out;\n\tmemset(&out, 0, sizeof(struct Message));\n\tfill_port_info(&out.pad_data.pad_info, id, 1);\n\tmemcpy(&out.pad_data.accel, data, sizeof(float) * 6);\n\tout.pad_data.motion_timestamp = timestamp * 1000;\n\tout.pad_data.packet_number = target->next_packet_no ++;\n\tsend_msg(fd, &target->address, &out, DSUS_PADDATARSP, 80);\n\t// DEBUG(\"Sent data to (0x%x)\", target->address.sin_port);\n}\n\nstatic void parse_message(int fd, const char* buffer, size_t size, struct sockaddr_in* source) {\n\tstruct Message* msg = (struct Message*)&buffer[0];\n\tstruct Message out;\n\tint i, x;\n\tif ((size < 20) || (buffer[0] != 'D') || (buffer[1]!='S') || (buffer[2] != 'U') || (buffer[3] != 'C')) {\n\t\tWARN(\"Recieved invalid message: Invalid header\");\n\t\treturn;\n\t}\n\tif (msg->protocol_version > MAX_PROTO_VERSION) {\n\t\tWARN(\"Recieved invalid message: Unsupported version\");\n\t\treturn;\n\t}\n\tif (size < msg->packet_size + 20 - 4) {\n\t\tWARN(\"Recieved invalid message: Invalid size (expected %i, got %zu)\", msg->packet_size + 20 - 4, size);\n\t\treturn;\n\t}\n\t\n\tswitch (msg->message_type) {\n\tcase DSUC_VERSIONREQ:\n\t\tout.version_info.min_version = 0;\n\t\tout.version_info.max_version = MAX_PROTO_VERSION;\n\t\tsend_msg(fd, source, &out, DSUS_VERSIONRSP, 4);\n\t\tbreak;\n\tcase DSUC_LISTPORTS:\n\t\tif ((msg->list_ports.count > 0) && (msg->list_ports.count <= 4)) {\n\t\t\tfor (i=0; i<msg->list_ports.count; i++) {\n\t\t\t\tfill_port_info(&out.port_info, msg->list_ports.ids[i], 0);\n\t\t\t\tsend_msg(fd, source, &out, DSUS_PORTINFO, 12);\n\t\t\t}\n\t\t}\n\t\tbreak;\n\tcase DSUC_PADDATAREQ: {\n\t\tif (!((msg->pad_data_req.flags == 0)\n\t\t\t|| (((msg->pad_data_req.flags & 0x01) != 0) && (msg->pad_data_req.id == 0)))) {\n\t\t\t\t// Only querying by ID and querying for 1st controller is supported\n\t\t\t\tWARN(\"Refusing request: flags=%x id=%x mac=%x:%x:%x:%x:%x:%x\",\n\t\t\t\t\t\tmsg->pad_data_req.flags, msg->pad_data_req.id,\n\t\t\t\t\t\tmsg->pad_data_req.mac[0],\n\t\t\t\t\t\tmsg->pad_data_req.mac[1],\n\t\t\t\t\t\tmsg->pad_data_req.mac[2],\n\t\t\t\t\t\tmsg->pad_data_req.mac[3],\n\t\t\t\t\t\tmsg->pad_data_req.mac[4],\n\t\t\t\t\t\tmsg->pad_data_req.mac[5]\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t}\n\t\tCEHClient* c = NULL;\n#ifdef PYTHON\n\t\tfor (x=0; x<CLIENT_LIMIT; x++) {\n\t\t\tif (clients[x].address.sin_port == 0)\n\t\t\t\tcontinue;\n\t\t\tCEHClient* i = &clients[x];\n#else\n\t\tFOREACH_IN(CEHClient*, i, clients) {\n#endif\n\t\t\tif (i->address.sin_port == source->sin_port) {\n\t\t\t\t// Server listens only on localhost, so it should be safe to assume IP matches\n\t\t\t\tc = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (c == NULL) {\n#ifdef PYTHON\n\t\t\tfor (x=0; x<CLIENT_LIMIT; x++) {\n\t\t\t\tif (clients[x].address.sin_port == 0) {\n\t\t\t\t\tc = &clients[x];\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (c == NULL) {\n\t\t\t\tWARN(\"Client limit reached\");\n\t\t\t\tbreak;\n\t\t\t}\n#else\n\t\t\tc = malloc(sizeof(CEHClient));\n\t\t\tif ((c == NULL) || (!list_allocate(clients, 1))) {\n\t\t\t\tWARN(\"Out of memory\");\n\t\t\t\tfree(c);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tlist_add(clients, c);\n#endif\n\t\t\tmemcpy(&c->address, source, sizeof(struct sockaddr_in));\n\t\t\tc->next_packet_no = mono_time_ms() & 0xFFFFFFFF;\n\t\t\tDEBUG(\"New client (0x%x) added\", c->address.sin_port);\n\t\t}\n\t\tc->last_seen = mono_time_ms();\n\t\tbreak;\n\t}\n\tdefault:\n\t\t// WARN(\"Recieved invalid message: Unknown message type\");\n\t\treturn;\n\t}\n}\n\n#ifdef PYTHON\nbool cemuhook_feed(int fd, int index, float data[6]) {\n#else\nbool sccd_cemuhook_feed(int index, float data[6]) {\n\tconst int fd = sock;\n#endif\n\tmonotime_t t = mono_time_ms();\n#ifdef PYTHON\n\tint x;\n\tfor (x=0; x<CLIENT_LIMIT; x++) {\n\t\tCEHClient* c = &clients[x];\n\t\tif (c->address.sin_port == 0)\n\t\t\tcontinue;\n#else\n\tListIterator it = iter_get(clients);\n\tif (it == NULL) return false;\t// OOM\n\twhile (iter_has_next(it)) {\n\t\tCEHClient* c = iter_next(it);\n#endif\n\t\tif ((t > c->last_seen + CLIENT_TIMEOUT) || (t < c->last_seen)) {\n\t\t\tDEBUG(\"Dropping client (0x%x)\", c->address.sin_port);\n\t\t\tc->address.sin_port = 0;\n#ifndef PYTHON\n\t\t\titer_remove(it);\n#endif\n\t\t} else {\n\t\t\tsend_gyro_data(fd, c, 0, data, (uint64_t)t);\n\t\t}\n\t}\n#ifndef PYTHON\n\titer_free(it);\n#endif\n\treturn true;\n}\n\n#ifdef PYTHON\n\nconst int cemuhook_module_version(void) {\n\treturn CEMUHOOK_MODULE_VERSION;\n}\n\nvoid cemuhook_data_recieved(int fd, int port, const char* buffer, size_t size) {\n\tstruct sockaddr_in source;\n\tsource.sin_family = AF_INET;\n\tsource.sin_addr.s_addr = inet_addr(\"127.0.0.1\");\n\tsource.sin_port = htons(port);\n\t\n\tparse_message(fd, buffer, size, &source);\n}\n\nbool cemuhook_socket_enable() {\n\tint i;\n\tfor (i=0; i<CLIENT_LIMIT; i++)\n\t\tclients[i].address.sin_port = 0;\n\t// listening is done in python\n\treturn true;\n}\n\n#else\n\nstatic void on_data_recieved(Daemon* d, int fd, void* userdata) {\n\tchar buffer[BUFFER_SIZE];\n\tstruct sockaddr_in source;\n\tsocklen_t len = sizeof(struct sockaddr_in);\n\tssize_t n = recvfrom(fd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&source, &len);\n\t\n\tif (n < 0) {\n\t\tLERROR(\"recvfrom: \" SOCKETERROR);\n\t\treturn;\n\t}\n\t\n\tparse_message(sock, buffer, n, &source);\n}\n\n\nbool sccd_cemuhook_socket_enable() {\n\tclients = list_new(CEHClient, 4);\n\tif (clients == NULL)\n\t\t// This may be enabled at random time, so I can't just crash here\n\t\treturn false;\n\t\n\tstruct sockaddr_in server_addr;\n\tmemset(&server_addr, 0, sizeof(struct sockaddr_in));\n\tserver_addr.sin_family = AF_INET;\n\tserver_addr.sin_addr.s_addr = inet_addr(\"127.0.0.1\");\n\tif (const char* custom_port = getenv(\"SCC_SERVER_PORT\")) {\n\t\tserver_addr.sin_port = atoi(custom_port);\n\t} else {\n\t\tserver_addr.sin_port = htons(26760);\n\t}\n\n#ifdef _WIN32\n\tWSADATA wsaData;\n\tint err = WSAStartup(MAKEWORD(2, 2), &wsaData);\n\tif (err != 0) {\n\t\tLERROR(\"Failed to initialize Winsock2: error %i\", err);\n\t\treturn false;\n\t}\n#endif\n\tsock = socket(AF_INET, SOCK_DGRAM, 0);\n\tif (sock < 0) {\n\t\tLERROR(\"Failed to open control socket\" SOCKETERROR);\n\t\treturn false;\n\t}\n\t\n\t/*\n#ifndef _WIN32\n\tif (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)) < 0) {\n#else\n\tif (setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, &(char){ 1 }, sizeof(char)) < 0) {\n#endif\n\t\t// stupid, but not fatal\n\t\tWARN(\"setsockopt failed\" SOCKETERROR);\n\t}\n\t*/\n\tif (bind(sock, (const struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) < 0) {\n\t\tLERROR(\"Bind failed\" SOCKETERROR);\n\t\treturn false;\n\t}\n\t\n\tif (!sccd_poller_add(sock, &on_data_recieved, NULL)) {\n\t\tLERROR(\"sccd_poller_add failed to add listening socket\");\n\t\treturn false;\n\t}\n\t\n\tLOG(\"Created CemuHookUDP Motion Provider\");\n\treturn true;\n}\n\n\n__attribute__((constructor)) void check_stuff() {\n\tASSERT(sizeof(MessageType) == sizeof(uint32_t));\n\tASSERT(sizeof(float) == 4);\n}\n\n#endif\n\n"
  },
  {
    "path": "scc/cemuhook_server.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Daemon - CemuHookUDP motion provider\n\nAccepts all connections from clients and sends data captured\nby 'cemuhook' actions to them.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import find_library\nfrom scc.lib.enum import IntEnum\nfrom ctypes import c_uint32, c_int, c_bool, c_char_p, c_size_t, c_float\nfrom ctypes import create_string_buffer\nimport logging, os, socket\nfrom threading import Thread\nfrom time import sleep\nfrom datetime import datetime, timedelta\nlog = logging.getLogger(\"CemuHook\")\n\nBUFFER_SIZE = 1024\nPORT = 26760\n\n\nclass MessageType(IntEnum):\n\tDSUC_VERSIONREQ =\t0x100000\n\tDSUS_VERSIONRSP =\t0x100000\n\tDSUC_LISTPORTS =\t0x100001\n\tDSUS_PORTINFO =\t\t0x100001\n\tDSUC_PADDATAREQ =\t0x100002\n\tDSUS_PADDATARSP =\t0x100002\n\n\nclass CemuhookServer:\n\tC_DATA_T = c_float * 6\n\ttimeout = timedelta(seconds=1)\n\t\n\tdef __init__(self, daemon):\n\t\tself._lib = find_library('libcemuhook')\n\t\tself._lib.cemuhook_data_recieved.argtypes = [ c_int, c_int, c_char_p, c_size_t ]\n\t\tself._lib.cemuhook_data_recieved.restype = None\n\t\tself._lib.cemuhook_feed.argtypes = [ c_int, c_int, CemuhookServer.C_DATA_T ]\n\t\tself._lib.cemuhook_feed.restype = None\n\t\tself._lib.cemuhook_socket_enable.argtypes = []\n\t\tself._lib.cemuhook_socket_enable.restype = c_bool\n\t\tself.last_signal = datetime.now()\n\t\t\n\t\tif not self._lib.cemuhook_socket_enable():\n\t\t\traise OSError(\"cemuhook_socket_enable failed\")\n\t\t\n\t\tself.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n\t\tself.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n\t\t\n\t\tpoller = daemon.get_poller()\n\t\tdaemon.poller.register(self.socket.fileno(), poller.POLLIN, self.on_data_recieved)\n\t\t\n\t\tserver_port = os.getenv('SCC_SERVER_PORT') or PORT;\n\t\tself.socket.bind(('127.0.0.1', server_port))\n\t\tlog.info(\"Created CemuHookUDP Motion Provider\")\n\n\t\tThread(target=self._keepalive).start()\n\n\t\n\tdef _keepalive(self):\n\t\twhile True:\n\t\t\tif datetime.now() - self.last_signal >= self.timeout:\n\t\t\t\t# feed all zeroes to indicate the gyro has not changed\n\t\t\t\tself.feed((0.0, 0.0, 0.0, 0.0, 0.0, 0.0))\n\t\t\tsleep(1)\n\t\n\tdef on_data_recieved(self, fd, event_type):\n\t\tif fd != self.socket.fileno(): return\n\t\tmessage, (ip, port) = self.socket.recvfrom(BUFFER_SIZE)\n\t\tbuffer = create_string_buffer(BUFFER_SIZE)\n\t\tself._lib.cemuhook_data_recieved(fd, port, message, len(message), buffer)\n\t\n\t\n\tdef feed(self, data):\n\t\tself.last_signal = datetime.now()\n\t\tc_data = CemuhookServer.C_DATA_T()\n\t\t#log.debug(data)\n\t\tc_data[0:6] = data[0:6]\n\t\t#log.debug(list(c_data))\n\t\tself._lib.cemuhook_feed(self.socket.fileno(), 0, c_data)\n\n\n"
  },
  {
    "path": "scc/cheader.py",
    "content": "#!/usr/bin/env python\n\n# The MIT License (MIT)\n#\n# Copyright (c) 2015 Stany MARCEL <stanypub@gmail.com>\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n\nimport ast\nimport os\nimport shlex\nfrom collections import OrderedDict\nimport operator as op\n\nOPERATORS = {\n\tast.Add\t: op.add,\n\tast.Sub\t: op.sub,\n\tast.Mult   : op.mul,\n\tast.Div\t: op.floordiv,\n\tast.Mod\t: op.mod,\n\tast.LShift : op.lshift,\n\tast.RShift : op.rshift,\n\tast.BitOr  : op.or_,\n\tast.BitXor : op.xor,\n\tast.BitAnd : op.and_,\n\tast.Invert : op.invert,\n\tast.Not\t: op.not_,\n\tast.UAdd   : op.pos,\n\tast.USub   : op.neg,\n\tast.And\t: op.and_,\n\tast.Or\t : op.or_,\n\tast.Eq\t : op.eq,\n\tast.NotEq  : op.ne,\n\tast.Lt\t : op.lt,\n\tast.LtE\t: op.le,\n\tast.Gt\t : op.gt,\n\tast.GtE\t: op.ge,\n}\n\ndef eval_expr(expr):\n\n\t\"\"\" Eval and expression inside a #define using a suppart of python grammar \"\"\"\n\n\tdef _eval(node):\n\t\tif isinstance(node, ast.Num):\n\t\t\treturn node.n\n\t\telif isinstance(node, ast.BinOp):\n\t\t\treturn OPERATORS[type(node.op)](_eval(node.left), _eval(node.right))\n\t\telif isinstance(node, ast.UnaryOp):\n\t\t\treturn OPERATORS[type(node.op)](_eval(node.operand))\n\t\telif isinstance(node, ast.BoolOp):\n\t\t\tvalues = [_eval(x) for x in node.values]\n\t\t\treturn OPERATORS[type(node.op)](**values)\n\t\telse:\n\t\t\traise TypeError(node)\n\n\treturn _eval(ast.parse(expr, mode='eval').body)\n\n\ndef defines(base, include):\n\n\t\"\"\" Extract #define from base/include following #includes \"\"\"\n\n\tparsed = set()\n\tfname = os.path.normpath(os.path.abspath(os.path.join(base, include)))\n\tparsed.add(fname)\n\n\tlexer = shlex.shlex(open(fname), posix=True)\n\n\tlexer.whitespace = ' \\t\\r'\n\tlexer.commenters = ''\n\tlexer.quotes = '\"'\n\n\tout = OrderedDict()\n\n\tdef parse_c_comments(lexer, tok, ntok):\n\t\tif tok != '/' or ntok != '*':\n\t\t\treturn False\n\t\tquotes = lexer.quotes\n\t\tlexer.quotes = ''\n\t\twhile True:\n\t\t\ttok = lexer.get_token()\n\t\t\tntok = lexer.get_token()\n\t\t\tif tok == '*' and ntok == '/':\n\t\t\t\tlexer.quotes = quotes\n\t\t\t\tbreak\n\t\t\telse:\n\t\t\t\tlexer.push_token(ntok)\n\t\treturn True\n\n\tdef parse_cpp_comments(lexer, tok, ntok):\n\t\tif tok != '/' or ntok != '/':\n\t\t\treturn False\n\t\tquotes = lexer.quotes\n\t\tlexer.quotes = ''\n\t\twhile True:\n\t\t\ttok = lexer.get_token()\n\t\t\tif tok == '\\n':\n\t\t\t\tlexer.quotes = quotes\n\t\t\t\tlexer.push_token(tok)\n\t\t\t\tbreak\n\t\treturn True\n\n\twhile True:\n\t\ttok = lexer.get_token()\n\t\tif not tok or tok == '':\n\t\t\tbreak\n\t\tntok = lexer.get_token()\n\n\t\tif parse_c_comments(lexer, tok, ntok):\n\t\t\tcontinue\n\t\tif parse_cpp_comments(lexer, tok, ntok):\n\t\t\tcontinue\n\n\t\tif tok != '\\n' or ntok != '#':\n\t\t\tlexer.push_token(ntok)\n\t\t\tcontinue\n\n\t\ttok = lexer.get_token()\n\t\tif tok == 'define':\n\t\t\tname = lexer.get_token()\n\t\t\texpr = ''\n\t\t\twhile True:\n\n\t\t\t\ttok = lexer.get_token()\n\t\t\t\tntok = lexer.get_token()\n\n\t\t\t\tif parse_c_comments(lexer, tok, ntok):\n\t\t\t\t\tcontinue\n\t\t\t\tif parse_cpp_comments(lexer, tok, ntok):\n\t\t\t\t\tcontinue\n\t\t\t\tlexer.push_token(ntok)\n\n\t\t\t\tif not tok or tok == '':\n\t\t\t\t\tbreak\n\t\t\t\tif tok == '\\n':\n\t\t\t\t\tlexer.push_token(tok)\n\t\t\t\t\tbreak\n\n\t\t\t\tif tok in out:\n\t\t\t\t\ttok = str(out[tok])\n\t\t\t\texpr = expr + tok\n\n\t\t\ttry:\n\t\t\t\tval = eval_expr(expr)\n\t\t\t\tout[name] = val\n\t\t\texcept (SyntaxError, TypeError):\n\t\t\t\tpass\n\t\telif tok == 'include':\n\n\t\t\ttok = lexer.get_token()\n\t\t\tif tok == '<':\n\t\t\t\tname = ''\n\t\t\t\twhile True:\n\t\t\t\t\ttok = lexer.get_token()\n\t\t\t\t\tif tok == '>':\n\t\t\t\t\t\tbreak\n\t\t\t\t\tname = name + tok\n\t\t\telse:\n\t\t\t\tname = tok\n\t\t\tfname = os.path.normpath(os.path.abspath(os.path.join(base, name)))\n\t\t\tif os.path.isfile(fname) and not fname in parsed:\n\t\t\t\tparsed.add(fname)\n\t\t\t\tlexer.push_source(open(fname))\n\t\telse:\n\t\t\tlexer.push_token(tok)\n\n\n\treturn out\n\n\nif __name__ == '__main__':\n\timport sys\n\tdefinesDict = defines(sys.argv[1], sys.argv[2])\n\tfor k, v in definesDict.items():\n\t\tprint(\"{}:\\t{}\".format(k, v))\n"
  },
  {
    "path": "scc/config.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Config\n\nHandles loading, storing and querying config file\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.paths import get_config_path\nfrom scc.profile import Encoder\nfrom scc.special_actions import ChangeProfileAction\n\nimport os, json, logging\nlog = logging.getLogger(\"Config\")\n\n\nclass Config(object):\n\tDEFAULTS = {\n\t\t\"autoswitch_osd\":\tTrue,\t# True to show OSD message when profile is autoswitched\n\t\t\"autoswitch\":\t\t[],\t\t# Empty list of conditions\n\t\t\"recent_max\":\t\t10,\t\t# Number of profiles to keep\n\t\t\"recent_profiles\":\t[\t\t# Hard-coded list of profiles from default_profiles/\n\t\t\t# This is actually updated by scc-osd-daemon, as that's\n\t\t\t# only thing actually knowing what to put here.\n\t\t\t\"Desktop\",\n\t\t\t\"XBox Controller with High Precision Camera\",\n\t\t\t\"XBox Controller\"\n\t\t],\n\t\t\"drivers\" : {\t\t\t\t# Map of drivers with values of True, Flase\n\t\t\t\t\t\t\t\t\t# or additional driver config where needed.\n\t\t\t\t\t\t\t\t\t# Anything but False means enabled here.\n\t\t\t\"sc_dongle\": True,\n\t\t\t\"sc_by_cable\": True,\n\t\t\t\"sc_by_bt\": True,\n\t\t\t\"steamdeck\": True,\n\t\t\t\"fake\": False,\t\t\t# Used for developement\n\t\t\t\"hiddrv\": True,\n\t\t\t\"evdevdrv\": True,\n\t\t\t\"ds4drv\": True,\t\t\t# At least one of hiddrv or evdevdrv has to be enabled as well\n\t\t\t\"ds5drv\": True,\n\t\t},\n\t\t\"fix_xinput\" : True,\t\t# If True, attempt is done to deatach emulated controller \n\t\t\t\t\t\t\t\t\t# from 'Virtual core pointer' core device.\n\t\t\"gui\": {\n\t\t\t# GUI-only settings\n\t\t\t\"enable_status_icon\" : False,\n\t\t\t\"minimize_to_status_icon\" : True,\n\t\t\t\"minimize_on_start\" : False,\n\t\t\t\"autokill_daemon\" : False,\n\t\t\t\"news\": {\n\t\t\t\t# Controls \"new in this version\" message\n\t\t\t\t\"enabled\": True,\t\t\t# if disabled, no querying is done\n\t\t\t\t\"last_version\": \"0.3.12\",\t# last version for which message was displayed\n\t\t\t}\n\t\t},\n\t\t\"controllers\": { },\n\t\t# output - modifies emulated controller\n\t\t# Changing this may be usefull, but can break a lot of things\n\t\t\"output\": {\n\t\t\t'vendor'\t: '0x045e',\n\t\t\t'product'\t: '0x028e',\n\t\t\t'version'\t: '0x110',\n\t\t\t'name'\t\t: \"Microsoft X-Box 360 pad\",\n\t\t\t'buttons'\t: 11,\n\t\t\t'rumble'\t: True,\n\t\t\t'axes'\t: [\n\t\t\t\t(-32768, 32767),\t# Axes.ABS_X\n\t\t\t\t(-32768, 32767),\t# Axes.ABS_Y\n\t\t\t\t(-32768, 32767),\t# Axes.ABS_RX\n\t\t\t\t(-32768, 32767),\t# Axes.ABS_RY\n\t\t\t\t(0, 255),\t\t\t# Axes.ABS_Z\n\t\t\t\t(0, 255),\t\t\t# Axes.ABS_RZ\n\t\t\t\t(-1, 1),\t\t\t# Axes.ABS_HAT0X\n\t\t\t\t(-1, 1)\t\t\t\t# Axes.ABS_HAT0Y\n\t\t\t],\n\t\t},\n\t\t# enable_sniffing - If enabled, another program with write access to\n\t\t# ~/.config/scc can ask daemon to send notifications about all\n\t\t# (or only some) inputs.\n\t\t# This enables GUI to display which physical button was pressed to user.\n\t\t\"enable_sniffing\" : False,\n\t\t# Style and colors used by OSD\n\t\t\"osd_style\": \"Classic.gtkstyle.css\",\n\t\t\"osd_colors\": {\n\t\t\t\"background\": \"101010\",\n\t\t\t\"border\": \"101010\",\n\t\t\t\"text\": \"16BF24\",\n\t\t\t\"menuitem_border\": \"101010\",\n\t\t\t\"menuitem_hilight\": \"202020\",\n\t\t\t\"menuitem_hilight_text\": \"16FF26\",\n\t\t\t\"menuitem_hilight_border\": \"16FF26\",\n\t\t\t\"menuseparator\": \"2e3436\",\n\t\t},\n\t\t# Colors used by on-screen keyboard\n\t\t\"osk_colors\": {\n\t\t\t'hilight' : '7A7A7A',\n\t\t\t'pressed' : 'B0B0B0',\n\t\t\t\"button1\" : \"101010\",\n\t\t\t\"button1_border\" : \"101010\",\n\t\t\t\"button2\" : \"2e3436\",\n\t\t\t\"button2_border\" : \"2e3436\",\n\t\t\t\"text\" : \"16BF24\"\n\t\t},\n\t\t# Colors used by gesture display. Unlike OSD and OSK, these are RGBA\n\t\t\"gesture_colors\" : {\n\t\t\t\"background\": \"160c00ff\",\n\t\t\t\"grid\": \"004000ff\",\n\t\t\t\"line\": \"ffffff1a\",\n\t\t},\n\t\t# TODO: Config for opacity\n\t\t\"windows_opacity\": 0.95,\n\t\t# See drivers/sc_dongle.py, read_serial method\n\t\t\"ignore_serials\" : True,\n\t}\n\t\n\tCONTROLLER_DEFAULTS = {\n\t\t# Defaults for controller config\n\t\t\"name\":\t\t\t\t\tNone,\t# Filled with controller ID on runtime\n\t\t\"icon\":\t\t\t\t\tNone,\t# Determined by magic by UI\n\t\t\"led_level\":\t\t\t80,\t\t# range 0 to 100\n\t\t\"idle_timeout\":\t\t\t600,\t# in seconds, range from 1 to 32767\n\t\t\"osd_alignment\":\t\t0,\t\t# not used yet\n\t\t\"input_rotation_l\":\t\t20,\t\t# range -180 to 180\n\t\t\"input_rotation_r\":\t\t-20,\t# range -180 to 180\n\t\t\"menu_control\":\t\t\t\"STICK\",\n\t\t\"menu_confirm\":\t\t\t\"A\",\n\t\t\"menu_cancel\":\t\t\t\"B\",\n\t}\n\t\n\t\n\tdef __init__(self):\n\t\tself.filename = os.path.join(get_config_path(), \"config.json\")\n\t\tself.reload()\n\t\n\t\n\tdef reload(self):\n\t\t\"\"\" (Re)loads configuration. Works as load(), but handles exceptions \"\"\"\n\t\ttry:\n\t\t\tself.load()\n\t\texcept Exception as e:\n\t\t\tlog.warning(\"Failed to load configuration; Creating new one.\")\n\t\t\tlog.warning(\"Reason: %s\", (e,))\n\t\t\tself.create()\n\t\tif self.check_values():\n\t\t\tself.save()\n\t\n\t\n\tdef _check_dict(self, values, defaults):\n\t\t\"\"\"\n\t\tRecursivelly checks if 'config' contains all keys in 'defaults'.\n\t\tCreates keys with default values where missing.\n\t\t\n\t\tReturns True if anything was changed.\n\t\t\"\"\"\n\t\trv = False\n\t\tfor d in defaults:\n\t\t\tif d not in values:\n\t\t\t\tvalues[d] = defaults[d]\n\t\t\t\trv = True\n\t\t\tif type(values[d]) == dict:\n\t\t\t\trv = self._check_dict(values[d], defaults[d]) or rv\n\t\treturn rv\n\t\n\t\n\tdef check_values(self):\n\t\t\"\"\"\n\t\tCheck if all required values are in place and fill by default\n\t\twhatever is missing.\n\t\t\n\t\tReturns True if anything gets changed.\n\t\t\"\"\"\n\t\trv = self._check_dict(self.values, self.DEFAULTS)\n\t\t# Special check for autoswitcher after v0.2.17\n\t\tif \"autoswitch\" in self.values:\n\t\t\tfor a in self.values[\"autoswitch\"]:\n\t\t\t\tif \"profile\" in a:\n\t\t\t\t\ta[\"action\"] = ChangeProfileAction(str(a[\"profile\"])).to_string()\n\t\t\t\t\tdel a[\"profile\"]\n\t\t\t\t\trv = True\n\t\treturn rv\n\t\n\t\n\tdef get_controller_config(self, controller_id):\n\t\t\"\"\"\n\t\tReturns self['controllers'][controller_id], creating new node populated\n\t\twith defaults if there is none.\n\t\t\"\"\"\n\t\tif controller_id in self.values['controllers']:\n\t\t\t# Check values in existing config\n\t\t\trv = self.values['controllers'][controller_id]\n\t\t\tfor key in self.CONTROLLER_DEFAULTS:\n\t\t\t\tif key not in rv:\n\t\t\t\t\tif key in (\"input_rotation_l\", \"input_rotation_r\"):\n\t\t\t\t\t\t# Special case, just to not change behavior for existing users\n\t\t\t\t\t\trv[key] = 0\n\t\t\t\t\telse:\n\t\t\t\t\t\trv[key] = self.CONTROLLER_DEFAULTS[key]\n\t\t\treturn rv\n\t\t# Create new config\n\t\trv = self.values['controllers'][controller_id] = {\n\t\t\tkey : self.CONTROLLER_DEFAULTS[key] for key in self.CONTROLLER_DEFAULTS\n\t\t}\n\t\trv[\"name\"] = controller_id\n\t\treturn rv\n\t\n\t\n\tdef load(self):\n\t\tself.values = json.loads(open(self.filename, \"r\").read())\n\t\n\t\n\tdef create(self):\n\t\t\"\"\" Creates new, empty configuration \"\"\"\n\t\tself.values = {}\n\t\tself.check_values()\n\t\tself.save()\n\t\n\t\n\tdef save(self):\n\t\t\"\"\" Saves configuration file \"\"\"\n\t\t# Check & create directory\n\t\tif not os.path.exists(get_config_path()):\n\t\t\tos.makedirs(get_config_path())\n\t\t# Save\n\t\tdata = { k:self.values[k] for k in self.values }\n\t\tjstr = Encoder(sort_keys=True, indent=4).encode(data)\n\t\topen(self.filename, \"w\").write(jstr)\n\t\tlog.debug(\"Configuration saved\")\n\t\n\t\n\tdef __iter__(self):\n\t\tfor k in self.values:\n\t\t\tyield k\n\t\n\tdef get(self, key, default=None):\n\t\treturn self.values.get(key, default)\n\t\n\tdef set(self, key, value):\n\t\tself.values[key] = value\n\t\n\t__getitem__ = get\n\t__setitem__ = set\n\t\n\tdef __contains__(self, key):\n\t\t\"\"\" Returns true if there is such value \"\"\"\n\t\treturn key in self.values\n\n"
  },
  {
    "path": "scc/constants.py",
    "content": "#!/usr/bin/env python\n\n# The MIT License (MIT)\n#\n# Copyright (c) 2015 Stany MARCEL <stanypub@gmail.com>\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\nfrom scc.lib import IntEnum\n\n\"\"\"\nIf SC-Controller is updated while daemon is running, DAEMON_VERSION send by\ndaemon will differ one one expected by UI and daemon will be forcefully restarted.\n\"\"\"\nDAEMON_VERSION = \"0.4.8.13\"\n\nHPERIOD  = 0.02\nLPERIOD  = 0.5\nDURATION = 1.0\n\n# Constants used when forcing gamepad to read some type of event is needed\nFE_STICK\t= 1\nFE_TRIGGER\t= 2\nFE_PAD\t\t= 3\nFE_GYRO\t\t= 4\n\n# Trigger names, pads, etc. These constants are used on multiple places\nLEFT\t= \"LEFT\"\nRIGHT\t= \"RIGHT\"\nCPAD\t= \"CPAD\"\nRSTICK\t= \"RSTICK\"\nDPAD\t= \"DPAD\"\nWHOLE\t= \"WHOLE\"\nSTICK\t= \"STICK\"\nGYRO\t= \"GYRO\"\nPITCH\t= \"PITCH\"\nYAW\t\t= \"YAW\"\nROLL\t= \"ROLL\"\n\n# Special constants currently used only by menus\nSAME = \"SAME\"\t\t# Menu is canceled by releasing same button that intiated it\nDEFAULT = \"DEFAULT\"\t# Default confirm/cancel button. A/B for menus initiated by\n\t\t\t\t\t# button, pad clicking / releasing for menus on pads\n\n# Deadzone modes\nCUT\t\t= \"CUT\"\nROUND\t= \"ROUND\"\nLINEAR\t= \"LINEAR\"\nMINIMUM\t= \"MINIMUM\"\n\n# Hipfire modes\nHIPFIRE_NORMAL = \"NORMAL\"\nHIPFIRE_SENSIBLE = \"SENSIBLE\"\nHIPFIRE_EXCLUSIVE = \"EXCLUSIVE\"\n\nPARSER_CONSTANTS = ( LEFT, RIGHT, WHOLE, STICK, GYRO, PITCH,\n\tYAW, ROLL, DEFAULT, SAME, CUT, ROUND, LINEAR, MINIMUM,\n\tHIPFIRE_NORMAL, HIPFIRE_SENSIBLE, HIPFIRE_EXCLUSIVE )\n\n\n\nclass SCButtons(IntEnum):\n\tRPADTOUCH\t= 0b000010000000000000000000000000000\n\tLPADTOUCH\t= 0b000001000000000000000000000000000\n\tRPAD\t\t= 0b000000100000000000000000000000000\n\tLPAD\t\t= 0b000000010000000000000000000000000 # Same for stick but without LPadTouch\n\tRGRIP\t\t= 0b000000001000000000000000000000000\n\tLGRIP\t\t= 0b000000000100000000000000000000000\n\tSTART\t\t= 0b000000000010000000000000000000000\n\tC\t\t\t= 0b000000000001000000000000000000000\n\tBACK\t\t= 0b000000000000100000000000000000000\n\tA\t\t\t= 0b000000000000000001000000000000000\n\tX\t\t\t= 0b000000000000000000100000000000000\n\tB\t\t\t= 0b000000000000000000010000000000000\n\tY\t\t\t= 0b000000000000000000001000000000000\n\tLB\t\t\t= 0b000000000000000000000100000000000\n\tRB\t\t\t= 0b000000000000000000000010000000000\n\tLT\t\t\t= 0b000000000000000000000001000000000\n\tRT\t\t\t= 0b000000000000000000000000100000000\n\tCPADTOUCH\t= 0b000000000000000000000000000000100 # Available on DS4 pad\n\tCPADPRESS\t= 0b000000000000000000000000000000010 # Available on DS4 pad\n\tSTICKPRESS\t= 0b001000000000000000000000000000000\n\tRSTICKPRESS\t= 0b010000000000000000000000000000000\n\tDOTS\t\t= 0b000000000000000000000000000001000 # Deck only\n\tRGRIP2\t\t= 0b000000000000000000000000000100000 # Deck only\n\tLGRIP2\t\t= 0b000000000000000000000000000010000 # Deck only\n\n\n# If lpad and stick is used at once, this is sent as\n# button with every other packet to signalize that\n# value of lpad_x and lpad_y belongs to stick\nSTICKTILT\t\t= 0b10000000000000000000000000000000\n\n\nclass HapticPos(IntEnum):\n\t\"\"\"Specify witch pad or trig is used\"\"\"\n\tRIGHT = 0\n\tLEFT = 1\n\tBOTH = 2\t# emulated\n\n\nclass ControllerFlags(IntEnum):\n\t\"\"\"\n\tUsed by mapper to workaround some physical differences between\n\tSteam Controller and other pads.\n\t\"\"\"\n\tNONE =\t\t\t\t0\t\t# No flags, default SC.\n\tHAS_RSTICK =\t\t1 << 0\t# Controller has right stick instead of touchpad\n\tSEPARATE_STICK =\t1 << 1\t# Left stick and left pad are using separate axes\n\tEUREL_GYROS =\t\t1 << 2\t# Gyro sensor values are provided as pitch, yaw\n\t\t\t\t\t\t\t\t# and roll instead of quaterion. 'q4' is unused\n\t\t\t\t\t\t\t\t# in such case.\n\tHAS_CPAD =\t\t\t1 << 3\t# Controller has DS4-like touchpad in center\n\tHAS_DPAD =\t\t\t1 << 4\t# Controller has normal d-pad instead of touchpad\n\tNO_GRIPS =\t\t\t1 << 5\t# Controller has no grips\n\tIS_DECK =\t\t\t1 << 6\t# Very special case\n\n\nSTICK_PAD_MIN = -32768\nSTICK_PAD_MAX = 32767\nSTICK_PAD_MIN_HALF = STICK_PAD_MIN / 3\nSTICK_PAD_MAX_HALF = STICK_PAD_MAX / 3\nSTICK_PAD_RES = STICK_PAD_MAX - (STICK_PAD_MIN)\n\n# Take async 360 stick axes into account\nOUTPUT_360_STICK_MAX = 32767\nOUTPUT_360_STICK_MIN = -32768\nOUTPUT_360_STICK_RES = OUTPUT_360_STICK_MAX - (OUTPUT_360_STICK_MIN)\n\nCPAD_MIN = 0\nCPAD_X_MAX = 1916\nCPAD_Y_MAX = 930\n\nSTICK_PAD_MIN_HALF = STICK_PAD_MIN / 3\nTRIGGER_MIN = 0\nTRIGGER_HALF = 50\nTRIGGER_CLICK = 254 # Values under this are generated until trigger clicks\nTRIGGER_MAX = 255\nBASE_STICK_MOUSE_SPEED = 1000 # Pixels per second\n"
  },
  {
    "path": "scc/controller.py",
    "content": "#!/usr/bin/env python2\nfrom scc.constants import HapticPos\nimport time\nimport logging\n\nlog = logging.getLogger(\"SCController\")\n\nnext_id = 1\t\t# Used with fallback controller id generator\n\nclass Controller(object):\n\t\"\"\"\n\tBase class for all controller drivers. Implementations are in\n\tscc.drivers package.\n\t\n\tDerived class should implement every method from here.\n\t\"\"\"\n\tflags = 0\n\t\n\tdef __init__(self):\n\t\tglobal next_id\n\t\tself.mapper = None\n\t\tself._id = next_id\n\t\tnext_id += 1\n\t\tself.lastTime = time.time()\n\t\tself.time_elapsed = 0.0\n\t\n\t\n\tdef get_type(self):\n\t\t\"\"\"\n\t\tThis method has to return type identifier - short string without spaces\n\t\tthat describes type of controller which should be unique for each\n\t\tdriver.\n\t\tString is used by UI to assign icons and, along with ID,\n\t\tto store controller settings.\n\t\t\n\t\tThis method has to be overriden.\n\t\t\"\"\"\n\t\traise RuntimeError(\"Controller.get_type not overriden\")\n\t\n\t\n\tdef get_id(self):\n\t\t\"\"\"\n\t\tReturns identifier that has to be unique at least until daemon\n\t\tis restarted, ideally derived from HW device serial number.\n\t\t\"\"\"\n\t\treturn self._id\n\t\n\t\n\tdef get_gui_config_file(self):\n\t\t\"\"\"\n\t\tReturns file name of json file that GUI can use to load more data about\n\t\tcontroller (background image, button images, available buttons and\n\t\taxes, etc...) File name may be absolute path or just name of file in\n\t\t/usr/share/scc\n\t\t\n\t\tReturns None if there is no configuration file (GUI will use\n\t\tdefaults in such case)\n\t\t\"\"\"\n\t\treturn None\n\t\n\t\n\tdef set_mapper(self, mapper):\n\t\t\"\"\" Sets mapper for controller \"\"\"\n\t\tself.mapper = mapper\n\t\n\t\n\tdef get_mapper(self):\n\t\t\"\"\" Returns mapper set for controller \"\"\"\n\t\treturn self.mapper\n\t\n\t\n\tdef apply_config(self, config):\n\t\t\"\"\"\n\t\tCalled from daemon to apply controller configuration stored\n\t\tin config file.\n\t\t\n\t\tDoes nothing by default.\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef set_led_level(self, level):\n\t\t\"\"\"\n\t\tConfigures LED intensity, if supported.\n\t\t'level' goes from 0.0 to 100.0\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef set_gyro_enabled(self, enabled):\n\t\t\"\"\" Enables or disables gyroscope, if supported \"\"\"\n\t\tpass\n\t\n\t\n\tdef get_gyro_enabled(self):\n\t\t\"\"\" Returns True if gyroscope is enabled \"\"\"\n\t\treturn False\n\t\n\t\n\tdef feedback(self, data):\n\t\t\"\"\"\n\t\tGenerates feedback effect, if supported.\n\t\t'data' is HapticData instance.\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef turnoff(self):\n\t\t\"\"\" Turns off controller, if supported \"\"\"\n\t\tpass\n\t\n\t\n\tdef disconnected(self):\n\t\t\"\"\" Called from daemon after controller is disconnected \"\"\"\n\t\tpass\n\t\n\nclass HapticData(object):\n\t\"\"\" Simple container to hold haptic feedback settings \"\"\"\n\t\n\tdef __init__(self, position, amplitude=512, frequency=4, period=1024, count=1):\n\t\t\"\"\"\n\t\t'frequency' is used only when emulating touchpad and describes how many\n\t\tpixels should mouse travell between two feedback ticks.\n\t\t\"\"\"\n\t\tdata = tuple([ int(x) for x in (position, amplitude, period, count) ])\n\t\tif data[0] not in (HapticPos.LEFT, HapticPos.RIGHT, HapticPos.BOTH):\n\t\t\traise ValueError(\"Invalid position\")\n\t\tfor i in (1,2,3):\n\t\t\tif data[i] > 0x8000 or data[i] < 0:\n\t\t\t\traise ValueError(\"Value out of range: %s\", data[i])\n\t\t# frequency is multiplied by 1000 just so I don't have big numbers everywhere;\n\t\t# it's float until here, so user still can make pad squeak if he wish\n\t\tfrequency = int(max(1.0, frequency * 1000.0))\n\t\t\n\t\tself.data = data\t\t\t\t# send to controller\n\t\tself.frequency = frequency\t\t# used internally\n\t\n\t\n\tdef with_position(self, position):\n\t\t\"\"\" Creates copy of HapticData with position value changed \"\"\"\n\t\ttrash, amplitude, period, count = self.data\n\t\treturn HapticData(position, amplitude, self.frequency, period, count)\n\t\n\t\n\tdef get_position(self):\n\t\treturn HapticPos(self.data[0])\n\t\n\tdef get_amplitude(self):\n\t\treturn self.data[1]\n\t\n\tdef get_frequency(self):\n\t\treturn float(self.frequency) / 1000.0\n\t\n\tdef get_period(self):\n\t\treturn self.data[2]\n\t\n\tdef get_count(self):\n\t\treturn self.data[3]\n\t\n\tdef __mul__(self, by):\n\t\t\"\"\"\n\t\tAllows multiplying HapticData by scalar to get same values\n\t\twith increased amplitude.\n\t\t\"\"\"\n\t\tposition, amplitude, period, count = self.data\n\t\tamplitude = min(amplitude * by, 0x8000)\n\t\treturn HapticData(position, amplitude, self.frequency, period, count)\n"
  },
  {
    "path": "scc/custom.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Custom module loader\n\nLoads ~/.config/scc/custom.py, if present. This allows injecting custom action\nclasses by user and breaking everything in very creative ways.\n\nload_custom_module function needs to be called by daemon and GUI, so it exists\nin separate module.\n\"\"\"\n\nfrom scc.paths import get_config_path\nimport os\n\ndef load_custom_module(log, who_calls=\"daemon\"):\n\t\"\"\"\n\tLoads and imports ~/.config/scc/custom.py, if it is present and displays\n\tbig, fat warning in such case.\n\t\n\tReturns True if file exists.\n\t\"\"\"\n\t\n\tfilename = os.path.join(get_config_path(), \"custom.py\")\n\tif os.path.exists(filename):\n\t\tlog.warning(\"=\" * 60)\n\t\tlog.warning(\"Loading %s\" % (filename, ))\n\t\tlog.warning(\"If you don't know what this means or you haven't \"\n\t\t\t\"created it, stop daemon right now and remove this file.\")\n\t\tlog.warning(\"\")\n\t\tlog.warning(\"Also try removing it if %s crashes \"\n\t\t\t\"shortly after this message.\" % (who_calls,))\n\t\t\n\t\timport imp\n\t\timp.load_source(\"custom\", filename)\n\t\tlog.warning(\"=\" * 60)\n\t\treturn True\n\treturn False\n"
  },
  {
    "path": "scc/device_monitor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Device Monitor\n\nExtends eudevmonitor with options to register callbacks and\nmanage plugging/releasing devices.\n\"\"\"\nfrom scc.lib.eudevmonitor import Eudev, Monitor\nfrom scc.lib.ioctl_opt import IOR\nfrom ctypes.util import find_library\nimport os, ctypes, fcntl, re, logging, time\n\nlog = logging.getLogger(\"DevMon\")\n\nRE_BT_NUMBERS = re.compile(r\"[0-9A-F]{4}:([0-9A-F]{4}):([0-9A-F]{4}).*\")\nHCIGETCONNLIST = IOR(ord('H'), 212, ctypes.c_int)\nHAVE_BLUETOOTH_LIB = False\ntry:\n\tbtlib_name = find_library('bluetooth')\n\tassert btlib_name\n\tbtlib = ctypes.CDLL(btlib_name)\n\tHAVE_BLUETOOTH_LIB = True\nexcept: pass\n\n\nclass DeviceMonitor(Monitor):\n\t\n\tdef __init__(self, *a):\n\t\tMonitor.__init__(self, *a)\n\t\tself.daemon = None\n\t\tself.dev_added_cbs = {}\n\t\tself.dev_removed_cbs = {}\n\t\tself.bt_addresses = {}\n\t\tself.known_devs = {}\n\t\n\t\n\tdef add_callback(self, subsystem, vendor_id, product_id, added_cb, removed_cb):\n\t\t\"\"\"\n\t\tAdds function that is called when eudev monitor detects new, ready\n\t\tto use device.\n\t\t\n\t\tThis has to be called from something called by init_drivers method.\n\t\t\"\"\"\n\t\tkey = (subsystem, vendor_id, product_id)\n\t\tassert key not in self.dev_added_cbs\n\t\tself.match_subsystem(subsystem)\n\t\t\n\t\tself.dev_added_cbs[key] = added_cb\n\t\tself.dev_removed_cbs[key] = removed_cb\n\t\n\t\n\tdef add_remove_callback(self, syspath, cb):\n\t\t\"\"\"\n\t\tAdds (possibly replaces) callback that will be called once\n\t\tdevice with specified syspath is disconnected.\n\t\t\"\"\"\n\t\tif syspath in self.known_devs:\n\t\t\tvendor, product, old_cb = self.known_devs.pop(syspath)\n\t\t\tself.known_devs[syspath] = (vendor, product, cb)\n\t\n\t\n\tdef start(self):\n\t\t\"\"\" Registers poller and starts listening for events \"\"\"\n\t\tif not HAVE_BLUETOOTH_LIB:\n\t\t\tlog.warning(\"Failed to load libbluetooth.so, bluetooth support will be incomplete\")\n\t\tpoller = self.daemon.poller\n\t\tpoller.register(self.fileno(), poller.POLLIN, self.on_data_ready)\n\t\tMonitor.start(self)\n\t\n\t\n\tdef _on_new_syspath(self, subsystem, syspath):\n\t\ttry:\n\t\t\tif subsystem == \"input\":\n\t\t\t\tvendor, product = None, None\n\t\t\telse:\n\t\t\t\tvendor, product = self.get_vendor_product(syspath, subsystem)\n\t\texcept (OSError, IOError):\n\t\t\t# Cannot grab vendor & product, probably subdevice or bus itself\n\t\t\treturn\n\t\tkey = (subsystem, vendor, product)\n\t\tcb = self.dev_added_cbs.get(key)\n\t\trem_cb = self.dev_removed_cbs.get(key)\n\t\tif cb:\n\t\t\tself.known_devs[syspath] = (vendor, product, rem_cb)\n\t\t\ttry:\n\t\t\t\tif cb(syspath, vendor, product) is None:\n\t\t\t\t\tdel self.known_devs[syspath]\n\t\t\texcept Exception as e:\n\t\t\t\tlog.exception(e)\n\t\t\t\tdel self.known_devs[syspath]\n\t\n\t\n\tdef _get_hci_addresses(self):\n\t\tif not HAVE_BLUETOOTH_LIB:\n\t\t\treturn\n\t\tcl = hci_conn_list_req()\n\t\tcl.dev_id = btlib.hci_get_route(ctypes.c_void_p(0))\n\t\tif cl.dev_id < 0 or cl.dev_id > 65534:\n\t\t\treturn\n\t\tcl.conn_num = 256\n\t\t\n\t\ts = btlib.hci_open_dev(cl.dev_id)\n\t\tif fcntl.ioctl(s, HCIGETCONNLIST, cl, True):\n\t\t\tlog.error(\"Failed to list bluetooth collections\")\n\t\t\treturn\n\t\t\n\t\tfor i in range(cl.conn_num):\n\t\t\tci = cl.conn_info[i]\n\t\t\tid = \"hci%s:%s\" % (cl.dev_id, ci.handle)\n\t\t\taddress = \":\".join([ hex(x).lstrip(\"0x\").zfill(2).upper() for x in reversed(ci.bdaddr) ])\n\t\t\tself.bt_addresses[id] = address\n\t\n\t\n\tdef _dev_for_hci(self, syspath):\n\t\t\"\"\"\n\t\tFor given syspath leading to ../hciX:ABCD, returns input device node\n\t\t\"\"\"\n\t\tname = syspath.split(\"/\")[-1]\n\t\tif \":\" not in name:\n\t\t\treturn None\n\t\taddr = self.bt_addresses.get(name)\n\t\tfor fname in os.listdir(\"/sys/bus/hid/devices/\"):\n\t\t\tnode = os.path.join(\"/sys/bus/hid/devices/\", fname)\n\t\t\ttry:\n\t\t\t\tnode_addr = DeviceMonitor._find_bt_address(node)\n\t\t\t\t#Joe: somehow my node_addr is in lowercase\n\t\t\t\tif node_addr is not None:\n\t\t\t\t\tnode_addr = str.upper(node_addr)\n\t\t\texcept IOError:\n\t\t\t\tcontinue\n\t\t\ttry:\n\t\t\t\t# SteamOS 3 \"Holo\" return caps\n\t\t\t\tif node_addr.lower() == addr.lower():\n\t\t\t\t\treturn node\n\t\t\t# None\n\t\t\texcept AttributeError:\n\t\t\t\tpass\n\t\treturn None\n\t\n\t\n\tdef on_data_ready(self, *a):\n\t\tevent = self.receive_device()\n\t\tif event:\n\t\t\tif event.action == \"bind\" and event.initialized:\n\t\t\t\tif event.syspath not in self.known_devs:\n\t\t\t\t\tself._on_new_syspath(event.subsystem, event.syspath)\n\t\t\telif event.action == \"add\" and event.initialized and event.subsystem in (\"input\", \"bluetooth\"):\n\t\t\t\t# those are not bound\n\t\t\t\tif event.syspath not in self.known_devs:\n\t\t\t\t\tif event.subsystem == \"bluetooth\":\n\t\t\t\t\t\tself._get_hci_addresses()\n\t\t\t\t\tself._on_new_syspath(event.subsystem, event.syspath)\n\t\t\telif event.action in (\"remove\", \"unbind\") and event.syspath in self.known_devs:\n\t\t\t\tvendor, product, cb = self.known_devs.pop(event.syspath)\n\t\t\t\tif cb:\n\t\t\t\t\tcb(event.syspath, vendor, product)\n\t\n\t\n\tdef rescan(self):\n\t\t\"\"\" Scans and calls callbacks for already connected devices \"\"\"\n\t\tself._get_hci_addresses()\n\t\tenumerator = self._eudev.enumerate()\n\t\tsubsystem_to_vp_to_callback = {}\n\t\t\n\t\tfor key, cb in self.dev_added_cbs.items():\n\t\t\tsubsystem, vendor_id, product_id = key\n\t\t\tenumerator.match_subsystem(subsystem)\n\t\t\tif subsystem not in subsystem_to_vp_to_callback:\n\t\t\t\tsubsystem_to_vp_to_callback[subsystem] = {}\n\t\t\tsubsystem_to_vp_to_callback[subsystem][vendor_id, product_id] = cb\n\t\t\n\t\tfor syspath in enumerator:\n\t\t\tif syspath not in self.known_devs:\n\t\t\t\ttry:\n\t\t\t\t\tsubsystem = DeviceMonitor.get_subsystem(syspath)\n\t\t\t\texcept (IOError, OSError):\n\t\t\t\t\tcontinue\n\t\t\t\tif subsystem in subsystem_to_vp_to_callback:\n\t\t\t\t\tself._on_new_syspath(subsystem, syspath)\n\t\n\t\n\tdef get_vendor_product(self, syspath, subsystem=None):\n\t\t\"\"\"\n\t\tFor given syspath, reads and returns (vendor_id, product_id) as ints.\n\t\t\n\t\tMay throw all kinds of OSErrors or IOErrors\n\t\t\"\"\"\n\t\tif os.path.exists(os.path.join(syspath, \"idVendor\")):\n\t\t\tvendor  = int(open(os.path.join(syspath, \"idVendor\")).read().strip(), 16)\n\t\t\tproduct = int(open(os.path.join(syspath, \"idProduct\")).read().strip(), 16)\n\t\t\treturn vendor, product\n\t\tif subsystem is None:\n\t\t\tsubsystem = DeviceMonitor.get_subsystem(syspath)\n\t\tif subsystem == \"bluetooth\":\n\t\t\t# Search for folder that matches regular expression...\n\t\t\tnames = [ name for name in os.listdir(syspath)\n\t\t\t\tif os.path.isdir(syspath) and RE_BT_NUMBERS.match(name) ]\n\t\t\tif len(names) > 0:\n\t\t\t\tvendor, product = [ int(x, 16) for x in RE_BT_NUMBERS.match(names[0]).groups() ]\n\t\t\t\treturn vendor, product\n\t\t\t# Above method works for anything _but_ SteamController\n\t\t\t# For that one, following desperate mess is needed\n\n\t\t\t# Sleep for 1 second to make sure info is available on system\n\t\t\ttime.sleep(1)\n\t\t\tnode = self._dev_for_hci(syspath)\n\t\t\tif node:\n\t\t\t\tname = node.split(\"/\")[-1]\n\t\t\t\tif RE_BT_NUMBERS.match(name):\n\t\t\t\t\tvendor, product = [ int(x, 16) for x in RE_BT_NUMBERS.match(name).groups() ]\n\t\t\t\t\treturn vendor, product\n\t\traise OSError(\"Cannot determine vendor and product IDs\")\n\t\n\t\n\tdef get_hidraw(self, syspath):\n\t\t\"\"\"\n\t\tFor given syspath, returns name of assotiated hidraw device.\n\t\tReturns None if there is no such thing.\n\t\t\"\"\"\n\t\tnode = self._dev_for_hci(syspath)\n\t\tif node is None:\n\t\t\treturn None\n\t\thidrawsubdir = os.path.join(node, \"hidraw\")\n\t\tfor fname in os.listdir(hidrawsubdir):\n\t\t\tif fname.startswith(\"hidraw\"):\n\t\t\t\treturn fname\n\t\treturn None\n\t\n\t\n\t@staticmethod\n\tdef _find_bt_address(syspath):\n\t\t\"\"\"\n\t\tRecursivelly searchs for \"input*\" subdirectories until \"uniq\" file\n\t\tis found. Then, returns address from that file.\n\t\t\"\"\"\n\t\tuniq = os.path.join(syspath, \"uniq\")\n\t\tif os.path.exists(uniq):\n\t\t\treturn open(uniq, \"r\").read().strip()\n\t\tfor name in os.listdir(syspath):\n\t\t\tif name.startswith(\"input\"):\n\t\t\t\tpath = os.path.join(syspath, name)\n\t\t\t\tif os.path.isdir(path) and not os.path.islink(path):\n\t\t\t\t\taddr = DeviceMonitor._find_bt_address(path)\n\t\t\t\t\tif addr: return addr\n\t\treturn None\n\t\n\t\n\t@staticmethod\n\tdef get_usb_address(syspath):\n\t\t\"\"\"\n\t\tFor given syspath, reads and returns (busnum, devnum) as ints.\n\t\t\n\t\tMay throw all kinds of OSErrors or IOErrors\n\t\t\"\"\"\n\t\tbusnum  = int(open(os.path.join(syspath, \"busnum\")).read().strip())\n\t\tdevnum = int(open(os.path.join(syspath, \"devnum\")).read().strip())\n\t\treturn busnum, devnum\n\t\n\t\n\t@staticmethod\n\tdef get_subsystem(syspath):\n\t\t\"\"\"\n\t\tFor given syspath, reads and returns subsystem as string.\n\t\t\n\t\tMay throw OSError if directory is not readable.\n\t\t\"\"\"\n\t\treturn os.readlink(os.path.join(syspath, \"subsystem\")).split(\"/\")[-1]\n\n\nclass hci_conn_info(ctypes.Structure):\n\t_fields_ = [\n\t\t('handle', ctypes.c_uint16),\n\t\t('bdaddr', ctypes.c_uint8 * 6),\n\t\t('type', ctypes.c_uint8),\n\t\t('out', ctypes.c_uint8),\n\t\t('state', ctypes.c_uint16),\n\t\t('link_mode', ctypes.c_uint32),\n\t]\n\n\nclass hci_conn_list_req(ctypes.Structure):\n\t_fields_ = [\n\t\t('dev_id', ctypes.c_uint16),\n\t\t('conn_num', ctypes.c_uint16),\n\t\t('conn_info', hci_conn_info * 256),\n\t]\n\n\ndef create_device_monitor(daemon):\n\tm = Eudev().monitor(subclass=DeviceMonitor)\n\tm.daemon = daemon\n\treturn m\n"
  },
  {
    "path": "scc/drivers/__init__.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller goes through all modules in scc.drivers package and calls\ninit(daemon) methods from every module that defines it.\n\nDrivers then can use daemon.add_mainloop() method to add code that should\nbe run in every mainloop iteration and daemon.add_controller() to add and\ndaemon.remove_controller() to remove Controller instances.\n\nAdditionaly, start(daemon) method is called from each module that defines it\njust before daemon startup is complete.\n\nAssigning Mapper to Controller is handled by daemon.\n\"\"\"\n\nMOD_INIT_ORDER = (\n\t# Modules mentioned here are initialized before everything else, in this exact order.\n\t\"scc.drivers.usb\",\n\t\"scc.drivers.evdevdrv\",\n\t\"scc.drivers.hiddrv\"\n)\n"
  },
  {
    "path": "scc/drivers/ds4drv.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Dualshock 4 Driver\n\nExtends HID driver with DS4-specific options.\n\"\"\"\n\nfrom scc.drivers.hiddrv import BUTTON_COUNT, ButtonData, AxisType, AxisData\nfrom scc.drivers.hiddrv import HIDController, HIDDecoder, hiddrv_test\nfrom scc.drivers.hiddrv import AxisMode, AxisDataUnion, AxisModeData\nfrom scc.drivers.hiddrv import HatswitchModeData, _lib\nfrom scc.drivers.evdevdrv import HAVE_EVDEV, EvdevController, get_axes\nfrom scc.drivers.evdevdrv import get_evdev_devices_from_syspath\nfrom scc.drivers.evdevdrv import make_new_device\nfrom scc.drivers.usb import register_hotplug_device\nfrom scc.constants import SCButtons, ControllerFlags\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.tools import init_logging, set_logging_level\nimport sys, logging, ctypes\nlog = logging.getLogger(\"DS4\")\n\nVENDOR_ID = 0x054c\nPRODUCT_ID = 0x09cc\nDS4_V1_PRODUCT_ID = 0x5C4\n\n\nclass DS4Controller(HIDController):\n\t# Most of axes are the same\n\tBUTTON_MAP = (\n\t\tSCButtons.X,\n\t\tSCButtons.A,\n\t\tSCButtons.B,\n\t\tSCButtons.Y,\n\t\tSCButtons.LB,\n\t\tSCButtons.RB,\n\t\t1 << 64,\n\t\t1 << 64,\n\t\tSCButtons.BACK,\n\t\tSCButtons.START,\n\t\tSCButtons.STICKPRESS,\n\t\tSCButtons.RPAD,\n\t\tSCButtons.C,\n\t\tSCButtons.CPADPRESS,\n\t)\n\t\n\tflags = ( ControllerFlags.EUREL_GYROS\n\t\t\t| ControllerFlags.HAS_RSTICK\n\t\t\t| ControllerFlags.HAS_CPAD\n\t\t\t| ControllerFlags.HAS_DPAD\n\t\t\t| ControllerFlags.SEPARATE_STICK\n\t\t\t| ControllerFlags.NO_GRIPS\n\t)\n\t\n\t\n\tdef _load_hid_descriptor(self, config, max_size, vid, pid, test_mode):\n\t\t# Overrided and hardcoded\n\t\tself._decoder = HIDDecoder()\n\t\tself._decoder.axes[AxisType.AXIS_LPAD_X] = AxisData(\n\t\t\tmode = AxisMode.HATSWITCH, byte_offset = 5, size = 8,\n\t\t\tdata = AxisDataUnion(hatswitch = HatswitchModeData(\n\t\t\t\tbutton = SCButtons.LPAD | SCButtons.LPADTOUCH,\n\t\t\t\tmin = STICK_PAD_MIN, max = STICK_PAD_MAX\n\t\t)))\n\t\tself._decoder.axes[AxisType.AXIS_STICK_X] = AxisData(\n\t\t\tmode = AxisMode.AXIS, byte_offset = 1, size = 8,\n\t\t\tdata = AxisDataUnion(axis = AxisModeData(\n\t\t\t\tscale = 1.0, offset = -127.5, clamp_max = 257, deadzone = 10\n\t\t)))\n\t\tself._decoder.axes[AxisType.AXIS_STICK_Y] = AxisData(\n\t\t\tmode = AxisMode.AXIS, byte_offset = 2, size = 8,\n\t\t\tdata = AxisDataUnion(axis = AxisModeData(\n\t\t\t\tscale = -1.0, offset = 127.5, clamp_max = 257, deadzone = 10\n\t\t)))\n\t\tself._decoder.axes[AxisType.AXIS_RPAD_X] = AxisData(\n\t\t\tmode = AxisMode.AXIS, byte_offset = 3, size = 8,\n\t\t\tdata = AxisDataUnion(axis = AxisModeData(\n\t\t\t\tbutton = SCButtons.RPADTOUCH,\n\t\t\t\tscale = 1.0, offset = -127.5, clamp_max = 257, deadzone = 10\n\t\t)))\n\t\tself._decoder.axes[AxisType.AXIS_RPAD_Y] = AxisData(\n\t\t\tmode = AxisMode.AXIS, byte_offset = 4, size = 8,\n\t\t\tdata = AxisDataUnion(axis = AxisModeData(\n\t\t\t\tbutton = SCButtons.RPADTOUCH,\n\t\t\t\tscale = -1.0, offset = 127.5, clamp_max = 257, deadzone = 10\n\t\t)))\n\t\tself._decoder.axes[AxisType.AXIS_LTRIG] = AxisData(\n\t\t\tmode = AxisMode.AXIS, byte_offset = 8, size = 8,\n\t\t\tdata = AxisDataUnion(axis = AxisModeData(\n\t\t\t\tscale = 1.0, clamp_max = 1, deadzone = 10\n\t\t)))\n\t\tself._decoder.axes[AxisType.AXIS_RTRIG] = AxisData(\n\t\t\tmode = AxisMode.AXIS, byte_offset = 9, size = 8,\n\t\t\tdata = AxisDataUnion(axis = AxisModeData(\n\t\t\t\tscale = 1.0, clamp_max = 1, deadzone = 10\n\t\t)))\n\t\tself._decoder.axes[AxisType.AXIS_GPITCH] = AxisData(\n\t\t\tmode = AxisMode.DS4ACCEL, byte_offset = 13)\n\t\tself._decoder.axes[AxisType.AXIS_GROLL] = AxisData(\n\t\t\tmode = AxisMode.DS4ACCEL, byte_offset = 17)\n\t\tself._decoder.axes[AxisType.AXIS_GYAW] = AxisData(\n\t\t\tmode = AxisMode.DS4ACCEL, byte_offset = 15)\n\t\tself._decoder.axes[AxisType.AXIS_Q1] = AxisData(\n\t\t\tmode = AxisMode.DS4GYRO, byte_offset = 23)\n\t\tself._decoder.axes[AxisType.AXIS_Q2] = AxisData(\n\t\t\tmode = AxisMode.DS4GYRO, byte_offset = 19)\n\t\tself._decoder.axes[AxisType.AXIS_Q3] = AxisData(\n\t\t\tmode = AxisMode.DS4GYRO, byte_offset = 21)\n\t\t\n\t\tself._decoder.axes[AxisType.AXIS_CPAD_X] = AxisData(\n\t\t\tmode = AxisMode.DS4TOUCHPAD, byte_offset = 36)\n\t\tself._decoder.axes[AxisType.AXIS_CPAD_Y] = AxisData(\n\t\t\tmode = AxisMode.DS4TOUCHPAD, byte_offset = 37, bit_offset=4)\n\t\tself._decoder.buttons = ButtonData(\n\t\t\tenabled = True, byte_offset=5, bit_offset=4, size=14,\n\t\t\tbutton_count = 14\n\t\t)\n\t\t\n\t\tif test_mode:\n\t\t\tfor x in range(BUTTON_COUNT):\n\t\t\t\tself._decoder.buttons.button_map[x] = x\n\t\telse:\n\t\t\tfor x in range(BUTTON_COUNT):\n\t\t\t\tself._decoder.buttons.button_map[x] = 64\n\t\t\tfor x, sc in enumerate(DS4Controller.BUTTON_MAP):\n\t\t\t\tself._decoder.buttons.button_map[x] = self.button_to_bit(sc)\n\t\t\n\t\tself._packet_size = 64\n\t\n\t\n\tdef input(self, endpoint, data):\n\t\t# Special override for CPAD touch button\n\t\tif _lib.decode(ctypes.byref(self._decoder), data):\n\t\t\tif self.mapper:\n\t\t\t\tif data[35] >> 7:\n\t\t\t\t\t# cpad is not touched\n\t\t\t\t\tself._decoder.state.buttons &= ~SCButtons.CPADTOUCH\n\t\t\t\telse:\n\t\t\t\t\tself._decoder.state.buttons |= SCButtons.CPADTOUCH\n\t\t\t\tself.mapper.input(self,\n\t\t\t\t\t\tself._decoder.old_state, self._decoder.state)\n\n\t\n\tdef get_gyro_enabled(self):\n\t\t# Cannot be actually turned off, so it's always active\n\t\t# TODO: Maybe emulate turning off?\n\t\treturn True\n\t\n\t\n\tdef get_type(self):\n\t\treturn \"ds4\"\n\t\n\t\n\tdef get_gui_config_file(self):\n\t\treturn \"ds4-config.json\"\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<DS4Controller %s>\" % (self.get_id(), )\n\t\n\t\n\tdef _generate_id(self):\n\t\t\"\"\"\n\t\tID is generated as 'ds4' or 'ds4:X' where 'X' starts as 1 and increases\n\t\tas controllers with same ids are connected.\n\t\t\"\"\"\n\t\tmagic_number = 1\n\t\tid = \"ds4\"\n\t\twhile id in self.daemon.get_active_ids():\n\t\t\tid = \"ds4:%s\" % (magic_number, )\n\t\t\tmagic_number += 1\n\t\treturn id\n\n\nclass DS4EvdevController(EvdevController):\n\tTOUCH_FACTOR_X = STICK_PAD_MAX / 940.0\n\tTOUCH_FACTOR_Y = STICK_PAD_MAX / 470.0\n\tBUTTON_MAP = {\n\t\t304: \"A\",\n\t\t305: \"B\",\n\t\t307: \"Y\",\n\t\t308: \"X\",\n\t\t310: \"LB\",\n\t\t311: \"RB\",\n\t\t314: \"BACK\",\n\t\t315: \"START\",\n\t\t316: \"C\",\n\t\t317: \"STICKPRESS\",\n\t\t318: \"RPAD\"\n\t\t# 319: \"CPAD\",\n\t}\n\tAXIS_MAP = {\n\t\t0:  { \"axis\": \"stick_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0 },\n\t\t1:  { \"axis\": \"stick_y\", \"deadzone\": 4, \"max\": 0, \"min\": 255 },\n\t\t3:  { \"axis\": \"rpad_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0 },\n\t\t4:  { \"axis\": \"rpad_y\", \"deadzone\": 8, \"max\": 0, \"min\": 255 },\n\t\t2:  { \"axis\": \"ltrig\", \"max\": 255, \"min\": 0 },\n\t\t5:  { \"axis\": \"rtrig\", \"max\": 255, \"min\": 0 },\n\t\t16: { \"axis\": \"lpad_x\", \"deadzone\": 0, \"max\": 1, \"min\": -1 },\n\t\t17: { \"axis\": \"lpad_y\", \"deadzone\": 0, \"max\": -1, \"min\": 1 }\n\t}\n\tBUTTON_MAP_OLD = {\n\t\t304: \"X\",\n\t\t305: \"A\",\n\t\t306: \"B\",\n\t\t307: \"Y\",\n\t\t308: \"LB\",\n\t\t309: \"RB\",\n\t\t312: \"BACK\",\n\t\t313: \"START\",\n\t\t314: \"STICKPRESS\",\n\t\t315: \"RPAD\",\n\t\t316: \"C\",\n\t\t# 317: \"CPAD\",\n\t}\n\tAXIS_MAP_OLD = {\n\t\t0:  { \"axis\": \"stick_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0 },\n\t\t1:  { \"axis\": \"stick_y\", \"deadzone\": 4, \"max\": 0, \"min\": 255 },\n\t\t2:  { \"axis\": \"rpad_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0 },\n\t\t5:  { \"axis\": \"rpad_y\", \"deadzone\": 8, \"max\": 0, \"min\": 255 },\n\t\t3:  { \"axis\": \"ltrig\", \"max\": 32767, \"min\": -32767 },\n\t\t4:  { \"axis\": \"rtrig\", \"max\": 32767, \"min\": -32767 },\n\t\t16: { \"axis\": \"lpad_x\", \"deadzone\": 0, \"max\": 1, \"min\": -1 },\n\t\t17: { \"axis\": \"lpad_y\", \"deadzone\": 0, \"max\": -1, \"min\": 1 }\n\t}\n\tGYRO_MAP = {\n\t\tEvdevController.ECODES.ABS_RX : ('gpitch', 0.01),\n\t\tEvdevController.ECODES.ABS_RY : ('gyaw', 0.01),\n\t\tEvdevController.ECODES.ABS_RZ : ('groll', 0.01),\n\t\tEvdevController.ECODES.ABS_X : (None, 1),\t\t# 'q2'\n\t\tEvdevController.ECODES.ABS_Y : (None, 1),\t\t# 'q3'\n\t\tEvdevController.ECODES.ABS_Z : (None, -1),\t\t# 'q1'\n\t}\n\tflags = ( ControllerFlags.EUREL_GYROS\n\t\t\t| ControllerFlags.HAS_RSTICK\n\t\t\t| ControllerFlags.HAS_CPAD\n\t\t\t| ControllerFlags.HAS_DPAD\n\t\t\t| ControllerFlags.SEPARATE_STICK\n\t\t\t| ControllerFlags.NO_GRIPS\n\t)\n\t\n\tdef __init__(self, daemon, controllerdevice, gyro, touchpad):\n\t\tconfig = {\n\t\t\t'axes' : DS4EvdevController.AXIS_MAP,\n\t\t\t'buttons' : DS4EvdevController.BUTTON_MAP,\n\t\t\t'dpads' : {}\n\t\t}\n\t\tif controllerdevice.info.version & 0x8000 == 0:\n\t\t\t# Older kernel uses different mappings\n\t\t\t# see kernel source, drivers/hid/hid-sony.c#L2748\n\t\t\tconfig['axes'] = DS4EvdevController.AXIS_MAP_OLD\n\t\t\tconfig['buttons'] = DS4EvdevController.BUTTON_MAP_OLD\n\t\tself._gyro = gyro\n\t\tself._touchpad = touchpad\n\t\tfor device in (self._gyro, self._touchpad):\n\t\t\tif device:\n\t\t\t\tdevice.grab()\n\t\tEvdevController.__init__(self, daemon, controllerdevice, None, config)\n\t\tif self.poller:\n\t\t\tself.poller.register(touchpad.fd, self.poller.POLLIN, self._touchpad_input)\n\t\t\tself.poller.register(gyro.fd, self.poller.POLLIN, self._gyro_input)\n\t\n\t\n\tdef _gyro_input(self, *a):\n\t\tnew_state = self._state\n\t\ttry:\n\t\t\tfor event in self._gyro.read():\n\t\t\t\tif event.type == self.ECODES.EV_ABS:\n\t\t\t\t\taxis, factor = DS4EvdevController.GYRO_MAP[event.code]\n\t\t\t\t\tif axis:\n\t\t\t\t\t\tnew_state = new_state._replace(\n\t\t\t\t\t\t\t\t**{ axis : int(event.value * factor) })\n\t\texcept IOError:\n\t\t\t# Errors here are not even reported, evdev class handles important ones\n\t\t\treturn\n\t\t\n\t\tif new_state is not self._state:\n\t\t\told_state, self._state = self._state, new_state\n\t\t\tif self.mapper:\n\t\t\t\tself.mapper.input(self, old_state, new_state)\n\t\n\t\n\tdef _touchpad_input(self, *a):\n\t\tnew_state = self._state\n\t\ttry:\n\t\t\tfor event in self._touchpad.read():\n\t\t\t\tif event.type == self.ECODES.EV_ABS:\n\t\t\t\t\tif event.code == self.ECODES.ABS_MT_POSITION_X:\n\t\t\t\t\t\tvalue = event.value * DS4EvdevController.TOUCH_FACTOR_X\n\t\t\t\t\t\tvalue = STICK_PAD_MIN + int(value)\n\t\t\t\t\t\tnew_state = new_state._replace(cpad_x = value)\n\t\t\t\t\telif event.code == self.ECODES.ABS_MT_POSITION_Y:\n\t\t\t\t\t\tvalue = event.value * DS4EvdevController.TOUCH_FACTOR_Y\n\t\t\t\t\t\tvalue = STICK_PAD_MAX - int(value)\n\t\t\t\t\t\tnew_state = new_state._replace(cpad_y = value)\n\t\t\t\telif event.type == 0:\n\t\t\t\t\tpass\n\t\t\t\telif event.code == self.ECODES.BTN_LEFT:\n\t\t\t\t\tif event.value == 1:\n\t\t\t\t\t\tb = new_state.buttons | SCButtons.CPADPRESS\n\t\t\t\t\t\tnew_state = new_state._replace(buttons = b)\n\t\t\t\t\telse:\n\t\t\t\t\t\tb = new_state.buttons & ~SCButtons.CPADPRESS\n\t\t\t\t\t\tnew_state = new_state._replace(buttons = b)\n\t\t\t\telif event.code == self.ECODES.BTN_TOUCH:\n\t\t\t\t\tif event.value == 1:\n\t\t\t\t\t\tb = new_state.buttons | SCButtons.CPADTOUCH\n\t\t\t\t\t\tnew_state = new_state._replace(buttons = b)\n\t\t\t\t\telse:\n\t\t\t\t\t\tb = new_state.buttons & ~SCButtons.CPADTOUCH\n\t\t\t\t\t\tnew_state = new_state._replace(buttons = b,\n\t\t\t\t\t\t\t\tcpad_x = 0, cpad_y = 0)\n\t\texcept IOError:\n\t\t\t# Errors here are not even reported, evdev class handles important ones\n\t\t\treturn\n\t\t\n\t\tif new_state is not self._state:\n\t\t\told_state, self._state = self._state, new_state\n\t\t\tif self.mapper:\n\t\t\t\tself.mapper.input(self, old_state, new_state)\n\t\n\t\n\tdef close(self):\n\t\tEvdevController.close(self)\n\t\tfor device in (self._gyro, self._touchpad):\n\t\t\ttry:\n\t\t\t\tself.poller.unregister(device.fd)\n\t\t\t\tdevice.ungrab()\n\t\t\texcept: pass\n\t\n\t\n\tdef get_gyro_enabled(self):\n\t\t# Cannot be actually turned off, so it's always active\n\t\t# TODO: Maybe emulate turning off?\n\t\treturn True\n\t\n\t\n\tdef get_type(self):\n\t\treturn \"ds4evdev\"\n\t\n\t\n\tdef get_gui_config_file(self):\n\t\treturn \"ds4-config.json\"\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<DS4EvdevController %s>\" % (self.get_id(), )\n\t\n\t\n\tdef _generate_id(self):\n\t\t\"\"\"\n\t\tID is generated as 'ds4' or 'ds4:X' where 'X' starts as 1 and increases\n\t\tas controllers with same ids are connected.\n\t\t\"\"\"\n\t\tmagic_number = 1\n\t\tid = \"ds4\"\n\t\twhile id in self.daemon.get_active_ids():\n\t\t\tid = \"ds4:%s\" % (magic_number, )\n\t\t\tmagic_number += 1\n\t\treturn id\n\n\ndef init(daemon, config):\n\t\"\"\" Registers hotplug callback for ds4 device \"\"\"\n\t\t\n\tdef hid_callback(device, handle):\n\t\treturn DS4Controller(device, daemon, handle, None, None)\n\t\n\tdef make_evdev_device(syspath, *whatever):\n\t\tdevices = get_evdev_devices_from_syspath(syspath)\n\t\t# With kernel 4.10 or later, PS4 controller pretends to be 3 different devices.\n\t\t# 1st, determining which one is actual controller is needed\n\t\tcontrollerdevice = None\n\t\tfor device in devices:\n\t\t\tcount = len(get_axes(device))\n\t\t\tif count == 8:\n\t\t\t\t# 8 axes - Controller\n\t\t\t\tcontrollerdevice = device\n\t\tif not controllerdevice:\n\t\t\tlog.warning(\"Failed to determine controller device\")\n\t\t\treturn None\n\t\t# 2nd, find motion sensor and touchpad with physical address matching controllerdevice\n\t\tgyro, touchpad = None, None\n\t\tphys = device.phys.split(\"/\")[0]\n\t\tfor device in devices:\n\t\t\tif device.phys.startswith(phys):\n\t\t\t\taxes = get_axes(device)\n\t\t\t\tcount = len(axes)\n\t\t\t\tif count == 6:\n\t\t\t\t\t# 6 axes\n\t\t\t\t\tif EvdevController.ECODES.ABS_MT_POSITION_X in axes:\n\t\t\t\t\t\t# kernel 4.17+ - touchpad\n\t\t\t\t\t\ttouchpad = device\n\t\t\t\t\telse:\n\t\t\t\t\t\t# gyro sensor\n\t\t\t\t\t\tgyro = device\n\t\t\t\t\tpass\n\t\t\t\telif count == 4:\n\t\t\t\t\t# 4 axes - Touchpad\n\t\t\t\t\ttouchpad = device\n\t\t# 3rd, do a magic\n\t\tif controllerdevice and gyro and touchpad:\n\t\t\treturn make_new_device(DS4EvdevController, controllerdevice, gyro, touchpad)\n\t\n\t\n\tdef fail_cb(syspath, vid, pid):\n\t\tif HAVE_EVDEV:\n\t\t\tlog.warning(\"Failed to acquire USB device, falling back to evdev driver. This is far from optimal.\")\n\t\t\tmake_evdev_device(syspath)\n\t\telse:\n\t\t\tlog.error(\"Failed to acquire USB device and evdev is not available. Everything is lost and DS4 support disabled.\")\n\t\t\t# TODO: Maybe add_error here, but error reporting needs little rework so it's not threated as fatal\n\t\t\t# daemon.add_error(\"ds4\", \"No access to DS4 device\")\n\t\n\tif config[\"drivers\"].get(\"hiddrv\") or (HAVE_EVDEV and config[\"drivers\"].get(\"evdevdrv\")):\n\t\t# DS4 v.2\n\t\tregister_hotplug_device(hid_callback, VENDOR_ID, PRODUCT_ID, on_failure=fail_cb)\n\t\t# DS4 v.1\n\t\tregister_hotplug_device(hid_callback, VENDOR_ID, DS4_V1_PRODUCT_ID, on_failure=fail_cb)\n\t\tif HAVE_EVDEV and config[\"drivers\"].get(\"evdevdrv\"):\n\t\t\t# DS4 v.2\n\t\t\tdaemon.get_device_monitor().add_callback(\"bluetooth\",\n\t\t\t\t\t\t\tVENDOR_ID, PRODUCT_ID, make_evdev_device, None)\n\t\t\t# DS4 v.1\n\t\t\tdaemon.get_device_monitor().add_callback(\"bluetooth\",\n\t\t\t\tVENDOR_ID, DS4_V1_PRODUCT_ID, make_evdev_device, None)\n\t\treturn True\n\telse:\n\t\tlog.warning(\"Neither HID nor Evdev driver is enabled, DS4 support cannot be enabled.\")\n\t\treturn False\n\n\nif __name__ == \"__main__\":\n\t\"\"\" Called when executed as script \"\"\"\n\tinit_logging()\n\tset_logging_level(True, True)\n\tsys.exit(hiddrv_test(DS4Controller, [ \"054c:09cc\" ]))\n"
  },
  {
    "path": "scc/drivers/ds5drv.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - DualSense driver\n\nExtends HID driver with DS5-specific options.\n\"\"\"\n\nimport ctypes\nimport logging\nimport os, sys\nimport zlib\nfrom datetime import datetime\nimport time\nimport binascii\nimport math\n\n\nfrom scc.constants import (\n    ControllerFlags, HapticPos, SCButtons,\n    STICK_PAD_MAX, STICK_PAD_MIN, STICK_PAD_RES,\n    OUTPUT_360_STICK_MAX, OUTPUT_360_STICK_MIN, OUTPUT_360_STICK_RES,\n)\nfrom scc.drivers.evdevdrv import (\n    EvdevController,\n    HAVE_EVDEV,\n    get_axes,\n    get_evdev_devices_from_syspath,\n    make_new_device,\n)\nfrom scc.drivers.hiddrv import (\n    AxisData,\n    AxisDataUnion,\n    AxisMode,\n    AxisModeData,\n    AxisType,\n    BUTTON_COUNT,\n    ButtonData,\n    HIDController,\n    HIDDecoder,\n    HatswitchModeData,\n    _lib,\n    hiddrv_test,\n)\nfrom scc.drivers.usb import register_hotplug_device\nfrom scc.lib import IntEnum, usb1\nfrom scc.tools import init_logging, set_logging_level\nfrom scc.controller import Controller\nfrom scc.lib.hidraw import HIDRaw\n\nlog = logging.getLogger(\"DS5\")\n\nVENDOR_ID = 0x054c\nPRODUCT_ID = 0x0ce6\n\n\nOPERATING_MODE_DS5_BT = 0x31\n\nclass OperatingMode(IntEnum):\n    DS4_COMPATIBILITY_MODE = 1 << 0\n    DS5_MODE = 1 << 1\n    DS5_MODE_BT = 1 << 5 | 1 << 4 | 1 << 0\n\n\nclass PhysicalEffectControl(IntEnum):\n    ENABLE_HAPTICS = 1 << 0 | 1 << 1\n    TRIGGER_EFFECTS_RIGHT = 1 << 2\n    TRIGGER_EFFECTS_LEFT = 1 << 3\n\n\nclass LightEffectControl(IntEnum):\n    MIC_MUTE_LED_CONTROL_ENABLE = 1 << 0\n    POWER_SAVE_CONTROL_ENABLE = 1 << 1\n    LIGHTBAR_CONTROL_ENABLE = 1 << 2\n    RELEASE_LEDS = 1 << 3\n    PLAYER_INDICATOR_CONTROL_ENABLE = 1 << 4\n\n\nclass DualSenseHIDOutput(ctypes.Structure):\n    _fields_ = [\n        ('operating_mode', ctypes.c_ubyte),\n        ('physical_effect_control', ctypes.c_ubyte),\n        ('light_effect_control', ctypes.c_ubyte),\n\n        ('motor_right', ctypes.c_ubyte),\n        ('motor_left', ctypes.c_ubyte),\n\n        ('unknown2', ctypes.c_ubyte * 4),\n        ('mute_button_led', ctypes.c_ubyte),\n        ('power_save_control', ctypes.c_ubyte),\n        ('right_trigger_effect', ctypes.c_ubyte * 11),\n        ('left_trigger_effect', ctypes.c_ubyte * 11),\n\n        ('unknown3', ctypes.c_ubyte * 8),\n\n        ('lightbar_control', ctypes.c_ubyte),\n        ('lightbar_setup', ctypes.c_ubyte),\n        ('led_brightness', ctypes.c_ubyte),\n\n        ('player_leds', ctypes.c_ubyte),\n        ('lightbar_red', ctypes.c_ubyte),\n        ('lightbar_green', ctypes.c_ubyte),\n        ('lightbar_blue', ctypes.c_ubyte),\n    ]\n\nclass DualSenseHIDOutputBT(ctypes.Structure):\n    _fields_ = [\n        ('operating_mode', ctypes.c_byte),\n        ('data_id_byte', ctypes.c_byte),\n        ('physical_effect_control', ctypes.c_byte),\n        ('light_effect_control', ctypes.c_byte),\n\n        ('motor_right', ctypes.c_byte),\n        ('motor_left', ctypes.c_byte),\n\n        ('unknown2', ctypes.c_byte * 4),\n        ('mute_button_led', ctypes.c_byte),\n        ('power_save_control', ctypes.c_byte),\n        ('right_trigger_effect', ctypes.c_byte * 11),\n        ('left_trigger_effect', ctypes.c_byte * 11),\n\n        ('unknown3', ctypes.c_byte * 6),\n\n        ('brightlite', ctypes.c_byte),\n        ('unknown6', ctypes.c_byte * 2),\n\n        ('lightbar_control', ctypes.c_byte),\n        #('lightbar_setup', ctypes.c_byte),\n        ('led_brightness', ctypes.c_byte),\n\n        ('player_leds', ctypes.c_byte),\n        ('lightbar_red', ctypes.c_byte),\n        ('lightbar_green', ctypes.c_byte),\n        ('lightbar_blue', ctypes.c_byte),\n\n        ('unknown4', ctypes.c_byte * 25),\n        #('unknown5', ctypes.c_byte * 4),\n        ('crc32', ctypes.c_byte * 4),\n        #('crc32', ctypes.c_uint16),\n    ]\n\nclass DualSenseHIDInputBT(ctypes.Structure):\n    _fields_ = [\n        ('report_id', ctypes.c_byte),\n        ('unknown1', ctypes.c_byte),\n        ('lx', ctypes.c_ubyte),\n        ('ly', ctypes.c_ubyte),\n    ]\n\nclass DualSenseBTControllerInput(ctypes.Structure):\n\t_fields_ = [\n        ('type', ctypes.c_uint16),\n        ('buttons', ctypes.c_uint32),\n        ('ltrig', ctypes.c_uint8),\n        ('rtrig', ctypes.c_uint8),\n        ('stick_x', ctypes.c_int32),\n        ('stick_y', ctypes.c_int32),\n        ('lpad_x', ctypes.c_int32),\n        ('lpad_y', ctypes.c_int32),\n        ('rpad_x', ctypes.c_int32),\n        ('rpad_y', ctypes.c_int32),\n        ('accel_x', ctypes.c_int32),\n        ('accel_y', ctypes.c_int32),\n        ('accel_z', ctypes.c_int32),\n        ('gpitch', ctypes.c_int32),\n        ('groll', ctypes.c_int32),\n        ('gyaw', ctypes.c_int32),\n        ('q1', ctypes.c_int32),\n        ('q2', ctypes.c_int32),\n        ('q3', ctypes.c_int32),\n        ('q4', ctypes.c_int32),\n        ('cpad_x', ctypes.c_uint16),\n        ('cpad_y', ctypes.c_uint16),\n\t]\n\nICON_COLORS = [\n    (0.0, 1.0, 0.0),  # 0\n    (0.0, 0.0, 1.0),  # 1\n    (1.0, 0.0, 0.0),  # 2\n    (1.0, 1.0, 0.0),  # 3\n    (0.0, 1.0, 1.0),  # 4\n    (1.0, 0.4, 0.0),  # 5\n    (1.0, 0.0, 1.0),  # 6\n]\n\n\nclass DS5Controller(HIDController):\n    # Most of axes are the same\n    BUTTON_MAP = (\n        SCButtons.X,\n        SCButtons.A,\n        SCButtons.B,\n        SCButtons.Y,\n        SCButtons.LB,\n        SCButtons.RB,\n        1 << 64,\n        1 << 64,\n        SCButtons.BACK,\n        SCButtons.START,\n        SCButtons.STICKPRESS,\n        SCButtons.RPAD,\n        SCButtons.C,\n        SCButtons.CPADPRESS,\n    )\n\n    flags = (ControllerFlags.EUREL_GYROS\n             | ControllerFlags.HAS_RSTICK\n             | ControllerFlags.HAS_CPAD\n             | ControllerFlags.HAS_DPAD\n             | ControllerFlags.SEPARATE_STICK\n             | ControllerFlags.NO_GRIPS\n             )\n\n    def __init__(self, device, daemon, handle, config_file, config, test_mode=False):\n        self._outputs = {}\n        self._feedback_output = DualSenseHIDOutput(\n            operating_mode=OperatingMode.DS5_MODE,\n            physical_effect_control=PhysicalEffectControl.ENABLE_HAPTICS,\n            motor_left=0,\n            motor_right=0,\n        )\n        self._feedback_cancel_task = None\n        super(DS5Controller, self).__init__(device, daemon, handle, config_file, config, test_mode)\n\n    def _load_hid_descriptor(self, config, max_size, vid, pid, test_mode):\n        # Overrided and hardcoded\n        self._decoder = HIDDecoder()\n\n        # Dpad works on DualSense!\n        self._decoder.axes[AxisType.AXIS_LPAD_X] = AxisData(\n            mode=AxisMode.HATSWITCH, byte_offset=8, size=8,\n            data=AxisDataUnion(\n                hatswitch=HatswitchModeData(\n                    button=SCButtons.LPAD | SCButtons.LPADTOUCH,\n                    min=STICK_PAD_MIN, max=STICK_PAD_MAX\n                )\n            )\n        )\n\n        # Sticks are the same as DS4\n        self._decoder.axes[AxisType.AXIS_STICK_X] = AxisData(\n            mode=AxisMode.AXIS, byte_offset=1, size=8,\n            data=AxisDataUnion(\n                axis=AxisModeData(\n                    scale=1.0, offset=-127.5, clamp_max=257, deadzone=2\n                )\n            )\n        )\n        self._decoder.axes[AxisType.AXIS_STICK_Y] = AxisData(\n            mode=AxisMode.AXIS, byte_offset=2, size=8,\n            data=AxisDataUnion(\n                axis=AxisModeData(\n                    scale=-1.0, offset=127.5, clamp_max=257, deadzone=2\n                )\n            )\n        )\n        self._decoder.axes[AxisType.AXIS_RPAD_X] = AxisData(\n            mode=AxisMode.AXIS, byte_offset=3, size=8,\n            data=AxisDataUnion(\n                axis=AxisModeData(\n                    button=SCButtons.RPADTOUCH,\n                    scale=1.0, offset=-127.5, clamp_max=257, deadzone=2\n                )\n            )\n        )\n        self._decoder.axes[AxisType.AXIS_RPAD_Y] = AxisData(\n            mode=AxisMode.AXIS, byte_offset=4, size=8,\n            data=AxisDataUnion(\n                axis=AxisModeData(\n                    button=SCButtons.RPADTOUCH,\n                    scale=-1.0, offset=127.5, clamp_max=257, deadzone=2\n                )\n            )\n        )\n\n        # Triggers\n        self._decoder.axes[AxisType.AXIS_LTRIG] = AxisData(\n            mode=AxisMode.AXIS, byte_offset=5, size=8,  # Not sure about the size\n            data=AxisDataUnion(\n                axis=AxisModeData(\n                    scale=1.0, clamp_max=1, deadzone=10\n                )\n            )\n        )\n        self._decoder.axes[AxisType.AXIS_RTRIG] = AxisData(\n            mode=AxisMode.AXIS, byte_offset=6, size=8,  # Not sure about the size\n            data=AxisDataUnion(\n                axis=AxisModeData(\n                    scale=1.0, clamp_max=1, deadzone=10\n                )\n            )\n        )\n\n        # Gyro\n        # Leaving the AxisMode naming to match DS4\n        self._decoder.axes[AxisType.AXIS_GPITCH] = AxisData(\n            mode=AxisMode.DS4ACCEL, byte_offset=16\n        )  # Pitch found\n        self._decoder.axes[AxisType.AXIS_GROLL] = AxisData(\n            mode=AxisMode.DS4ACCEL, byte_offset=20\n        )  # Roll\n        self._decoder.axes[AxisType.AXIS_GYAW] = AxisData(\n            mode=AxisMode.DS4ACCEL, byte_offset=18\n        )  # Yaw found\n        self._decoder.axes[AxisType.AXIS_Q1] = AxisData(\n            mode=AxisMode.DS4GYRO, byte_offset=26\n        )\n        self._decoder.axes[AxisType.AXIS_Q2] = AxisData(\n            mode=AxisMode.DS4GYRO, byte_offset=22\n        )\n        self._decoder.axes[AxisType.AXIS_Q3] = AxisData(\n            mode=AxisMode.DS4GYRO, byte_offset=24\n        )\n\n        # Touchpad\n        self._decoder.axes[AxisType.AXIS_CPAD_X] = AxisData(\n            mode=AxisMode.DS4TOUCHPAD, byte_offset=34\n        )  # DualSense X\n        self._decoder.axes[AxisType.AXIS_CPAD_Y] = AxisData(\n            mode=AxisMode.DS4TOUCHPAD, byte_offset=35, bit_offset=4\n        )  # DualSense Y\n\n        # Button maps seem to work for standard arrangement (matching Xbox360)\n        # Not enough information about the button event triggered when LT && RT are pressed?\n        # Could be connected to adaptive triggers?\n        self._decoder.buttons = ButtonData(\n            enabled=True, byte_offset=8, bit_offset=4, size=14,  # Not sure about bit offset\n            button_count=14\n        )\n\n        if test_mode:\n            for x in range(BUTTON_COUNT):\n                self._decoder.buttons.button_map[x] = x\n        else:\n            for x in range(BUTTON_COUNT):\n                self._decoder.buttons.button_map[x] = 64\n            for x, sc in enumerate(DS5Controller.BUTTON_MAP):\n                self._decoder.buttons.button_map[x] = self.button_to_bit(sc)\n\n        self._packet_size = 64\n\n    def input(self, endpoint, data):\n        # Special override for CPAD touch button\n        if _lib.decode(ctypes.byref(self._decoder), data):\n            if self.mapper:\n                if data[33] >> 7:\n                    # cpad is not touched\n                    self._decoder.state.buttons &= ~SCButtons.CPADTOUCH\n                else:\n                    self._decoder.state.buttons |= SCButtons.CPADTOUCH\n                self.mapper.input(\n                    self,\n                    self._decoder.old_state, self._decoder.state\n                )\n\n    def feedback(self, data):\n        position, amplitude, period, count = data.data\n\n        normalized_amp = float(amplitude) / 0x8000\n        clamped_amp = int(normalized_amp * 0xff)\n        half_amp = int(normalized_amp * 0x80)\n\n        if position == HapticPos.LEFT:\n            # NOTE: the left motor is heavier, so we must give it less oomph\n            self._feedback_output.motor_left = half_amp\n        elif position == HapticPos.RIGHT:\n            self._feedback_output.motor_right = clamped_amp\n        elif position == HapticPos.BOTH:\n            self._feedback_output.motor_right = clamped_amp\n            self._feedback_output.motor_left = half_amp\n\n        duration = float(period) * count / 0x10000\n        # The motors don't seem to perform reliably when shut off under 20ms\n        duration = max(duration, 0.02)\n\n        self.schedule_output('feedback', self._feedback_output)\n\n        def clear_feedback(mapper):\n            self._feedback_output.motor_right = self._feedback_output.motor_left = 0\n            self.schedule_output('feedback', self._feedback_output)\n\n        if self._feedback_cancel_task:\n            self._feedback_cancel_task.cancel()\n        self._feedback_cancel_task = self.mapper.schedule(duration, clear_feedback)\n\n    def apply_config(self, config):\n        icon = config['icon']\n        led_level = config['led_level']\n        self.configure(icon=icon, led_level=led_level)\n\n    def configure(self, icon=None, led_level=100):\n        lightbar_color = (0.0, 0.0, 1.0)  # blue by default\n        if icon:\n            basename, ext = icon.rsplit('.', 1)\n            parts = basename.rsplit('-', 1)\n            if parts:\n                raw_idx = parts[-1]\n                try:\n                    icon_idx = int(raw_idx)\n                except ValueError:\n                    pass\n                else:\n                    if icon_idx < len(ICON_COLORS):\n                        lightbar_color = ICON_COLORS[icon_idx]\n\n        led_level_norm = float(led_level) / 100\n        lightbar_color_bytes = tuple(\n            int(color_norm * led_level_norm * 255)\n            for color_norm in lightbar_color\n        )\n\n        output = DualSenseHIDOutput(\n            operating_mode=OperatingMode.DS5_MODE,\n            light_effect_control=LightEffectControl.LIGHTBAR_CONTROL_ENABLE,\n            lightbar_red=lightbar_color_bytes[0],\n            lightbar_green=lightbar_color_bytes[1],\n            lightbar_blue=lightbar_color_bytes[2],\n        )\n        self.schedule_output('lightbar', output)\n\n    def get_gyro_enabled(self):\n        # Cannot be actually turned off, so it's always active\n        # TODO: Maybe emulate turning off?\n        return True\n\n    def get_type(self):\n        return \"ds5\"\n\n    def get_gui_config_file(self):\n        return \"ds5-config.json\"\n\n    def __repr__(self):\n        return \"<DS5Controller %s>\" % (self.get_id(),)\n\n    def _generate_id(self):\n        \"\"\"\n        ID is generated as 'ds5' or 'ds5:X' where 'X' starts as 1 and increases\n        as controllers with same ids are connected.\n        \"\"\"\n        magic_number = 1\n        id = \"ds5\"\n        while id in self.daemon.get_active_ids():\n            id = \"ds5:%s\" % (magic_number,)\n            magic_number += 1\n        return id\n\n    def schedule_output(self, output_id, output):\n        self._outputs[output_id] = output\n\n    def flush(self):\n        super(DS5Controller, self).flush()\n\n        while self._outputs:\n            output_id, output = self._outputs.popitem()\n            data = bytes(bytearray(output).ljust(64, b'\\x00'))\n            self.handle.interruptWrite(3, data)\n\n\nclass DS5HidRawDriver:\n    def __init__(self, daemon, config):\n        self.config = config\n        self.daemon = daemon\n        daemon.get_device_monitor().add_callback(\"bluetooth\", VENDOR_ID, PRODUCT_ID, self.make_bt_hidraw_callback, None)\n\n    def retry(self, syspath):\n        pass\n\n    def make_bt_hidraw_callback(self, syspath, *whatever):\n        hidrawname = self.daemon.get_device_monitor().get_hidraw(syspath)\n        if hidrawname is None:\n            return None\n\n        #log.debug(whatever)\n        try:\n            dev = HIDRaw(open(os.path.join(\"/dev/\", hidrawname), \"w+b\"))\n            return DS5HidRawController(self, syspath, dev)\n        except Exception as e:\n            log.exception(e)\n            return None\n\n\nclass DS5HidRawController(Controller):\n    class _DPadOutputValues:\n        def __init__(self, x, y):\n            self.x = x\n            self.y = y\n\n    \"\"\"# (x, y) values\n    DPAD_STATE_TYPES = {\n        0: (0, STICK_PAD_MAX), # Up\n        1: (STICK_PAD_MAX, STICK_PAD_MAX), # UpRight\n        2: (STICK_PAD_MAX, 0), # Right\n        3: (STICK_PAD_MAX, STICK_PAD_MIN), # DownRight\n        4: (0, STICK_PAD_MIN), # Down\n        5: (STICK_PAD_MIN, STICK_PAD_MIN), # DownLeft\n        6: (STICK_PAD_MIN, 0), # Left\n        7: (STICK_PAD_MIN, STICK_PAD_MAX), # UpLeft\n        8: (0, 0) # Centered\n    }\n    \"\"\"\n\n    # (x, y) values\n    DPAD_STATE_TYPES = {\n        0: _DPadOutputValues(0, STICK_PAD_MAX), # Up\n        1: _DPadOutputValues(STICK_PAD_MAX, STICK_PAD_MAX), # UpRight\n        2: _DPadOutputValues(STICK_PAD_MAX, 0), # Right\n        3: _DPadOutputValues(STICK_PAD_MAX, STICK_PAD_MIN), # DownRight\n        4: _DPadOutputValues(0, STICK_PAD_MIN), # Down\n        5: _DPadOutputValues(STICK_PAD_MIN, STICK_PAD_MIN), # DownLeft\n        6: _DPadOutputValues(STICK_PAD_MIN, 0), # Left\n        7: _DPadOutputValues(STICK_PAD_MIN, STICK_PAD_MAX), # UpLeft\n        8: _DPadOutputValues(0, 0) # Centered\n    }\n\n    DPAD_CENTERED_STATE = _DPadOutputValues(0, 0)\n    # Leading byte not included in sent output report data.\n    # Needed to add for proper CRC32 computation that controller\n    # will accept\n    BT_CRC32_HEAD = b\"\\xA2\"\n    # Leading byte not included in feature report data.\n    # Needed if performing CRC32 computation on returned feature\n    # report data (for IMU calibration data)\n    BT_CALIBRATION_CRC32_HEAD = b\"\\xA3\"\n\n    flags = ( ControllerFlags.EUREL_GYROS\n\t\t\t| ControllerFlags.HAS_RSTICK\n\t\t\t| ControllerFlags.HAS_CPAD\n\t\t\t| ControllerFlags.HAS_DPAD\n\t\t\t| ControllerFlags.SEPARATE_STICK\n\t\t\t| ControllerFlags.NO_GRIPS\n\t)\n\n    #def __init__(self, device, daemon, handle, config_file, config, test_mode=False):\n    def __init__(self, driver, syspath, hidrawdev):\n        self.driver = driver\n        self.daemon = driver.daemon\n        self.syspath = syspath\n\n        super().__init__()\n        #super().__init__(device, daemon, handle, config_file, config, test_mode=False)\n\n        self._feedback_output = DualSenseHIDOutputBT(\n            operating_mode=OperatingMode.DS5_MODE_BT,\n            data_id_byte = 0x02,\n            physical_effect_control=PhysicalEffectControl.ENABLE_HAPTICS,\n            motor_left=0,\n            motor_right=0,\n        )\n        self._feedback_cancel_task = None\n        self._outputs = {}\n        # Use empty struct for starting state\n        self._old_state = DualSenseBTControllerInput()\n\n        self._device_name = hidrawdev.getName()\n        self._hidrawdev = hidrawdev\n        self._fileno = hidrawdev._device.fileno()\n        self._id = self._generate_id() if driver else \"-\"\n        self._previous_quat = [1.0, 0.0, 0.0, 0.0]\n        self._delta_time = time.time()\n        self._previous_time = time.time()\n\n        #time.sleep(1)\n        self._set_operational()\n        self.read_serial()\n        #self.configure()\n        self._poller = self.daemon.get_poller()\n        if self._poller:\n            self._poller.register(self._fileno, self._poller.POLLIN, self._input)\n        self.daemon.get_device_monitor().add_remove_callback(syspath, self.close)\n        self.daemon.add_controller(self)\n\n    def get_device_name(self):\n        return \"DualSense over Bluetooth HIDRaw\"\n\n    def get_type(self):\n        return \"ds5bt_hidraw\"\n\n    def apply_config(self, config):\n        icon = config['icon']\n        led_level = config['led_level']\n        self.configure(icon=icon, led_level=led_level)\n\n    def configure(self, icon=None, led_level=100):\n        #log.debug(\"CALLED CONFIGURE\")\n        #return\n\n        lightbar_color = (0.0, 0.0, 1.0)  # blue by default\n        if icon:\n            basename, ext = icon.rsplit('.', 1)\n            parts = basename.rsplit('-', 1)\n            if parts:\n                raw_idx = parts[-1]\n                try:\n                    icon_idx = int(raw_idx)\n                except ValueError:\n                    pass\n                else:\n                    if icon_idx < len(ICON_COLORS):\n                        lightbar_color = ICON_COLORS[icon_idx]\n\n        led_level_norm = float(led_level) / 100\n        lightbar_color_bytes = tuple(\n            int(color_norm * led_level_norm * 255)\n            for color_norm in lightbar_color\n        )\n\n        #print(lightbar_color_bytes)\n        #lightbar_red=lightbar_color_bytes[0],\n        #lightbar_green=lightbar_color_bytes[1],\n        #lightbar_blue=lightbar_color_bytes[2],\n        output = DualSenseHIDOutputBT(\n            operating_mode=OperatingMode.DS5_MODE_BT,\n            data_id_byte = 0x02,\n            light_effect_control=LightEffectControl.LIGHTBAR_CONTROL_ENABLE,\n            lightbar_red=lightbar_color_bytes[0],\n            lightbar_green=lightbar_color_bytes[1],\n            lightbar_blue=lightbar_color_bytes[2],\n        )\n\n        tempbuffer = bytearray(output)\n        #print(\"ITS A BLUE: {}\".format(output.lightbar_blue))\n        self._prepare_buffer_crc(tempbuffer)\n        self.schedule_output('lightbar', tempbuffer)\n        #time.sleep(2)\n        self.flush()\n        #self._hidrawdev.read(78)\n        #feature_data = self._hidrawdev.getFeatureReport(9)\n        #time.sleep(2)\n        #self._hidrawdev.read(78)\n\n    def _set_operational(self):\n        #log.debug(\"CALLING SET_OPERATIONAL\")\n        # Get feature report for serial performs initial switch\n        # to DS5 mode\n        feature_data = self._hidrawdev.getFeatureReport(9)\n\n        init_output = DualSenseHIDOutputBT(\n            operating_mode = OperatingMode.DS5_MODE_BT,\n            data_id_byte = 0x02,\n            light_effect_control=0x55,\n            lightbar_red=0,\n            lightbar_green=0,\n            lightbar_blue=200,\n        )\n\n        tempman = bytearray(init_output)\n        \"\"\"calcCrc32 = zlib.crc32(b\"\\xA2\") & 0xFFFFFFFF\n        calcCrc32 = zlib.crc32(tempman[0:74], calcCrc32) & 0xFFFFFFFF\n        tempman[74] = calcCrc32 & 0xFF\n        tempman[75] = (calcCrc32 >> 8) & 0xFF\n        tempman[76] = (calcCrc32 >> 16) & 0xFF\n        tempman[77] = (calcCrc32 >> 24) & 0xFF\n        \"\"\"\n        #self._hidrawdev.write(tempman)\n        #time.sleep(1)\n        self._prepare_buffer_crc(tempman)\n        self.schedule_output(\"init\", tempman)\n        self.flush()\n        # Seems to not register until the next device read. Need\n        # to see if there is a way around that\n        self._hidrawdev.read(78)\n        #feature_data = self._hidrawdev.getFeatureReport(9)\n        #time.sleep(2)\n\n    def _prepare_buffer_crc(self, buf):\n        calcCrc32 = zlib.crc32(b\"\\xA2\") & 0xFFFFFFFF\n        calcCrc32 = zlib.crc32(buf[0:74], calcCrc32) & 0xFFFFFFFF\n        buf[74] = calcCrc32 & 0xFF\n        buf[75] = (calcCrc32 >> 8) & 0xFF\n        buf[76] = (calcCrc32 >> 16) & 0xFF\n        buf[77] = (calcCrc32 >> 24) & 0xFF\n\n    def feedback(self, data):\n        position, amplitude, period, count = data.data\n\n        normalized_amp = float(amplitude) / 0x8000\n        clamped_amp = int(normalized_amp * 0xff)\n        half_amp = int(normalized_amp * 0x80)\n\n        if position == HapticPos.LEFT:\n            # NOTE: the left motor is heavier, so we must give it less oomph\n            self._feedback_output.motor_left = half_amp\n        elif position == HapticPos.RIGHT:\n            self._feedback_output.motor_right = clamped_amp\n        elif position == HapticPos.BOTH:\n            self._feedback_output.motor_right = clamped_amp\n            self._feedback_output.motor_left = half_amp\n\n        duration = float(period) * count / 0x10000\n        # The motors don't seem to perform reliably when shut off under 50ms\n        duration = max(duration, 0.05)\n\n        tempman = bytearray(self._feedback_output)\n        self._prepare_buffer_crc(tempman)\n        self.schedule_output('feedback', tempman)\n        self.flush()\n\n        def clear_feedback(mapper):\n            self._feedback_output.motor_right = self._feedback_output.motor_left = 0\n            tempman = bytearray(self._feedback_output)\n            self._prepare_buffer_crc(tempman)\n            self.schedule_output('feedback', tempman)\n            self.flush()\n\n        if self._feedback_cancel_task:\n            self._feedback_cancel_task.cancel()\n        self._feedback_cancel_task = self.mapper.schedule(duration, clear_feedback)\n\n    def _input(self, *a):\n        #log.debug(\"FOUND INPUT\")\n        tempdata = self._hidrawdev.read(78)\n        # Skip over packet if not a DS5 mode input packet\n        if tempdata[0] != 0x31:\n            return\n\n        #log.debug(tempdata)\n        old_state = self._old_state\n        current_time = time.time()\n        self._delta_time = current_time - self._previous_time\n        #hamtaro = DualSenseHIDInputBT.from_buffer_copy(tempdata)\n        #log.debug(\"LX: {} {}\".format(hamtaro.lx, tempdata[2]))\n        state_data = self._convert_input_data(tempdata)\n        \"\"\"print(\"INPUT [\", end=\"\")\n        for index, value in enumerate(tempdata):\n            print(\"[{},{}]\".format(index, value), end=\", \")\n\n        print(\"\")\n        \"\"\"\n\n        #log.debug(self._hidrawdev._device)\n        #self.flush()\n        #self.close()\n        if self.mapper:\n            self.mapper.input(self, old_state, state_data)\n\n        self._old_state = state_data\n        # Check for pending output data\n        self.flush()\n\n        self._previous_time = current_time\n\n    def _convert_input_data(self, data):\n        state = DualSenseBTControllerInput()\n        state.stick_x = self._stick_axis_scale(data[2], False)\n        state.stick_y = self._stick_axis_scale(data[3], True)\n        state.rpad_x = self._stick_axis_scale(data[4], False)\n        state.rpad_y = self._stick_axis_scale(data[5], True)\n        state.ltrig = data[6]\n        state.rtrig = data[7]\n        tempbyte = data[9]\n        if (tempbyte & (1 << 7)) != 0:\n            state.buttons |= SCButtons.Y\n        if (tempbyte & (1 << 6)) != 0:\n            state.buttons |= SCButtons.B\n        if (tempbyte & (1 << 5)) != 0:\n            state.buttons |= SCButtons.A\n        if (tempbyte & (1 << 4)) != 0:\n            state.buttons |= SCButtons.X\n        dpad_state = data[9] & 0x0F\n        if (dpad_state != 8):\n            state.buttons |= SCButtons.LPAD | SCButtons.LPADTOUCH\n            tempDPad = DS5HidRawController.DPAD_STATE_TYPES.get(dpad_state,\n                DS5HidRawController.DPAD_CENTERED_STATE)\n            state.lpad_x = tempDPad.x\n            state.lpad_y = tempDPad.y\n\n        tempbyte = data[10]\n        if (tempbyte & (1 << 7)) != 0:\n            state.buttons |= SCButtons.RPAD\n        if (tempbyte & (1 << 6)) != 0:\n            state.buttons |= SCButtons.STICKPRESS\n        if (tempbyte & (1 << 5)) != 0:\n            state.buttons |= SCButtons.START\n        if (tempbyte & (1 << 4)) != 0:\n            state.buttons |= SCButtons.BACK\n        if (tempbyte & (1 << 3)) != 0:\n            state.buttons |= SCButtons.RT\n        if (tempbyte & (1 << 2)) != 0:\n            state.buttons |= SCButtons.LT\n        if (tempbyte & (1 << 1)) != 0:\n            state.buttons |= SCButtons.RB\n        if (tempbyte & (1 << 0)) != 0:\n            state.buttons |= SCButtons.LB\n\n        tempbyte = data[11]\n        if (tempbyte & (1 << 0)) != 0:\n            state.buttons |= SCButtons.C\n        if (tempbyte & (1 << 1)) != 0:\n            state.buttons |= SCButtons.CPADPRESS\n\n        # Change gyro dir values to match Steam Controller\n        state.gpitch = ctypes.c_int16((data[18] << 8) | data[17]).value\n        state.gyaw = ctypes.c_int16((data[20] << 8) | data[19]).value * -1\n        state.groll = ctypes.c_int16((data[22] << 8) | data[21]).value * -1\n\n        # Change accel axes to match Steam Controller (flip pitch and roll)\n        # Scale values for 2G instead of 1G\n        state.accel_x = ctypes.c_int16((data[24] << 8) | data[23]).value * 2\n        # Invert pitch\n        state.accel_y = ctypes.c_int16((data[28] << 8) | data[27]).value * -2\n        state.accel_z = ctypes.c_int16((data[26] << 8) | data[25]).value * 2\n        #print(\"GYRO: {} {} {}\".format(state.gyaw, state.gpitch, state.groll))\n        #print(\"ACCEL: {} | {} | {}\".format(state.accel_x, state.accel_y, state.accel_z))\n        # Calculate quaternion for gyro data. Needed for tilt controls output.\n        # TODO: Try to add sensor fusion and complementary filter later\n        self._calculate_quaternion(state)\n\n        # Check for CPAD touch\n        if (data[34] & 0x80) == 0:\n            state.buttons |= SCButtons.CPADTOUCH\n\n        state.cpad_x = ((data[36] & 0x0F) << 8) | data[35]\n        state.cpad_y = ((data[37] & 0x0F) << 4) | ((data[36] & 0xF0) >> 4)\n\n        return state\n\n    def _stick_axis_scale(self, value, invert=False, test=False):\n        result = value - 128\n        tempRatio = (result / 127.0) if (value >= 128) else (result / (128.0))\n        if invert:\n            tempRatio = -tempRatio\n\n        tempRatio = (tempRatio + 1.0) * 0.5\n\n        \"\"\"if test:\n            print(tempRatio)\n            print(\"RES {}\".format(STICK_PAD_RES))\n            print(tempRatio * STICK_PAD_RES + STICK_PAD_MIN)\n        \"\"\"\n        result = int(tempRatio * STICK_PAD_RES + STICK_PAD_MIN)\n        return result\n\n    def _calculate_quaternion(self, state):\n        # Convert raw gyro values to degrees per second\n        GYRO_RES_IN_DEG_SEC = 16\n        (yaw, pitch, roll) = ((state.gyaw / GYRO_RES_IN_DEG_SEC),\n            (state.gpitch / GYRO_RES_IN_DEG_SEC),\n            (state.groll / GYRO_RES_IN_DEG_SEC))\n\n        # Remove time delta element to get gyro angles and convert to radians\n        old_yaw = yaw\n        yaw = yaw * self._delta_time\n        yaw_rad = yaw * math.pi / 180.0\n        pitch = pitch * self._delta_time\n        pitch_rad = pitch *  math.pi / 180.0\n        roll = roll * self._delta_time\n        roll_rad = roll * math.pi / 180.0\n        #print(\"GYRO: {} {} {}\".format(yaw, pitch, roll))\n        #print(\"GYRO: {} | {} | {} | {} | {}\".format(state.gyaw, old_yaw, yaw, yaw_rad, self._delta_time))\n\n        # Obtain current quaternion\n        # qx (Roll), qy (Pitch), qz (Yaw), qw (Theta)\n        qx = math.sin(roll_rad/2) * math.cos(pitch_rad/2) * math.cos(yaw_rad/2) - math.cos(roll_rad/2) * math.sin(pitch_rad/2) * math.sin(yaw_rad/2)\n        qy = math.cos(roll_rad/2) * math.sin(pitch_rad/2) * math.cos(yaw_rad/2) + math.sin(roll_rad/2) * math.cos(pitch_rad/2) * math.sin(yaw_rad/2)\n        qz = math.cos(roll_rad/2) * math.cos(pitch_rad/2) * math.sin(yaw_rad/2) - math.sin(roll_rad/2) * math.sin(pitch_rad/2) * math.cos(yaw_rad/2)\n        qw = math.cos(roll_rad/2) * math.cos(pitch_rad/2) * math.cos(yaw_rad/2) + math.sin(roll_rad/2) * math.sin(pitch_rad/2) * math.sin(yaw_rad/2)\n\n        # Multiply previous calculated quaternion by new quaternion\n        (old_qw, old_qx, old_qy, old_qz) = self._previous_quat\n        Q0Q1_w = old_qw * qw - old_qx * qx - old_qy * qy - old_qz * qz\n        Q0Q1_x = old_qw * qx + old_qx * qw + old_qy * qz - old_qz * qy\n        Q0Q1_y = old_qw * qy - old_qx * qz + old_qy * qw + old_qz * qx\n        Q0Q1_z = old_qw * qz + old_qx * qy - old_qy * qx + old_qz * qw\n\n        # Convert normalized values to mapper expected range and store\n        # in state object\n        # q1 (Theta), q2 (Pitch), q3 (Roll), q4 (Yaw)\n        state.q1 = int(Q0Q1_w * 32767.0)\n        state.q2 = int(Q0Q1_y * 32767.0)\n        state.q3 = int(Q0Q1_x * 32767.0)\n        # Invert Yaw to match Steam Controller\n        state.q4 = int(Q0Q1_z * -32767.0)\n        # Store calculated quaternion for next poll\n        self._previous_quat = [Q0Q1_w, Q0Q1_x, Q0Q1_y, Q0Q1_z]\n        #print(\"TEST QUAT: {}\".format(self._previous_quat))\n        #print(\"TEST QUAT Z: {}\".format(self._previous_quat[3] * -1))\n\n    def close(self):\n        if self._poller:\n            self._poller.unregister(self._fileno)\n\n        self.daemon.remove_controller(self)\n        self._hidrawdev._device.close()\n        #log.debug(\"CLOSING\")\n\n    def read_serial(self):\n        self._serial = (self._hidrawdev\n            .getPhysicalAddress().replace(b\":\", b\"\"))\n\n    def schedule_output(self, output_id, output):\n        self._outputs[output_id] = output\n\n    def flush(self):\n        while self._outputs:\n            output_id, output = self._outputs.popitem()\n            #print(\"PAYLOAD {} {}\".format(output_id, output))\n            self._hidrawdev.write(output)\n            #time.sleep(0.1)\n            #print(\"\")\n\n        #print(\"SLEEPING\")\n        #time.sleep(0.5)\n\n    # TODO: Remove. Temporarily keep as a reference to the nonsense\n    # I went through to figure out how to compute valid CRC32 that\n    # the controller would accept\n    def flushni(self):\n        output = []\n        print(hex(zlib.crc32(b\"hello-world\") & 0xffffffff))\n\n        feedback_output2 = DualSenseHIDOutputBT(\n            operating_mode = 0x31,\n            data_id_byte = 0x02,\n            physical_effect_control = 0x0F,\n            light_effect_control=0x55,\n            motor_right = 0x00,\n            brightlite = 0x06,\n            lightbar_control = 0x02,\n            led_brightness = 0x02,\n            lightbar_red=200,\n            lightbar_green=90,\n            lightbar_blue=211,\n        )\n\n        calcCrc32 = zlib.crc32(b\"\\xA2\") & 0xFFFFFFFF\n        #calcCrc32 = ~zlib.crc32(b\"\\x31\", calcCrc32)\n        log.debug(\"CRC BETA\")\n        log.debug(calcCrc32)\n\n        log.debug(len(bytearray(feedback_output2)[0:74]))\n        damn = [int(i) for i in bytearray(feedback_output2)[0:78]]\n        log.debug(damn)\n        # Working\n        #tempman = bytearray([0x31, 0x02, 0x0f, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x02, 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])\n        tempman = bytearray(feedback_output2)\n        # Working\n        #calcCrc32 = binascii.crc32(bytearray([0x31, 0x02, 0x0f, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x02, 0x00, 0x14, 0xFF, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), ~calcCrc32)\n        #calcCrc32 = binascii.crc32(tempman[0:74], ~calcCrc32)\n        calcCrc32 = zlib.crc32(tempman[0:74], calcCrc32) & 0xFFFFFFFF\n        #_feedback_output.crc32 = calcCrc32\n        #_feedback_output.crc32[0] = calcCrc32\n        #_feedback_output.crc32[1] = calcCrc32 >> 8\n        #_feedback_output.crc32[2] = calcCrc32 >> 16\n        #_feedback_output.crc32[3] = calcCrc32 >> 24\n        tempman[74] = calcCrc32 & 0xFF\n        tempman[75] = (calcCrc32 >> 8) & 0xFF\n        tempman[76] = (calcCrc32 >> 16) & 0xFF\n        tempman[77] = (calcCrc32 >> 24) & 0xFF\n        log.debug(\"CRC\")\n        log.debug(calcCrc32)\n\n        print(\"RADIO EDIT\")\n        #print(bytearray(_feedback_output))\n        \"\"\"print(\"[\", end=\"\")\n        for index, value in enumerate(tempman):\n            print(\"[{},{}]\".format(index, value), end=\", \")\n\n        print(\"\")\n        \"\"\"\n\n        #_test = bytes(_feedback_output)\n        #_test = bytearray([0x31, 0x02, 0x0f, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x02, 0x02, 0x00, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])\n        #_test[74] = calcCrc32 & 0xFF\n        #_test[75] = (calcCrc32 >> 8) & 0xFF\n        #_test[76] = (calcCrc32 >> 16) & 0xFF\n        #_test[77] = (calcCrc32 >> 24) & 0xFF\n        #print(len(_test))\n        #print(\"THE THING {} {} {}\".format(_test[40], _test[46], _test[77]))\n\n        result = self._hidrawdev.write(tempman)\n        time.sleep(0.1)\n        print(result)\n\n    def get_type(self):\n        return \"ds5\"\n\n    def get_gui_config_file(self):\n        return \"ds5-config.json\"\n\n    def _generate_id(self):\n        \"\"\"\n        ID is generated as 'ds5' or 'ds5:X' where 'X' starts as 1 and increases\n        as controllers with same ids are connected.\n        \"\"\"\n        magic_number = 1\n        id = \"ds5\"\n        while id in self.daemon.get_active_ids():\n            id = \"ds5:%s\" % (magic_number,)\n            magic_number += 1\n        return id\n\n    def get_gyro_enabled(self):\n        # Cannot be actually turned off, so it's always active\n        # TODO: Maybe emulate turning off?\n        return True\n\n\nclass DS5EvdevController(EvdevController):\n    TOUCH_FACTOR_X = STICK_PAD_MAX / 940.0\n    TOUCH_FACTOR_Y = STICK_PAD_MAX / 470.0\n    BUTTON_MAP = {\n        304: \"A\",\n        305: \"B\",\n        307: \"Y\",\n        308: \"X\",\n        310: \"LB\",\n        311: \"RB\",\n        # TODO: Figure out what it is the purpose of the button event when using the trigger\n        # 312: \"LT2\",\n        # 313: \"RT2\",\n        314: \"BACK\",\n        315: \"START\",\n        316: \"C\",\n        317: \"STICKPRESS\",\n        318: \"RPAD\"\n        # 319: \"CPAD\",\n    }\n    AXIS_MAP = {\n        0: {\"axis\": \"stick_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0},\n        1: {\"axis\": \"stick_y\", \"deadzone\": 4, \"max\": 0, \"min\": 255},\n        3: {\"axis\": \"rpad_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0},\n        4: {\"axis\": \"rpad_y\", \"deadzone\": 8, \"max\": 0, \"min\": 255},\n        2: {\"axis\": \"ltrig\", \"max\": 255, \"min\": 0},\n        5: {\"axis\": \"rtrig\", \"max\": 255, \"min\": 0},\n        16: {\"axis\": \"lpad_x\", \"deadzone\": 0, \"max\": 1, \"min\": -1},\n        17: {\"axis\": \"lpad_y\", \"deadzone\": 0, \"max\": -1, \"min\": 1}\n    }\n    # TODO: Should the old button for DS4 map be removed? DualSense support came with kernel 5.12\n    # BUTTON_MAP_OLD = {\n    # \t304: \"X\",\n    # \t305: \"A\",\n    # \t306: \"B\",\n    # \t307: \"Y\",\n    # \t308: \"LB\",\n    # \t309: \"RB\",\n    # \t312: \"BACK\",\n    # \t313: \"START\",\n    # \t314: \"STICKPRESS\",\n    # \t315: \"RPAD\",\n    # \t316: \"C\",\n    # \t# 317: \"CPAD\",\n    # }\n    # AXIS_MAP_OLD = {\n    # \t0:  { \"axis\": \"stick_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0 },\n    # \t1:  { \"axis\": \"stick_y\", \"deadzone\": 4, \"max\": 0, \"min\": 255 },\n    # \t2:  { \"axis\": \"rpad_x\", \"deadzone\": 4, \"max\": 255, \"min\": 0 },\n    # \t5:  { \"axis\": \"rpad_y\", \"deadzone\": 8, \"max\": 0, \"min\": 255 },\n    # \t3:  { \"axis\": \"ltrig\", \"max\": 32767, \"min\": -32767 },\n    # \t4:  { \"axis\": \"rtrig\", \"max\": 32767, \"min\": -32767 },\n    # \t16: { \"axis\": \"lpad_x\", \"deadzone\": 0, \"max\": 1, \"min\": -1 },\n    # \t17: { \"axis\": \"lpad_y\", \"deadzone\": 0, \"max\": -1, \"min\": 1 }\n    # }\n    GYRO_MAP = {\n        EvdevController.ECODES.ABS_RX: ('gpitch', 0.01),\n        EvdevController.ECODES.ABS_RY: ('gyaw', 0.01),\n        EvdevController.ECODES.ABS_RZ: ('groll', 0.01),\n        EvdevController.ECODES.ABS_X: (None, 1),  # 'q2'\n        EvdevController.ECODES.ABS_Y: (None, 1),  # 'q3'\n        EvdevController.ECODES.ABS_Z: (None, -1),  # 'q1'\n    }\n    flags = (ControllerFlags.EUREL_GYROS\n             | ControllerFlags.HAS_RSTICK\n             | ControllerFlags.HAS_CPAD\n             | ControllerFlags.HAS_DPAD\n             | ControllerFlags.SEPARATE_STICK\n             | ControllerFlags.NO_GRIPS\n             )\n\n    def __init__(self, daemon, controllerdevice, gyro, touchpad):\n        config = {\n            'axes': DS5EvdevController.AXIS_MAP,\n            'buttons': DS5EvdevController.BUTTON_MAP,\n            'dpads': {}\n        }\n        # if controllerdevice.info.version & 0x8000 == 0:\n        # \t# Older kernel uses different mappings\n        # \t# see kernel source, drivers/hid/hid-sony.c#L2748\n        # \tconfig['axes'] = DS4EvdevController.AXIS_MAP_OLD\n        # \tconfig['buttons'] = DS4EvdevController.BUTTON_MAP_OLD\n        self._gyro = gyro\n        self._touchpad = touchpad\n        for device in (self._gyro, self._touchpad):\n            if device:\n                device.grab()\n        EvdevController.__init__(self, daemon, controllerdevice, None, config)\n        if self.poller:\n            self.poller.register(touchpad.fd, self.poller.POLLIN, self._touchpad_input)\n            self.poller.register(gyro.fd, self.poller.POLLIN, self._gyro_input)\n\n    def _gyro_input(self, *a):\n        new_state = self._state\n        try:\n            for event in self._gyro.read():\n                if event.type == self.ECODES.EV_ABS:\n                    axis, factor = DS5EvdevController.GYRO_MAP[event.code]\n                    if axis:\n                        new_state = new_state._replace(\n                            **{axis: int(event.value * factor)}\n                        )\n        except IOError:\n            # Errors here are not even reported, evdev class handles important ones\n            return\n\n        if new_state is not self._state:\n            old_state, self._state = self._state, new_state\n            if self.mapper:\n                self.mapper.input(self, old_state, new_state)\n\n    def _touchpad_input(self, *a):\n        new_state = self._state\n        try:\n            for event in self._touchpad.read():\n                if event.type == self.ECODES.EV_ABS:\n                    if event.code == self.ECODES.ABS_MT_POSITION_X:\n                        value = event.value * DS5EvdevController.TOUCH_FACTOR_X\n                        value = STICK_PAD_MIN + int(value)\n                        new_state = new_state._replace(cpad_x=value)\n                    elif event.code == self.ECODES.ABS_MT_POSITION_Y:\n                        value = event.value * DS5EvdevController.TOUCH_FACTOR_Y\n                        value = STICK_PAD_MAX - int(value)\n                        new_state = new_state._replace(cpad_y=value)\n                elif event.type == 0:\n                    pass\n                elif event.code == self.ECODES.BTN_LEFT:\n                    if event.value == 1:\n                        b = new_state.buttons | SCButtons.CPADPRESS\n                        new_state = new_state._replace(buttons=b)\n                    else:\n                        b = new_state.buttons & ~SCButtons.CPADPRESS\n                        new_state = new_state._replace(buttons=b)\n                elif event.code == self.ECODES.BTN_TOUCH:\n                    if event.value == 1:\n                        b = new_state.buttons | SCButtons.CPADTOUCH\n                        new_state = new_state._replace(buttons=b)\n                    else:\n                        b = new_state.buttons & ~SCButtons.CPADTOUCH\n                        new_state = new_state._replace(\n                            buttons=b,\n                            cpad_x=0, cpad_y=0\n                            )\n        except IOError:\n            # Errors here are not even reported, evdev class handles important ones\n            return\n\n        if new_state is not self._state:\n            old_state, self._state = self._state, new_state\n            if self.mapper:\n                self.mapper.input(self, old_state, new_state)\n\n    def close(self):\n        EvdevController.close(self)\n        for device in (self._gyro, self._touchpad):\n            try:\n                self.poller.unregister(device.fd)\n                device.ungrab()\n            except:\n                pass\n\n    def get_gyro_enabled(self):\n        # Cannot be actually turned off, so it's always active\n        # TODO: Maybe emulate turning off?\n        return True\n\n    def get_type(self):\n        return \"ds5evdev\"\n\n    # TODO: Create ds5-config.json for GUI\n    def get_gui_config_file(self):\n        return \"ds5-config.json\"\n\n    def __repr__(self):\n        return \"<DS5EvdevController %s>\" % (self.get_id(),)\n\n    def _generate_id(self):\n        \"\"\"\n        ID is generated as 'ds5' or 'ds5:X' where 'X' starts as 1 and increases\n        as controllers with same ids are connected.\n        \"\"\"\n        magic_number = 1\n        id = \"ds5\"\n        while id in self.daemon.get_active_ids():\n            id = \"ds5:%s\" % (magic_number,)\n            magic_number += 1\n        return id\n\n\ndef init(daemon, config):\n    \"\"\" Registers hotplug callback for ds5 device \"\"\"\n\n    def hid_callback(device, handle):\n        return DS5Controller(device, daemon, handle, None, None)\n\n    def make_evdev_device(syspath, *whatever):\n        devices = get_evdev_devices_from_syspath(syspath)\n        # With kernel 4.10 or later, PS4 controller pretends to be 3 different devices.\n        # 1st, determining which one is actual controller is needed\n        controllerdevice = None\n        for device in devices:\n            count = len(get_axes(device))\n            if count == 8:\n                # 8 axes - Controller\n                controllerdevice = device\n        if not controllerdevice:\n            log.warning(\"Failed to determine controller device\")\n            return None\n        # 2nd, find motion sensor and touchpad with physical address matching controllerdevice\n        gyro, touchpad = None, None\n        phys = device.phys.split(\"/\")[0]\n        for device in devices:\n            if device.phys.startswith(phys):\n                axes = get_axes(device)\n                count = len(axes)\n                if count == 6:\n                    # 6 axes\n                    if EvdevController.ECODES.ABS_MT_POSITION_X in axes:\n                        # kernel 4.17+ - touchpad\n                        touchpad = device\n                    else:\n                        # gyro sensor\n                        gyro = device\n                    pass\n                elif count == 4:\n                    # 4 axes - Touchpad\n                    touchpad = device\n        # 3rd, do a magic\n        if controllerdevice and gyro and touchpad:\n            return make_new_device(DS5EvdevController, controllerdevice, gyro, touchpad)\n\n    def fail_cb(syspath, vid, pid):\n        if HAVE_EVDEV:\n            log.warning(\n                \"Failed to acquire USB device, falling back to evdev driver. This is far from optimal.\"\n                )\n            make_evdev_device(syspath)\n        else:\n            log.error(\n                \"Failed to acquire USB device and evdev is not available. Everything is lost and DS5 support disabled.\"\n                )\n        # TODO: Maybe add_error here, but error reporting needs little rework so it's not threated as fatal\n        # daemon.add_error(\"ds5\", \"No access to DS5 device\")\n\n    if config[\"drivers\"].get(\"hiddrv\") or (HAVE_EVDEV and config[\"drivers\"].get(\"evdevdrv\")):\n        register_hotplug_device(hid_callback, VENDOR_ID, PRODUCT_ID, on_failure=fail_cb)\n        if config[\"drivers\"].get(\"hiddrv\"):\n            # Only enable HIDRaw support for BT connections if hiddrv is enabled\n            _drv = DS5HidRawDriver(daemon, config)\n        else:\n            # Attempt evdev as a backup\n            if HAVE_EVDEV and config[\"drivers\"].get(\"evdevdrv\"):\n                daemon.get_device_monitor().add_callback(\n                    \"bluetooth\",\n                    VENDOR_ID, PRODUCT_ID, make_evdev_device, None\n                )\n        return True\n    else:\n        log.warning(\"Neither HID nor Evdev driver is enabled, DS5 support cannot be enabled.\")\n        return False\n\n\nif __name__ == \"__main__\":\n    \"\"\" Called when executed as script \"\"\"\n    init_logging()\n    set_logging_level(True, True)\n    sys.exit(hiddrv_test(DS5Controller, [\"054c:0ce6\"]))\n"
  },
  {
    "path": "scc/drivers/evdevdrv.py",
    "content": "\"\"\"\nUniversal driver for gamepads managed by evdev.\n\nHandles no devices by default. Instead of trying to guess which evdev device\nis a gamepad and which user actually wants to be handled by SCC, list of enabled\ndevices is read from config file.\n\"\"\"\n\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX, TRIGGER_MIN, TRIGGER_MAX\nfrom scc.constants import SCButtons, ControllerFlags\nfrom scc.controller import Controller\nfrom scc.paths import get_config_path\nfrom scc.tools import clamp\n\n\nHAVE_EVDEV = False\ntry:\n\t# Driver disables itself if evdev is not available\n\timport evdev\n\tfrom evdev import ecodes\n\tHAVE_EVDEV = True\nexcept ImportError:\n\tclass FakeECodes:\n\t\tdef __getattr__(self, key):\n\t\t\treturn key\n\tecodes = FakeECodes()\n\nfrom collections import namedtuple\nimport os, sys, binascii, json, logging\nlog = logging.getLogger(\"evdev\")\n\nTRIGGERS = \"ltrig\", \"rtrig\"\nFIRST_BUTTON = 288\n\nEvdevControllerInput = namedtuple('EvdevControllerInput',\n\t'buttons ltrig rtrig stick_x stick_y lpad_x lpad_y rpad_x rpad_y '\n\t'accel_x accel_y accel_z '\n\t'gpitch groll gyaw q1 q2 q3 q4 '\n\t'cpad_x cpad_y'\n)\n\nAxisCalibrationData = namedtuple('AxisCalibrationData',\n\t'scale offset center clamp_min clamp_max deadzone'\n)\n\nclass EvdevController(Controller):\n\t\"\"\"\n\tWrapper around evdev device.\n\tTo keep stuff simple, this class tries to provide and use same methods\n\tas SCController class does.\n\t\"\"\"\n\tPADPRESS_EMULATION_TIMEOUT = 0.2\n\tECODES = ecodes\n\tflags = ( ControllerFlags.HAS_RSTICK\n\t\t\t| ControllerFlags.SEPARATE_STICK\n\t\t\t| ControllerFlags.HAS_DPAD\n\t\t\t| ControllerFlags.NO_GRIPS )\n\n\tdef __init__(self, daemon, device, config_file, config):\n\t\ttry:\n\t\t\tself._parse_config(config)\n\t\texcept Exception:\n\t\t\tlog.error(\"Failed to parse config for evdev controller\")\n\t\t\traise\n\t\tController.__init__(self)\n\t\tself.device = device\n\t\tself.config_file = config_file\n\t\tself.config = config\n\t\tself.daemon = daemon\n\t\tself.poller = None\n\t\tif daemon:\n\t\t\tself.poller = daemon.get_poller()\n\t\t\tself.poller.register(self.device.fd, self.poller.POLLIN, self.input)\n\t\t\tself.device.grab()\n\t\t\tself._id = self._generate_id()\n\t\tself._state = EvdevControllerInput( *[0] * len(EvdevControllerInput._fields) )\n\t\tself._padpressemu_task = None\n\n\n\tdef _parse_config(self, config):\n\t\tself._button_map = {}\n\t\tself._axis_map = {}\n\t\tself._dpad_map = {}\n\t\tself._calibrations = {}\n\n\t\tfor x, value in config.get(\"buttons\", {}).items():\n\t\t\ttry:\n\t\t\t\tkeycode = int(x)\n\t\t\t\tif value in TRIGGERS:\n\t\t\t\t\tself._axis_map[keycode] = value\n\t\t\t\telse:\n\t\t\t\t\tsc = getattr(SCButtons, value)\n\t\t\t\t\tself._button_map[keycode] = sc\n\t\t\texcept: pass\n\t\tfor x, value in config.get(\"axes\", {}).items():\n\t\t\tcode, axis = int(x), value.get(\"axis\")\n\t\t\tif axis in EvdevControllerInput._fields:\n\t\t\t\tself._calibrations[code] = parse_axis(value)\n\t\t\t\tself._axis_map[code] = axis\n\t\tfor x, value in config.get(\"dpads\", {}).items():\n\t\t\tcode, axis = int(x), value.get(\"axis\")\n\t\t\tif axis in EvdevControllerInput._fields:\n\t\t\t\tself._calibrations[code] = parse_axis(value)\n\t\t\t\tself._dpad_map[code] = value.get(\"positive\", False)\n\t\t\t\tself._axis_map[code] = axis\n\n\n\tdef close(self):\n\t\tself.poller.unregister(self.device.fd)\n\t\ttry:\n\t\t\tself.device.ungrab()\n\t\texcept: pass\n\t\tself.device.close()\n\n\n\tdef get_type(self):\n\t\treturn \"evdev\"\n\n\n\tdef get_id(self):\n\t\treturn self._id\n\n\n\tdef get_device_filename(self):\n\t\treturn self.device.fn\n\n\n\tdef get_device_name(self):\n\t\treturn self.device.name\n\n\n\tdef _generate_id(self):\n\t\t\"\"\"\n\t\tID is generated as 'ev' + upper_case(hex(crc32(device name + X)))\n\t\twhere 'X' starts as 0 and increases as controllers with same name are\n\t\tconnected.\n\t\t\"\"\"\n\t\tmagic_number = 0\n\t\tid = None\n\t\twhile id is None or id in self.daemon.get_active_ids():\n\t\t\tcrc32 = binascii.crc32(b\"%s%d\" % (bytes(self.device.name, 'utf-8'), magic_number))\n\t\t\tid = \"ev%s\" % (hex(crc32).upper().strip(\"-0X\"),)\n\t\t\tmagic_number += 1\n\t\treturn id\n\n\n\tdef get_gui_config_file(self):\n\t\treturn self.config_file\n\n\n\tdef __repr__(self):\n\t\treturn \"<Evdev %s>\" % self.device.name\n\n\n\tdef input(self, *a):\n\t\tnew_state = self._state\n\t\tneed_cancel_padpressemu = False\n\t\ttry:\n\t\t\tfor event in self.device.read():\n\t\t\t\tif event.type == ecodes.EV_KEY and event.code in self._dpad_map:\n\t\t\t\t\tcal = self._calibrations[event.code]\n\t\t\t\t\tif event.value:\n\t\t\t\t\t\tif self._dpad_map[event.code]:\n\t\t\t\t\t\t\t# Positive\n\t\t\t\t\t\t\tvalue = STICK_PAD_MAX\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tvalue = STICK_PAD_MIN\n\t\t\t\t\t\tcal = self._calibrations[event.code]\n\t\t\t\t\t\tvalue = int(value * cal.scale * STICK_PAD_MAX)\n\t\t\t\t\telse:\n\t\t\t\t\t\tvalue = 0\n\t\t\t\t\taxis = self._axis_map[event.code]\n\t\t\t\t\tif not new_state.buttons & SCButtons.LPADTOUCH and axis in (\"lpad_x\", \"lpad_y\"):\n\t\t\t\t\t\tb = new_state.buttons | SCButtons.LPAD | SCButtons.LPADTOUCH\n\t\t\t\t\t\tneed_cancel_padpressemu = True\n\t\t\t\t\t\tnew_state = new_state._replace(buttons=b, **{ axis : value })\n\t\t\t\t\telif not new_state.buttons & SCButtons.RPADTOUCH and axis in (\"rpad_x\", \"rpad_y\"):\n\t\t\t\t\t\tb = new_state.buttons | SCButtons.RPADTOUCH\n\t\t\t\t\t\tneed_cancel_padpressemu = True\n\t\t\t\t\t\tnew_state = new_state._replace(buttons=b, **{ axis : value })\n\t\t\t\t\telse:\n\t\t\t\t\t\tnew_state = new_state._replace(**{ axis : value })\n\t\t\t\telif event.type == ecodes.EV_KEY and event.code in self._button_map:\n\t\t\t\t\tif event.value:\n\t\t\t\t\t\tb = new_state.buttons | self._button_map[event.code]\n\t\t\t\t\t\tnew_state = new_state._replace(buttons=b)\n\t\t\t\t\telse:\n\t\t\t\t\t\tb = new_state.buttons & ~self._button_map[event.code]\n\t\t\t\t\t\tnew_state = new_state._replace(buttons=b)\n\t\t\t\telif event.type == ecodes.EV_KEY and event.code in self._axis_map:\n\t\t\t\t\taxis = self._axis_map[event.code]\n\t\t\t\t\tif event.value:\n\t\t\t\t\t\tnew_state = new_state._replace(**{ axis : TRIGGER_MAX })\n\t\t\t\t\telse:\n\t\t\t\t\t\tnew_state = new_state._replace(**{ axis : TRIGGER_MIN })\n\t\t\t\telif event.type == ecodes.EV_ABS and event.code in self._axis_map:\n\t\t\t\t\tcal = self._calibrations[event.code]\n\t\t\t\t\tvalue = (float(event.value) * cal.scale) + cal.offset\n\t\t\t\t\tif value >= -cal.deadzone and value <= cal.deadzone:\n\t\t\t\t\t\tvalue = 0\n\t\t\t\t\telse:\n\t\t\t\t\t\tvalue = clamp(cal.clamp_min,\n\t\t\t\t\t\t\t\tint(value * cal.clamp_max), cal.clamp_max)\n\t\t\t\t\taxis = self._axis_map[event.code]\n\t\t\t\t\tif not new_state.buttons & SCButtons.LPADTOUCH and axis in (\"lpad_x\", \"lpad_y\"):\n\t\t\t\t\t\tb = new_state.buttons | SCButtons.LPAD | SCButtons.LPADTOUCH\n\t\t\t\t\t\tneed_cancel_padpressemu = True\n\t\t\t\t\t\tnew_state = new_state._replace(buttons=b, **{ axis : value })\n\t\t\t\t\telif not new_state.buttons & SCButtons.RPADTOUCH and axis in (\"rpad_x\", \"rpad_y\"):\n\t\t\t\t\t\tb = new_state.buttons | SCButtons.RPADTOUCH\n\t\t\t\t\t\tneed_cancel_padpressemu = True\n\t\t\t\t\t\tnew_state = new_state._replace(buttons=b, **{ axis : value })\n\t\t\t\t\telse:\n\t\t\t\t\t\tnew_state = new_state._replace(**{ axis : value })\n\t\texcept IOError as e:\n\t\t\t# TODO: Maybe check e.errno to determine exact error\n\t\t\t# all of them are fatal for now\n\t\t\tlog.error(e)\n\t\t\t_evdevdrv.device_removed(self.device.fn)\n\n\t\tif new_state is not self._state:\n\t\t\t# Something got changed\n\t\t\told_state, self._state = self._state, new_state\n\t\t\tif self.mapper:\n\t\t\t\tif need_cancel_padpressemu:\n\t\t\t\t\tif self._padpressemu_task:\n\t\t\t\t\t\tself.mapper.cancel_task(self._padpressemu_task)\n\t\t\t\t\tself._padpressemu_task = self.mapper.schedule(\n\t\t\t\t\t\tself.PADPRESS_EMULATION_TIMEOUT,\n\t\t\t\t\t\tself.cancel_padpress_emulation\n\t\t\t\t\t)\n\t\t\t\tself.mapper.input(self, old_state, new_state)\n\n\n\tdef test_input(self, event):\n\t\tif event.type == ecodes.EV_KEY:\n\t\t\tif event.code >= FIRST_BUTTON:\n\t\t\t\tif event.value:\n\t\t\t\t\tprint(\"ButtonPress\", event.code)\n\t\t\t\telse:\n\t\t\t\t\tprint(\"ButtonRelease\", event.code)\n\t\t\t\tsys.stdout.flush()\n\t\telif event.type == ecodes.EV_ABS:\n\t\t\tprint(\"Axis\", event.code, event.value)\n\t\t\tsys.stdout.flush()\n\n\n\tdef cancel_padpress_emulation(self, mapper):\n\t\t\"\"\"\n\t\tSince evdev gamepad typically can't generate LPADTOUCH nor RPADTOUCH\n\t\tbuttons/events, pushing those buttons is emulated when apropriate stick\n\t\tis moved.\n\n\t\tEmulated *PADTOUCH button is held until stick is being moved and then\n\t\tfor small time set by PADPRESS_EMULATION_TIMEOUT.\n\t\tThen, to release those purely virtual buttons, this method is called.\n\t\t\"\"\"\n\n\t\tneed_reschedule = False\n\t\tnew_state = self._state\n\t\tif new_state.buttons & SCButtons.LPADTOUCH:\n\t\t\tif self._state.lpad_x == 0 and self._state.lpad_y == 0:\n\t\t\t\tb = new_state.buttons & ~(SCButtons.LPAD | SCButtons.LPADTOUCH)\n\t\t\t\tnew_state = new_state._replace(buttons=b)\n\t\t\telse:\n\t\t\t\tneed_reschedule = True\n\n\t\tif new_state.buttons & SCButtons.RPADTOUCH:\n\t\t\tif self._state.rpad_x == 0 and self._state.rpad_y == 0:\n\t\t\t\tb = new_state.buttons & ~SCButtons.RPADTOUCH\n\t\t\t\tnew_state = new_state._replace(buttons=b)\n\t\t\telse:\n\t\t\t\tneed_reschedule = True\n\n\t\tif new_state is not self._state:\n\t\t\t# Something got changed\n\t\t\told_state, self._state = self._state, new_state\n\t\t\tif self.mapper:\n\t\t\t\tself.mapper.input(self, old_state, new_state)\n\n\t\tif need_reschedule:\n\t\t\tself._padpressemu_task = mapper.schedule(\n\t\t\t\tself.PADPRESS_EMULATION_TIMEOUT, self.cancel_padpress_emulation)\n\t\telse:\n\t\t\tself._padpressemu_task = None\n\n\n\tdef apply_config(self, config):\n\t\t# TODO: This?\n\t\tpass\n\n\n\tdef disconnected(self):\n\t\t# TODO: This!\n\t\tpass\n\n\n\t# def configure(self, idle_timeout=None, enable_gyros=None, led_level=None):\n\n\n\tdef set_led_level(self, level):\n\t\t# TODO: This?\n\t\tpass\n\n\n\tdef set_gyro_enabled(self, enabled):\n\t\t# TODO: This, maybe.\n\t\tpass\n\n\n\tdef turnoff(self):\n\t\t\"\"\"\n\t\tExists to stay compatibile with SCController class as evdev controller\n\t\ttypically cannot be shut down like this.\n\t\t\"\"\"\n\t\tpass\n\n\n\tdef get_gyro_enabled(self):\n\t\t\"\"\" Returns True if gyroscope input is currently enabled \"\"\"\n\t\treturn False\n\n\n\tdef feedback(self, data):\n\t\t\"\"\" TODO: It would be nice to have feedback... \"\"\"\n\t\tpass\n\n\ndef parse_axis(axis):\n\tmin       = axis.get(\"min\", -127)\n\tmax       = axis.get(\"max\",  128)\n\tcenter    = axis.get(\"center\", 0)\n\tclamp_min = STICK_PAD_MIN\n\tclamp_max = STICK_PAD_MAX\n\tdeadzone  = axis.get(\"deadzone\", 0)\n\toffset = 0\n\tif (max >= 0 and min >= 0):\n\t\toffset = 1\n\tif max > min:\n\t\tscale = (-2.0 / (min-max)) if min != max else 1.0\n\t\tdeadzone = abs(float(deadzone) * scale)\n\t\toffset *= -1.0\n\telse:\n\t\tscale = (-2.0 / (min-max)) if min != max else 1.0\n\t\tdeadzone = abs(float(deadzone) * scale)\n\tif axis in TRIGGERS:\n\t\tclamp_min = TRIGGER_MIN\n\t\tclamp_max = TRIGGER_MAX\n\t\toffset += 1.0\n\t\tscale *= 0.5\n\n\treturn AxisCalibrationData(scale, offset, center, clamp_min, clamp_max, deadzone)\n\n\nclass EvdevDriver(object):\n\tSCAN_INTERVAL = 5\n\n\tdef __init__(self):\n\t\tself.daemon = None\n\t\tself._devices = {}\n\t\tself._scan_thread = None\n\t\tself._next_scan = None\n\n\n\tdef start(self):\n\t\tself.daemon.get_device_monitor().add_callback(\"input\", None, None,\n\t\t\t\tself.handle_new_device, self.handle_removed_device)\n\n\n\tdef set_daemon(self, daemon):\n\t\tself.daemon = daemon\n\n\n\t@staticmethod\n\tdef get_event_node(syspath):\n\t\tfilename = syspath.split(\"/\")[-1]\n\t\tif not filename.startswith(\"event\"):\n\t\t\treturn None\n\t\treturn \"/dev/input/%s\" % (filename, )\n\n\n\tdef handle_new_device(self, syspath, *bunchofnones):\n\t\t# There is no way to get anything usefull from /sys/.../input node,\n\t\t# but I'm interested about event devices here anyway\n\t\teventnode = EvdevDriver.get_event_node(syspath)\n\t\tif eventnode is None: return False\t\t\t\t# Not evdev\n\t\tif eventnode in self._devices: return False\t\t# Already handled\n\n\t\ttry:\n\t\t\tdev = evdev.InputDevice(eventnode)\n\t\t\tassert dev.fn == eventnode\n\t\t\tconfig_fn = \"evdev-%s.json\" % (dev.name.strip().replace(\"/\", \"\"),)\n\t\t\tconfig_file = os.path.join(get_config_path(), \"devices\", config_fn)\n\t\texcept OSError as ose:\n\t\t\tif ose.errno == 13:\n\t\t\t\t# Excepted error that happens often, don't report\n\t\t\t\treturn False\n\t\t\tlog.exception(ose)\n\t\t\treturn False\n\t\texcept Exception as e:\n\t\t\tlog.exception(e)\n\t\t\treturn False\n\n\t\tif os.path.exists(config_file):\n\t\t\tconfig = None\n\t\t\ttry:\n\t\t\t\tconfig = json.loads(open(config_file, \"r\").read())\n\t\t\texcept Exception as e:\n\t\t\t\tlog.exception(e)\n\t\t\t\treturn False\n\t\t\ttry:\n\t\t\t\tcontroller = EvdevController(self.daemon, dev, config_file, config)\n\t\t\texcept Exception as e:\n\t\t\t\tlog.debug(\"Failed to add evdev device: %s\", e)\n\t\t\t\tlog.exception(e)\n\t\t\t\treturn False\n\t\t\tself._devices[eventnode] = controller\n\t\t\tself.daemon.add_controller(controller)\n\t\t\tlog.debug(\"Evdev device added: %s\", dev.name)\n\t\t\treturn True\n\n\n\tdef handle_removed_device(self, syspath, *bunchofnones):\n\t\teventnode = EvdevDriver.get_event_node(syspath)\n\t\tself.device_removed(eventnode)\n\n\n\tdef device_removed(self, eventnode):\n\t\tif eventnode in self._devices:\n\t\t\tcontroller = self._devices[eventnode]\n\t\t\tdel self._devices[eventnode]\n\t\t\tself.daemon.remove_controller(controller)\n\t\t\tcontroller.close()\n\n\n\tdef handle_callback(self, callback, devices):\n\t\ttry:\n\t\t\tcontroller = callback(devices)\n\t\texcept Exception as e:\n\t\t\tlog.debug(\"Failed to add evdev device: %s\", e)\n\t\t\tlog.exception(e)\n\t\t\treturn\n\t\tif controller is not None:\n\t\t\tself._devices[controller.get_device_filename()] = controller\n\t\t\tself.daemon.add_controller(controller)\n\t\t\tlog.debug(\"Evdev device added: %s\", controller.get_device_name())\n\n\n\tdef make_new_device(self, factory, evdevdevice, *userdata):\n\t\t\"\"\"\n\t\tSimilar to handle_new_device, but meant for use by other drivers.\n\t\tSee global make_new_device method for more info\n\t\t\"\"\"\n\t\ttry:\n\t\t\tcontroller = factory(self.daemon, evdevdevice, *userdata)\n\t\texcept IOError as e:\n\t\t\tprint(\"Failed to open device:\", str(e), file=sys.stderr)\n\t\t\treturn None\n\t\tif controller:\n\t\t\tself._devices[evdevdevice.fn] = controller\n\t\t\tself.daemon.add_controller(controller)\n\t\t\tlog.debug(\"Evdev device added: %s\", controller.get_device_name())\n\t\treturn controller\n\n\nif HAVE_EVDEV:\n\t# Just like USB driver, EvdevDriver is process-wide singleton\n\t_evdevdrv = EvdevDriver()\n\n\n\tdef start(daemon):\n\t\t_evdevdrv.start()\n\n\ndef init(daemon, config):\n\tif not HAVE_EVDEV:\n\t\tlog.warning(\"Failed to enable Evdev driver: 'python-evdev' package is missing.\")\n\t\treturn False\n\n\t_evdevdrv.set_daemon(daemon)\n\treturn True\n\n\ndef make_new_device(factory, evdevdevice, *userdata):\n\t\"\"\"\n\tCreates and registers device using given evdev device and given factory method.\n\tFactory is called as factory(daemon, device, *userdata) and if it returns device,\n\tthis device is added into watch list, so it can be closed automatically.\n\n\tReturns whatever Factory returned.\n\t\"\"\"\n\tassert HAVE_EVDEV, \"evdev driver is not available\"\n\treturn _evdevdrv.make_new_device(factory, evdevdevice, *userdata)\n\n\ndef get_evdev_devices_from_syspath(syspath):\n\t\"\"\"\n\tFor given syspath, returns all assotiated event devices.\n\t\"\"\"\n\tassert HAVE_EVDEV, \"evdev driver is not available\"\n\trv = []\n\tfor name in os.listdir(syspath):\n\t\tpath = os.path.join(syspath, name)\n\t\tif name.startswith(\"event\"):\n\t\t\teventnode = EvdevDriver.get_event_node(path)\n\t\t\tif eventnode is not None:\n\t\t\t\ttry:\n\t\t\t\t\tdev = evdev.InputDevice(eventnode)\n\t\t\t\t\tassert dev.fn == eventnode\n\t\t\t\t\trv.append(dev)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.exception(e)\n\t\t\t\t\tcontinue\n\t\telse:\n\t\t\tif os.path.isdir(path) and not os.path.islink(path):\n\t\t\t\trv += get_evdev_devices_from_syspath(path)\n\treturn rv\n\n\ndef get_axes(dev):\n\t\"\"\" Helper function to get list ofa available axes \"\"\"\n\tassert HAVE_EVDEV, \"evdev driver is not available\"\n\tcaps = dev.capabilities(verbose=False)\n\treturn [ axis for (axis, trash) in caps.get(ecodes.EV_ABS, []) ]\n\n\ndef evdevdrv_test(args):\n\t\"\"\"\n\tSmall input test used by GUI while setting up the device.\n\tOutput and usage matches one from hiddrv.\n\t\"\"\"\n\tfrom scc.scripts import InvalidArguments\n\n\ttry:\n\t\tpath = args[0]\n\t\tdev = evdev.InputDevice(path)\n\texcept IndexError:\n\t\traise InvalidArguments()\n\texcept Exception as e:\n\t\tprint(\"Failed to open device:\", str(e), file=sys.stderr)\n\t\treturn 2\n\n\tc = EvdevController(None, dev, None, {})\n\tcaps = dev.capabilities(verbose=False)\n\tprint(\"Buttons:\", \" \".join([ str(x)\n\t\t\tfor x in caps.get(ecodes.EV_KEY, [])]))\n\tprint(\"Axes:\", \" \".join([ str(axis)\n\t\t\tfor (axis, trash) in caps.get(ecodes.EV_ABS, []) ]))\n\tprint(\"Ready\")\n\tsys.stdout.flush()\n\tfor event in dev.read_loop():\n\t\tc.test_input(event)\n\treturn 0\n\n\nif __name__ == \"__main__\":\n\t\"\"\" Called when executed as script \"\"\"\n\tfrom scc.tools import init_logging, set_logging_level\n\tinit_logging()\n\tset_logging_level(True, True)\n\tsys.exit(evdevdrv_test(sys.argv[1:]))\n\n"
  },
  {
    "path": "scc/drivers/fake.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Fake controller driver\n\nThis driver does nothing by default, unless SCC_FAKES environment variable is\nset. If it is, creates as many fake controller devices as integer stored in\nSCC_FAKES says.\n\nCreated controllers are completely useless. For debuging purposes only.\n\"\"\"\n\nfrom scc.controller import Controller\nimport os, logging\n\nENV_VAR = \"SCC_FAKES\"\n\nif ENV_VAR in os.environ:\n\tlog = logging.getLogger(\"FakeDrv\")\n\t\n\t\n\tdef init(daemon, config):\n\t\treturn True\n\t\n\t\n\tdef start(daemon):\n\t\tnum = int(os.environ[ENV_VAR])\n\t\tlog.debug(\"Creating %s fake controllers\", num)\n\t\tfor x in range(0, num):\n\t\t\tdaemon.add_controller(FakeController(x))\n\n\nclass FakeController(Controller):\n\tdef __init__(self, number):\n\t\tController.__init__(self)\n\t\tself._number = number\n\t\tself._id = \"fake%s\" % (self._number,)\n\t\n\t\n\tdef get_type(self):\n\t\treturn \"fake\"\n\t\n\t\n\tdef set_led_level(self, level):\n\t\tlog.debug(\"FakeController %s led level set to %s\", self.get_id(), level)\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<FakeController %s>\" % (self.get_id(),)\n"
  },
  {
    "path": "scc/drivers/hiddrv.c",
    "content": "#define PY_SSIZE_T_CLEAN\n#include <Python.h>\n#include <inttypes.h>\n#include <stdbool.h>\n#include <limits.h>\n#define CLAMP(min, x, max) x\n\n#define HIDDRV_MODULE_VERSION 5\nPyObject* module;\n\n#define AXIS_COUNT 20\n#define BUTTON_COUNT 32\n\nstruct HIDControllerInput {\n\tuint32_t buttons;\n\tint32_t axes[AXIS_COUNT];\n};\n\n\nenum AxisType {\n\tAXIS_LPAD_X  = 0,\n\tAXIS_LPAD_Y  = 1,\n\tAXIS_RPAD_X  = 2,\n\tAXIS_RPAD_Y  = 3,\n\tAXIS_STICK_X = 4,\n\tAXIS_STICK_Y = 5,\n\tAXIS_LTRIG   = 6,\n\tAXIS_RTRIG   = 7,\n\tAXIS_ACCEL_X = 8,\n\tAXIS_ACCEL_Y = 9,\n\tAXIS_ACCEL_Z = 10,\n\tAXIS_GPITCH  = 11,\n\tAXIS_GROLL   = 12,\n\tAXIS_GYAW    = 13,\n\tAXIS_Q1      = 14,\n\tAXIS_Q2      = 15,\n\tAXIS_Q3      = 16,\n\tAXIS_Q4      = 17,\n\tAXIS_CPAD_X  = 18,\n\tAXIS_CPAD_Y  = 19,\n\t_AxisType_force_int = INT_MAX\n};\n\n\nenum AxisMode {\n\tDISABLED      = 0,\n\tAXIS          = 1,\n\tAXIS_NO_SCALE = 2,\n\tDPAD          = 3,\n\tHATSWITCH     = 4,\n\tDS4ACCEL      = 5,\n\tDS4GYRO       = 6,\n\tDS4TOUCHPAD   = 7,\n\t_AxisMode_force_int = INT_MAX\n};\n\n\nstruct AxisModeData {\n\tuint32_t button;\n\tfloat scale;\n\tfloat offset;\n\tint clamp_min;\n\tint clamp_max;\n\tfloat deadzone;\n};\n\n\nstruct DPadModeData {\n\tuint32_t button;\n\tunsigned char button1;\n\tunsigned char button2;\n\tint min;\n\tint max;\n};\n\n\nstruct HatswitchModeData {\n\tuint32_t button;\n\tint min;\n\tint max;\n};\n\n\nunion AxisDataUnion {\n\tstruct AxisModeData axis;\n\tstruct DPadModeData dpad;\n\tstruct HatswitchModeData hatswitch;\n};\n\n\nstruct AxisData {\n\tenum AxisMode mode;\n\tsize_t byte_offset;\n\tuint8_t bit_offset;\n\tuint8_t size;\n\t\n\tunion AxisDataUnion data;\n};\n\n\nstruct ButtonData {\n\tbool enabled;\n\tsize_t byte_offset;\n\tuint8_t bit_offset;\n\tuint8_t size;\n\tuint8_t button_count;\n\tuint8_t button_map[BUTTON_COUNT];\n};\n\n\nstruct HIDDecoder {\n\tstruct AxisData axes[AXIS_COUNT];\n\tstruct ButtonData buttons;\n\tsize_t packet_size;\n\t\n\tstruct HIDControllerInput old_state;\n\tstruct HIDControllerInput state;\n};\n\n\nunion Value {\n\tuint8_t  u8;\n\tuint16_t u16;\n\tint16_t  s16;\n\tuint32_t u32;\n\tuint64_t u64;\n};\n\n\nstatic union Value grab_value(const char* data, const size_t byte_offset, uint8_t bit_offset) {\n\tunion Value val = *((union Value*)(data + byte_offset));\n\tval.u64 = val.u64 >> bit_offset;\n\treturn val;\n}\n\n\nstatic int grab_with_size(const uint8_t size, const char* data, const size_t byte_offset, uint8_t bit_offset) {\n\tunion Value val = grab_value(data, byte_offset, bit_offset);\n\t// if (size == 16)\n\t// \tprintf(\" => %i\\n\", (int)val.u16);\n\tswitch (size) {\n\t\tcase 16: return val.u16;\n\t\tcase 32: return val.u32;\n\t\tcase 64: return val.u64;\n\t\tdefault: return val.u8;\n\t}\n}\n\n\nbool decode(struct HIDDecoder* dec, const char* data) {\n\tsize_t i;\n\tmemcpy(&(dec->old_state), &(dec->state), sizeof(struct HIDControllerInput));\n\tdec->state.buttons = 0;\n\t// Axes\n\tfor (i=0; i<AXIS_COUNT; i++) {\n\t\tunion Value value;\n\t\tfloat fval;\n\t\tswitch (dec->axes[i].mode) {\n\t\t\tcase AXIS:\n\t\t\t\tfval = ((grab_with_size(dec->axes[i].size,\n\t\t\t\t\t\tdata, dec->axes[i].byte_offset, dec->axes[i].bit_offset)\n\t\t\t\t\t\t\t* dec->axes[i].data.axis.scale)\n\t\t\t\t\t\t\t+ dec->axes[i].data.axis.offset\n\t\t\t\t);\n\t\t\t\tif ((fval >= -dec->axes[i].data.axis.deadzone) && (fval <= dec->axes[i].data.axis.deadzone)) {\n\t\t\t\t\t\tdec->state.axes[i] = 0;\n\t\t\t\t} else {\n\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.dpad.button;\n\t\t\t\t\tdec->state.axes[i] = CLAMP(\n\t\t \t\t\t\tdec->axes[i].data.axis.clamp_min,\n\t\t \t\t\t\tfval * dec->axes[i].data.axis.clamp_max,\n\t\t \t\t\t\tdec->axes[i].data.axis.clamp_max\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase AXIS_NO_SCALE:\n\t\t\t\tdec->state.axes[i] = grab_with_size(dec->axes[i].size,\n\t\t\t\t\tdata, dec->axes[i].byte_offset, dec->axes[i].bit_offset);\n\t\t\t\tbreak;\n\t\t\tcase DPAD:\n\t\t\t\tvalue = grab_value(data, dec->axes[i].byte_offset,\n\t\t\t\t\tdec->axes[i].bit_offset);\n\t\t\t\tif ((value.u32 >> dec->axes[i].data.dpad.button1) & 1) {\n\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.dpad.button;\n\t\t\t\t\tdec->state.axes[i] = dec->axes[i].data.dpad.min;\n\t\t\t\t} else if ((value.u32 >> dec->axes[i].data.dpad.button2) & 1) {\n\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.dpad.button;\n\t\t\t\t\tdec->state.axes[i] = dec->axes[i].data.dpad.max;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase HATSWITCH:\n\t\t\t\tvalue = grab_value(data, dec->axes[i].byte_offset,\n\t\t\t\t\tdec->axes[i].bit_offset);\n\t\t\t\tswitch (value.u8 & 0b1111) {\n\t\t\t\t\tcase 0:\t// up\n\t\t\t\t\t\tdec->state.axes[i + 0] = 0;\n\t\t\t\t\t\tdec->state.axes[i + 1] = dec->axes[i].data.hatswitch.max;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 1:\t// up-right\n\t\t\t\t\t\tdec->state.axes[i + 0] = dec->axes[i].data.hatswitch.max;\n\t\t\t\t\t\tdec->state.axes[i + 1] = dec->axes[i].data.hatswitch.max;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 2:\t// right\n\t\t\t\t\t\tdec->state.axes[i + 0] = dec->axes[i].data.hatswitch.max;\n\t\t\t\t\t\tdec->state.axes[i + 1] = 0;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 3:\t// down-right\n\t\t\t\t\t\tdec->state.axes[i + 0] = dec->axes[i].data.hatswitch.max;\n\t\t\t\t\t\tdec->state.axes[i + 1] = dec->axes[i].data.hatswitch.min;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 4:\t// down\n\t\t\t\t\t\tdec->state.axes[i + 0] = 0;\n\t\t\t\t\t\tdec->state.axes[i + 1] = dec->axes[i].data.hatswitch.min;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 5:\t// up-left\n\t\t\t\t\t\tdec->state.axes[i + 0] = dec->axes[i].data.hatswitch.min;\n\t\t\t\t\t\tdec->state.axes[i + 1] = dec->axes[i].data.hatswitch.min;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 6:\t// left\n\t\t\t\t\t\tdec->state.axes[i + 0] = dec->axes[i].data.hatswitch.min;\n\t\t\t\t\t\tdec->state.axes[i + 1] = 0;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 7:\t// down-left\n\t\t\t\t\t\tdec->state.axes[i + 0] = dec->axes[i].data.hatswitch.min;\n\t\t\t\t\t\tdec->state.axes[i + 1] = dec->axes[i].data.hatswitch.max;\n\t\t\t\t\t\tdec->state.buttons |= dec->axes[i].data.hatswitch.button;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault: // centered\n\t\t\t\t\t\tdec->state.axes[i + 0] = 0;\n\t\t\t\t\t\tdec->state.axes[i + 1] = 0;\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase DS4ACCEL:\n\t\t\t\tvalue = grab_value(data, dec->axes[i].byte_offset,\n\t\t\t\t\tdec->axes[i].bit_offset);\n\t\t\t\tdec->state.axes[i] = value.s16;\n\t\t\t\tbreak;\n\t\t\tcase DS4GYRO:\n\t\t\t\tvalue = grab_value(data, dec->axes[i].byte_offset,\n\t\t\t\t\tdec->axes[i].bit_offset);\n\t\t\t\tdec->state.axes[i] = -value.s16;\n\t\t\t\tbreak;\n\t\t\tcase DS4TOUCHPAD:\n\t\t\t\tvalue = grab_value(data, dec->axes[i].byte_offset,\n\t\t\t\t\tdec->axes[i].bit_offset);\n\t\t\t\tdec->state.axes[i] = (value.u16 & 0x0FFF);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\t\n\t// Buttons\n\tif (dec->buttons.enabled) {\n\t\tunion Value value = grab_value(data, dec->buttons.byte_offset, dec->buttons.bit_offset);\n\t\tfor (i=0; i<BUTTON_COUNT; i++) {\n\t\t\tif (dec->buttons.button_map[i] < 33) {\n\t\t\t\tuint32_t bit = (value.u32 >> i) & 1;\n\t\t\t\tdec->state.buttons |= bit << dec->buttons.button_map[i];\n\t\t\t}\n\t\t}\n\t}\n\treturn memcmp(&(dec->old_state), &(dec->state), sizeof(struct HIDControllerInput)) != 0;\n}\n\n\nconst int hiddrv_module_version(void) {\n\treturn HIDDRV_MODULE_VERSION;\n}\n"
  },
  {
    "path": "scc/drivers/hiddrv.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nSC Controller - Universal HID driver. For all three universal HID devices.\n\nBorrows bit of code and configuration from evdevdrv.\n\"\"\"\nfrom scc.lib.hidparse import GlobalItem, LocalItem, MainItem, ItemType\nfrom scc.lib.hidparse import UsagePage, parse_report_descriptor\nfrom scc.lib.hidparse import GenericDesktopPage, AXES\nfrom scc.drivers.usb import register_hotplug_device, unregister_hotplug_device\nfrom scc.drivers.usb import USBDevice\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.constants import SCButtons, ControllerFlags\nfrom scc.drivers.evdevdrv import FIRST_BUTTON, TRIGGERS, parse_axis\nfrom scc.controller import Controller\nfrom scc.paths import get_config_path\nfrom scc.tools import find_library\nfrom scc.lib import IntEnum\n\nimport os, json, ctypes, sys, logging\nlog = logging.getLogger(\"HID\")\n\nDEV_CLASS_HID = 3\nTRANSFER_TYPE_INTERRUPT = 3\nLIBUSB_DT_REPORT = 0x22\nAXIS_COUNT = 20\t\t# Must match number of axis fields in HIDControllerInput and values in AxisType\nBUTTON_COUNT = 32\t# Must match (or be less than) number of bits in HIDControllerInput.buttons\nALLOWED_SIZES = [1, 2, 4, 8, 16, 32]\nSYS_DEVICES = \"/sys/devices\"\n\n\nBLACKLIST = [\n\t# List of devices known to pretend to be HID compatible but breaking horribly with HID\n\t# vendor, product\n\t(0x045e, 0x0719),\t# Xbox controller\n\t(0x045e, 0x028e),\t# Xbox wireless adapter\n\t(0x0738, 0x4716),\t# Mad Catz, Inc controller\n]\n\n\nclass HIDDrvError(Exception): pass\nclass NotHIDDevice(HIDDrvError): pass\nclass UnparsableDescriptor(HIDDrvError): pass\n\nclass HIDControllerInput(ctypes.Structure):\n\t_fields_ = [\n\t\t('buttons', ctypes.c_uint32),\n\t\t# Note: Axis order is same as in AxisType enum\n\t\t('lpad_x', ctypes.c_int32),\n\t\t('lpad_y', ctypes.c_int32),\n\t\t('rpad_x', ctypes.c_int32),\n\t\t('rpad_y', ctypes.c_int32),\n\t\t('stick_x', ctypes.c_int32),\n\t\t('stick_y', ctypes.c_int32),\n\t\t('ltrig', ctypes.c_int32),\n\t\t('rtrig', ctypes.c_int32),\n\t\t('accel_x', ctypes.c_int32),\n\t\t('accel_y', ctypes.c_int32),\n\t\t('accel_z', ctypes.c_int32),\n\t\t('gpitch', ctypes.c_int32),\n\t\t('groll', ctypes.c_int32),\n\t\t('gyaw', ctypes.c_int32),\n\t\t('q1', ctypes.c_int32),\n\t\t('q2', ctypes.c_int32),\n\t\t('q3', ctypes.c_int32),\n\t\t('q4', ctypes.c_int32),\n\t\t('cpad_x', ctypes.c_int32),\n\t\t('cpad_y', ctypes.c_int32),\n\t]\n\n\nclass AxisType(IntEnum):\n\tAXIS_LPAD_X  = 0\n\tAXIS_LPAD_Y  = 1\n\tAXIS_RPAD_X  = 2\n\tAXIS_RPAD_Y  = 3\n\tAXIS_STICK_X = 4\n\tAXIS_STICK_Y = 5\n\tAXIS_LTRIG   = 6\n\tAXIS_RTRIG   = 7\n\tAXIS_ACCEL_X = 8\n\tAXIS_ACCEL_Y = 9\n\tAXIS_ACCEL_Z = 10\n\tAXIS_GPITCH  = 11\n\tAXIS_GROLL   = 12\n\tAXIS_GYAW    = 13\n\tAXIS_Q1      = 14\n\tAXIS_Q2      = 15\n\tAXIS_Q3      = 16\n\tAXIS_Q4      = 17\n\tAXIS_CPAD_X  = 18\n\tAXIS_CPAD_Y  = 19\n\n\nclass AxisMode(IntEnum):\n\tDISABLED      = 0\n\tAXIS          = 1\n\tAXIS_NO_SCALE = 2\n\tDPAD          = 3\n\tHATSWITCH     = 4\n\tDS4ACCEL      = 5\t# 16bit, signed, no additional math needed\n\tDS4GYRO       = 6\t# 16bit, signed, inverted\n\tDS4TOUCHPAD   = 7\t# 12bit\n\n\nclass AxisModeData(ctypes.Structure):\n\t_fields_ = [\n\t\t('button', ctypes.c_uint32),\n\t\t('scale', ctypes.c_float),\n\t\t('offset', ctypes.c_float),\n\t\t('clamp_min', ctypes.c_int),\n\t\t('clamp_max', ctypes.c_int),\n\t\t('deadzone', ctypes.c_float),\n\t]\n\n\nclass DPadModeData(ctypes.Structure):\n\t_fields_ = [\n\t\t('button', ctypes.c_uint32),\n\t\t('button1', ctypes.c_uint8),\n\t\t('button2', ctypes.c_uint8),\n\t\t('min', ctypes.c_int),\n\t\t('max', ctypes.c_int),\n\t]\n\n\nclass HatswitchModeData(ctypes.Structure):\n\t_fields_ = [\n\t\t('button', ctypes.c_uint32),\n\t\t('min', ctypes.c_int),\n\t\t('max', ctypes.c_int),\n\t]\n\n\nclass AxisDataUnion(ctypes.Union):\n\t_fields_ = [\n\t\t('axis', AxisModeData),\n\t\t('dpad', DPadModeData),\n\t\t('hatswitch', HatswitchModeData),\n\t]\n\n\nclass AxisData(ctypes.Structure):\n\t_fields_ = [\n\t\t('mode', ctypes.c_int),\n\t\t('byte_offset', ctypes.c_size_t),\n\t\t('bit_offset', ctypes.c_uint8),\n\t\t('size', ctypes.c_uint8),\t# TODO: Currently unused\n\n\t\t('data', AxisDataUnion),\n\t]\n\n\nclass ButtonData(ctypes.Structure):\n\t_fields_ = [\n\t\t('enabled', ctypes.c_bool),\n\t\t('byte_offset', ctypes.c_size_t),\n\t\t('bit_offset', ctypes.c_uint8),\n\t\t('size', ctypes.c_uint8),\n\t\t('button_count', ctypes.c_uint8),\n\t\t('button_map', ctypes.c_uint8 * BUTTON_COUNT),\n\t]\n\n\nclass HIDDecoder(ctypes.Structure):\n\t_fields_ = [\n\t\t('axes', AxisData * AXIS_COUNT),\n\t\t('buttons', ButtonData),\n\t\t('packet_size', ctypes.c_size_t),\n\n\t\t('old_state', HIDControllerInput),\n\t\t('state', HIDControllerInput),\n\t]\n\n\nHIDDecoderPtr = ctypes.POINTER(HIDDecoder)\n\n\n_lib = find_library('libhiddrv')\n_lib.decode.restype = bool\n_lib.decode.argtypes = [ HIDDecoderPtr, ctypes.c_char_p ]\n\n\nclass HIDController(USBDevice, Controller):\n\tflags = ( ControllerFlags.HAS_RSTICK\n\t\t\t| ControllerFlags.SEPARATE_STICK\n\t\t\t| ControllerFlags.HAS_DPAD\n\t\t\t| ControllerFlags.NO_GRIPS )\n\n\tdef __init__(self, device, daemon, handle, config_file, config, test_mode=False):\n\t\tUSBDevice.__init__(self, device, handle)\n\t\tself._ready = False\n\t\tself.daemon = daemon\n\t\tself.config_file = config_file\n\n\t\tid = None\n\t\tmax_size = 64\n\t\tfor inter in self.device[0]:\n\t\t\tfor setting in inter:\n\t\t\t\tif setting.getClass() == DEV_CLASS_HID:\n\t\t\t\t\tfor endpoint in setting:\n\t\t\t\t\t\tif endpoint.getAttributes() == TRANSFER_TYPE_INTERRUPT:\n\t\t\t\t\t\t\tif id is None or endpoint.getAddress() > id:\n\t\t\t\t\t\t\t\tid = endpoint.getAddress()\n\t\t\t\t\t\t\t\tmax_size = endpoint.getMaxPacketSize()\n\n\t\tif id is None:\n\t\t\traise NotHIDDevice()\n\n\t\tlog.debug(\"Endpoint: %s\", id)\n\n\t\tvid, pid = self.device.getVendorID(), self.device.getProductID()\n\t\tif (vid, pid) in BLACKLIST:\n\t\t\traise NotHIDDevice(\"Blacklisted device: %x:%x\", vid, pid)\n\t\tself._packet_size = 64\n\t\tself._load_hid_descriptor(config, max_size, vid, pid, test_mode)\n\t\tself.claim_by(klass=DEV_CLASS_HID, subclass=0, protocol=0)\n\t\tController.__init__(self)\n\n\t\tif test_mode:\n\t\t\tself.set_input_interrupt(id, self._packet_size, self.test_input)\n\n\n\t\t\tprint(\"Buttons:\", \" \".join([ str(x + FIRST_BUTTON)\n\t\t\t\t\tfor x in range(self._decoder.buttons.button_count) ]))\n\t\t\tprint(\"Axes:\", \" \".join([ str(x)\n\t\t\t\t\tfor x in range(len([\n\t\t\t\t\t\ta for a in self._decoder.axes\n\t\t\t\t\t\tif a.mode != AxisMode.DISABLED\n\t\t\t\t\t]))]))\n\t\telse:\n\t\t\tself._id = self._generate_id()\n\t\t\tself.set_input_interrupt(id, self._packet_size, self.input)\n\t\t\tself.daemon.add_controller(self)\n\t\t\tself._ready = True\n\n\n\tdef _load_hid_descriptor(self, config, max_size, vid, pid, test_mode):\n\t\thid_descriptor = HIDController.find_sys_devices_descriptor(vid, pid)\n\t\tif hid_descriptor is None:\n\t\t\thid_descriptor = self.handle.getRawDescriptor(\n\t\t\t\t\tLIBUSB_DT_REPORT, 0, 512)\n\t\topen(\"report\", \"wb\").write(bytes([x for x in hid_descriptor ]))\n\t\tself._build_hid_decoder(hid_descriptor, config, max_size)\n\t\tself._packet_size = self._decoder.packet_size\n\n\n\tdef _build_button_map(self, config):\n\t\t\"\"\"\n\t\tReturns button  map readed from configuration, in format situable\n\t\tfor HIDDecoder.buttons.button_map field.\n\n\t\tGenerates default if config is not available.\n\t\t\"\"\"\n\t\tif config:\n\t\t\t# Last possible value is default \"maps-to-nothing\" mapping\n\t\t\tbuttons = [BUTTON_COUNT - 1] * BUTTON_COUNT\n\t\t\tfor keycode, value in config.get('buttons', {}).items():\n\t\t\t\tkeycode = int(keycode) - FIRST_BUTTON\n\t\t\t\tif keycode < 0 or keycode >= BUTTON_COUNT:\n\t\t\t\t\t# Out of range\n\t\t\t\t\tcontinue\n\t\t\t\tif value in TRIGGERS:\n\t\t\t\t\t# Not used here\n\t\t\t\t\tpass\n\t\t\t\telse:\n\t\t\t\t\tbuttons[keycode] = self.button_to_bit(getattr(SCButtons, value))\n\t\telse:\n\t\t\tbuttons = list(range(BUTTON_COUNT))\n\n\t\treturn (ctypes.c_uint8 * BUTTON_COUNT)(*buttons)\n\n\n\t@staticmethod\n\tdef button_to_bit(sc):\n\t\tsc, bit = int(sc), 0\n\t\twhile sc and (sc & 1 == 0):\n\t\t\tsc >>= 1\n\t\t\tbit += 1\n\t\tif sc & 1 == 1:\n\t\t\treturn bit\n\t\treturn BUTTON_COUNT - 1\n\n\n\tdef _build_axis_maping(self, axis, config, mode = AxisMode.AXIS):\n\t\t\"\"\"\n\t\tConverts configuration mapping for _one_ axis to value situable\n\t\tfor self._decoder.axes field.\n\t\t\"\"\"\n\t\taxis_config = config.get(\"axes\", {}).get(str(int(axis)))\n\t\tif axis_config:\n\t\t\ttry:\n\t\t\t\ttarget = ( list([ x for (x, y) in HIDControllerInput._fields_ ])\n\t\t\t\t\t.index(axis_config.get(\"axis\")) - 1 )\n\t\t\texcept Exception:\n\t\t\t\t# Maps to unknown axis\n\t\t\t\treturn None, None\n\t\t\tcdata = parse_axis(axis_config)\n\t\t\tbutton = 0\n\t\t\tif AxisType(target) in (AxisType.AXIS_LPAD_X, AxisType.AXIS_LPAD_Y):\n\t\t\t\tbutton = (SCButtons.LPADTOUCH | SCButtons.LPAD)\n\t\t\telif AxisType(target) in (AxisType.AXIS_RPAD_X, AxisType.AXIS_RPAD_Y):\n\t\t\t\tbutton = SCButtons.RPAD\n\t\t\tif mode == AxisMode.AXIS:\n\t\t\t\taxis_data = AxisData(\n\t\t\t\t\tmode = AxisMode.AXIS,\n\t\t\t\t\tdata = AxisDataUnion(\n\t\t\t\t\t\taxis = AxisModeData(button = button, **{\n\t\t\t\t\t\t\tfield : getattr(cdata, field) for field in cdata._fields\n\t\t\t\t\t\t})\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\telif mode == AxisMode.HATSWITCH:\n\t\t\t\taxis_data = AxisData(\n\t\t\t\t\tmode = AxisMode.HATSWITCH,\n\t\t\t\t\tdata = AxisDataUnion(\n\t\t\t\t\t\thatswitch = HatswitchModeData(\n\t\t\t\t\t\t\tbutton = button,\n\t\t\t\t\t\t\tmax = axis_config['max'],\n\t\t\t\t\t\t\tmin = axis_config['min']\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\telse:\n\t\t\t\taxis_data = AxisData(mode = AxisMode.DISABLED)\n\t\t\treturn target, axis_data\n\t\treturn None, None\n\n\n\tdef _build_hid_decoder(self, data, config, max_size):\n\t\tsize, count, total, kind = 1, 0, 0, None\n\t\tnext_axis = AxisType.AXIS_LPAD_X\n\t\tself._decoder = HIDDecoder()\n\t\tfor x in parse_report_descriptor(data, True):\n\t\t\tif x[0] == GlobalItem.ReportSize:\n\t\t\t\tsize = x[1]\n\t\t\telif x[0] == GlobalItem.ReportCount:\n\t\t\t\tcount = x[1]\n\t\t\telif x[0] == LocalItem.Usage:\n\t\t\t\tkind = x[1]\n\t\t\telif x[0] == MainItem.Input:\n\t\t\t\tif x[1] == ItemType.Constant:\n\t\t\t\t\ttotal += count * size\n\t\t\t\t\tlog.debug(\"Found %s bits of nothing\", count * size)\n\t\t\t\telif x[1] == ItemType.Data:\n\t\t\t\t\tif kind in AXES:\n\t\t\t\t\t\tif not size in ALLOWED_SIZES:\n\t\t\t\t\t\t\traise UnparsableDescriptor(\"Axis with invalid size (%s bits)\" % (size, ))\n\t\t\t\t\t\tfor i in range(count):\n\t\t\t\t\t\t\tif next_axis < AXIS_COUNT:\n\t\t\t\t\t\t\t\tlog.debug(\"Found axis #%s at bit %s\", int(next_axis), total)\n\t\t\t\t\t\t\t\tif config:\n\t\t\t\t\t\t\t\t\ttarget, axis_data = self._build_axis_maping(next_axis, config)\n\t\t\t\t\t\t\t\t\tif axis_data:\n\t\t\t\t\t\t\t\t\t\taxis_data.byte_offset = total // 8\n\t\t\t\t\t\t\t\t\t\taxis_data.bit_offset = total % 8\n\t\t\t\t\t\t\t\t\t\taxis_data.size = size\n\t\t\t\t\t\t\t\t\t\tself._decoder.axes[target] = axis_data\n\t\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\t\tself._decoder.axes[next_axis] = AxisData(mode = AxisMode.AXIS_NO_SCALE)\n\t\t\t\t\t\t\t\t\tself._decoder.axes[next_axis].byte_offset = total // 8\n\t\t\t\t\t\t\t\t\tself._decoder.axes[next_axis].bit_offset = total % 8\n\t\t\t\t\t\t\t\t\tself._decoder.axes[next_axis].size = size\n\t\t\t\t\t\t\t\tnext_axis = next_axis + 1\n\t\t\t\t\t\t\t\tif next_axis < AXIS_COUNT:\n\t\t\t\t\t\t\t\t\tnext_axis = AxisType(next_axis)\n\t\t\t\t\t\t\ttotal += size\n\t\t\t\t\telif kind == GenericDesktopPage.Hatswitch:\n\t\t\t\t\t\tif count * size != 4:\n\t\t\t\t\t\t\traise UnparsableDescriptor(\"Invalid size for Hatswitch (%sb)\" % (count * size, ))\n\t\t\t\t\t\tif next_axis + 1 < AXIS_COUNT:\n\t\t\t\t\t\t\tlog.debug(\"Found hat #%s at bit %s\", int(next_axis), total)\n\t\t\t\t\t\t\tif config:\n\t\t\t\t\t\t\t\ttarget, axis_data = self._build_axis_maping(next_axis, config, AxisMode.HATSWITCH)\n\t\t\t\t\t\t\t\tif axis_data:\n\t\t\t\t\t\t\t\t\taxis_data.byte_offset = total // 8\n\t\t\t\t\t\t\t\t\taxis_data.bit_offset = total % 8\n\t\t\t\t\t\t\t\t\tself._decoder.axes[target] = axis_data\n\t\t\t\t\t\t\telse:\n\t\t\t\t\t\t\t\tself._decoder.axes[next_axis] = AxisData(mode = AxisMode.HATSWITCH)\n\t\t\t\t\t\t\t\tself._decoder.axes[next_axis].byte_offset = total // 8\n\t\t\t\t\t\t\t\tself._decoder.axes[next_axis].bit_offset = total % 8\n\t\t\t\t\t\t\t\tself._decoder.axes[next_axis].data.hatswitch.min = STICK_PAD_MIN\n\t\t\t\t\t\t\t\tself._decoder.axes[next_axis].data.hatswitch.max = STICK_PAD_MAX\n\t\t\t\t\t\t\t# Hatswitch is little special as it covers 2 axes at once\n\t\t\t\t\t\t\tnext_axis = next_axis + 2\n\t\t\t\t\t\t\tif next_axis < AXIS_COUNT:\n\t\t\t\t\t\t\t\tnext_axis = AxisType(next_axis)\n\t\t\t\t\t\ttotal += 4\n\t\t\t\t\telif kind == UsagePage.ButtonPage:\n\t\t\t\t\t\tif self._decoder.buttons.enabled:\n\t\t\t\t\t\t\traise UnparsableDescriptor(\"HID descriptor with two sets of buttons\")\n\t\t\t\t\t\tif count * size < 8:\n\t\t\t\t\t\t\tbuttons_size = 8\n\t\t\t\t\t\telif count * size < 32:\n\t\t\t\t\t\t\tbuttons_size = 32\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\traise UnparsableDescriptor(\"Too many buttons (up to 32 supported)\")\n\t\t\t\t\t\tlog.debug(\"Found %s buttons at bit %s\", count, total)\n\t\t\t\t\t\tself._decoder.buttons = ButtonData(\n\t\t\t\t\t\t\tenabled = True,\n\t\t\t\t\t\t\tbyte_offset = total // 8,\n\t\t\t\t\t\t\tbit_offset = total % 8,\n\t\t\t\t\t\t\tsize = buttons_size,\n\t\t\t\t\t\t\tbutton_count = count,\n\t\t\t\t\t\t\tbutton_map = self._build_button_map(config)\n\t\t\t\t\t\t)\n\t\t\t\t\t\ttotal += count * size\n\t\t\t\t\telse:\n\t\t\t\t\t\tlog.debug(\"Skipped over %s bits for %s at bit %s\", count * size, kind, total)\n\t\t\t\t\t\ttotal += count * size\n\n\t\tself._decoder.packet_size = total // 8\n\t\tif total % 8 > 0:\n\t\t\tself._decoder.packet_size += 1\n\t\tif self._decoder.packet_size > max_size:\n\t\t\tself._decoder.packet_size = max_size\n\t\tlog.debug(\"Packet size: %s\", self._decoder.packet_size)\n\n\n\t@staticmethod\n\tdef find_sys_devices_descriptor(vid, pid):\n\t\t\"\"\"\n\t\tFinds, loads and returns HID descriptor available somewhere deep in\n\t\t/sys/devices structure.\n\n\t\tDone by walking /sys/devices recursivelly, searching for file named\n\t\t'report_descriptor' in subdirectory with name contining vid and pid.\n\n\t\tThis is very much prefered before loading HID descriptor from device,\n\t\tas some controllers are presenting descriptor that are completly\n\t\tbroken and kernel already deals with it.\n\t\t\"\"\"\n\t\tdef recursive_search(pattern, path):\n\t\t\tfor name in os.listdir(path):\n\t\t\t\tfull_path = os.path.join(path, name)\n\t\t\t\tif name == \"report_descriptor\":\n\t\t\t\t\tif pattern in os.path.split(path)[-1].lower():\n\t\t\t\t\t\treturn full_path\n\t\t\t\ttry:\n\t\t\t\t\tif os.path.islink(full_path):\n\t\t\t\t\t\t# Recursive stuff in /sys ftw...\n\t\t\t\t\t\tcontinue\n\t\t\t\t\tif os.path.isdir(full_path):\n\t\t\t\t\t\tr = recursive_search(pattern, full_path)\n\t\t\t\t\t\tif r: return r\n\t\t\t\texcept IOError:\n\t\t\t\t\tpass\n\t\t\treturn None\n\n\t\tpattern = \":%.4x:%.4x\" % (vid, pid)\n\t\tfull_path = recursive_search(pattern, SYS_DEVICES)\n\t\ttry:\n\t\t\tif full_path:\n\t\t\t\tlog.debug(\"Loading descriptor from '%s'\", full_path)\n\t\t\t\ttemp_list = None\n\t\t\t\twith open(full_path, \"rb\") as read_file:\n\t\t\t\t\ttemp_list = [ x for x in read_file.read(1024)]\n\n\t\t\t\treturn temp_list\n\t\texcept Exception as e:\n\t\t\tlog.exception(e)\n\t\treturn None\n\n\n\tdef close(self):\n\t\t# Called when pad is disconnected\n\t\tUSBDevice.close(self)\n\t\tif self._ready:\n\t\t\tself.daemon.remove_controller(self)\n\t\t\tself._ready = False\n\n\n\tdef get_type(self):\n\t\treturn \"hid\"\n\n\n\tdef _generate_id(self):\n\t\t\"\"\"\n\t\tID is generated as 'hid0000:1111' where first number is vendor and\n\t\t2nd product id. If two or more controllers with same vendor/product\n\t\tIDs are added, ':X' is added, where 'X' starts as 1 and increases\n\t\tas controllers with same ids are connected.\n\t\t\"\"\"\n\t\tmagic_number = 1\n\t\tvid, pid = self.device.getVendorID(), self.device.getProductID()\n\t\tid = \"hid%.4x:%.4x\" % (vid, pid)\n\t\twhile id in self.daemon.get_active_ids():\n\t\t\tid = \"hid%.4x:%.4x:%s\" % (vid, pid, magic_number)\n\t\t\tmagic_number += 1\n\t\treturn id\n\n\n\tdef get_id(self):\n\t\treturn self._id\n\n\n\tdef get_gui_config_file(self):\n\t\treturn self.config_file\n\n\n\tdef __repr__(self):\n\t\tvid, pid = self.device.getVendorID(), self.device.getProductID()\n\t\treturn \"<HID %.4x%.4x>\" % (vid, pid)\n\n\n\tdef test_input(self, endpoint, data):\n\t\tif not _lib.decode(ctypes.byref(self._decoder), data):\n\t\t\t# Returns True if anything changed\n\t\t\treturn\n\t\t# Note: This is quite slow, but good enough for test mode\n\t\tcode = 0\n\t\tfor attr, trash in self._decoder.state._fields_:\n\t\t\tif attr == \"buttons\": continue\n\t\t\tif getattr(self._decoder.state, attr) != getattr(self._decoder.old_state, attr):\n\t\t\t\t# print(\"Axis\", code, getattr(self._decoder.state, attr))\n\t\t\t\tsys.stdout.flush()\n\t\t\tcode += 1\n\n\t\tpressed = self._decoder.state.buttons & ~self._decoder.old_state.buttons\n\t\treleased = self._decoder.old_state.buttons & ~self._decoder.state.buttons\n\t\tfor j in range(0, self._decoder.buttons.button_count):\n\t\t\tmask = 1 << j\n\t\t\tif pressed & mask:\n\t\t\t\tprint(\"ButtonPress\", FIRST_BUTTON + j)\n\t\t\t\tsys.stdout.flush()\n\t\t\tif released & mask:\n\t\t\t\tprint(\"ButtonRelease\", FIRST_BUTTON + j)\n\t\t\t\tsys.stdout.flush()\n\n\n\tdef input(self, endpoint, data):\n\t\tif _lib.decode(ctypes.byref(self._decoder), data):\n\t\t\tif self.mapper:\n\t\t\t\tself.mapper.input(self,\n\t\t\t\t\t\tself._decoder.old_state, self._decoder.state)\n\n\n\tdef apply_config(self, config):\n\t\t# TODO: This?\n\t\tpass\n\n\n\tdef disconnected(self):\n\t\t# TODO: This!\n\t\tpass\n\n\n\t# def configure(self, idle_timeout=None, enable_gyros=None, led_level=None):\n\n\n\tdef set_led_level(self, level):\n\t\t# TODO: This?\n\t\tpass\n\n\n\tdef set_gyro_enabled(self, enabled):\n\t\t# TODO: This, maybe.\n\t\tpass\n\n\nclass HIDDrv(object):\n\n\tdef __init__(self, daemon):\n\t\tself.registered = set()\n\t\tself.config_files = {}\n\t\tself.configs = {}\n\t\tself.scan_files()\n\t\tself.daemon = daemon\n\n\n\tdef hotplug_cb(self, device, handle):\n\t\tvid, pid = device.getVendorID(), device.getProductID()\n\t\tif (vid, pid) in self.configs:\n\t\t\tcontroller = HIDController(device, self.daemon, handle,\n\t\t\t\tself.config_files[vid, pid], self.configs[vid, pid])\n\t\t\treturn controller\n\t\treturn None\n\n\n\tdef scan_files(self):\n\t\t\"\"\"\n\t\tGoes through ~/.config/scc/devices and enables hotplug callback for\n\t\tevery known HID device\n\t\t\"\"\"\n\t\tpath = os.path.join(get_config_path(), \"devices\")\n\t\tif not os.path.exists(path):\n\t\t\t# Nothing to do\n\t\t\treturn\n\n\t\tknown = set()\n\t\tfor name in os.listdir(path):\n\t\t\tif name.startswith(\"hid-\") and name.endswith(\".json\"):\n\t\t\t\tvid, pid = name.split(\"-\", 2)[1].split(\":\")[0:2]\n\t\t\t\tvid = int(vid, 16)\n\t\t\t\tpid = int(pid, 16)\n\t\t\t\tconfig_file = os.path.join(path, name)\n\t\t\t\ttry:\n\t\t\t\t\tconfig = json.loads(open(config_file, \"r\").read())\n\t\t\t\texcept Exception:\n\t\t\t\t\tlog.warning(\"Ignoring file that cannot be parsed: %s\", name)\n\t\t\t\t\tcontinue\n\n\t\t\t\tself.config_files[vid, pid] = config_file.decode(\"utf-8\")\n\t\t\t\tself.configs[vid, pid] = config\n\t\t\t\tknown.add((vid, pid))\n\n\t\tfor new in known - self.registered:\n\t\t\tvid, pid = new\n\t\t\tregister_hotplug_device(self.hotplug_cb, vid, pid)\n\t\t\tself.registered.add(new)\n\n\t\tfor removed in self.registered - known:\n\t\t\tvid, pid = removed\n\t\t\tunregister_hotplug_device(self.hotplug_cb, vid, pid)\n\t\t\tself.registered.remove(removed)\n\t\t\tif (vid, pid) in self.config_files:\n\t\t\t\tdel self.config_files[vid, pid]\n\t\t\tif (vid, pid) in self.configs:\n\t\t\t\tdel self.configs[vid, pid]\n\ndef hiddrv_test(cls, args):\n\t\"\"\"\n\tSmall input test used by GUI while setting up the device.\n\tBasically, if HID device works with this, it will work with daemon as well.\n\t\"\"\"\n\tfrom scc.poller import Poller\n\tfrom scc.drivers.usb import _usb\n\tfrom scc.device_monitor import create_device_monitor\n\tfrom scc.scripts import InvalidArguments\n\n\ttry:\n\t\tif \":\" in args[0]:\n\t\t\targs[0:1] = args[0].split(\":\")\n\t\tvid = int(args[0], 16)\n\t\tpid = int(args[1], 16)\n\texcept Exception:\n\t\traise InvalidArguments()\n\n\tclass FakeDaemon(object):\n\n\t\tdef __init__(self):\n\t\t\tself.poller = Poller()\n\t\t\tself.dev_monitor = create_device_monitor(self)\n\t\t\tself.exitcode = -1\n\n\t\tdef get_device_monitor(self):\n\t\t\treturn self.dev_monitor\n\n\t\tdef add_error(self, id, error):\n\t\t\tfake_daemon.exitcode = 2\n\t\t\tlog.error(error)\n\n\t\tdef remove_error(*a): pass\n\n\t\tdef get_poller(self):\n\t\t\treturn self.poller\n\n\tfake_daemon = FakeDaemon()\n\n\tdef cb(device, handle):\n\t\ttry:\n\t\t\treturn cls(device, None, handle, None, None, test_mode=True)\n\t\texcept NotHIDDevice:\n\t\t\tprint(\"%.4x:%.4x is not a HID device\" % (vid, pid), file=sys.stderr)\n\t\t\tfake_daemon.exitcode = 3\n\t\texcept UnparsableDescriptor as e:\n\t\t\tprint(\"Invalid or unparsable HID descriptor\", str(e), file=sys.stderr)\n\t\t\tfake_daemon.exitcode = 4\n\t\texcept Exception as e:\n\t\t\tprint(\"Failed to open device:\", str(e), file=sys.stderr)\n\t\t\tfake_daemon.exitcode = 2\n\n\t_usb.set_daemon(fake_daemon)\n\tregister_hotplug_device(cb, vid, pid)\n\tfake_daemon.dev_monitor.start()\n\t_usb.start()\n\tfake_daemon.dev_monitor.rescan()\n\n\tif fake_daemon.exitcode < 0:\n\t\tprint(\"Ready\")\n\tsys.stdout.flush()\n\twhile fake_daemon.exitcode < 0:\n\t\tfake_daemon.poller.poll()\n\t\t_usb.mainloop()\n\n\treturn fake_daemon.exitcode\n\ndef init(daemon, config):\n\t\"\"\" Called from scc-daemon \"\"\"\n\td = HIDDrv(daemon)\n\tdaemon.add_on_rescan(d.scan_files)\n\treturn True\n\n\nif __name__ == \"__main__\":\n\t\"\"\" Called when executed as script \"\"\"\n\tfrom scc.tools import init_logging, set_logging_level\n\tinit_logging()\n\tset_logging_level(True, True)\n\tsys.exit(hiddrv_test(HIDController, sys.argv[1:]))\n\n"
  },
  {
    "path": "scc/drivers/remotepad.h",
    "content": "/**\n * SC Controller - remotepad driver\n *\n * This is implementation or protocol used by Retroarch's Remote RetroPad core.\n *\n * Based on https://github.com/libretro/RetroArch/blob/master/cores/libretro-net-retropad.\n */\n#include \"scc_future.h\"\n\n/** MAX_DESC_LEN has to fit \"<RemotePad at 255.255.255.255>\" */\n#define MAX_DESC_LEN\t32\n#define MAX_ID_LEN\t\t24\n\nstruct remote_joypad_message {\n\tint port;\n\tint device;\n\tint index;\n\tint id;\n\tuint16_t state;\n};\n\ntypedef struct RemotePad {\n\tMapper*\t\t\t\t\tmapper;\n\tControllerInput\t\t\tinput;\n} RemotePad;\n\n\nvoid remotepad_input(RemotePad* pad, struct remote_joypad_message* msg);\n\n////// Following are declarations from libretro //////\n\n// Buttons for the RetroPad (JOYPAD).\n// The placement of these is equivalent to placements on the Super Nintendo controller.\n// L2/R2/L3/R3 buttons correspond to the PS1 DualShock.\n#define RETRO_DEVICE_ID_JOYPAD_B\t\t\t0\n#define RETRO_DEVICE_ID_JOYPAD_Y\t\t\t1\n#define RETRO_DEVICE_ID_JOYPAD_SELECT\t\t2\n#define RETRO_DEVICE_ID_JOYPAD_START\t\t3\n#define RETRO_DEVICE_ID_JOYPAD_UP\t\t\t4\n#define RETRO_DEVICE_ID_JOYPAD_DOWN\t\t\t5\n#define RETRO_DEVICE_ID_JOYPAD_LEFT\t\t\t6\n#define RETRO_DEVICE_ID_JOYPAD_RIGHT\t\t7\n#define RETRO_DEVICE_ID_JOYPAD_A\t\t\t8\n#define RETRO_DEVICE_ID_JOYPAD_X\t\t\t9\n#define RETRO_DEVICE_ID_JOYPAD_L\t\t\t10\n#define RETRO_DEVICE_ID_JOYPAD_R\t\t\t11\n#define RETRO_DEVICE_ID_JOYPAD_L2\t\t\t12\n#define RETRO_DEVICE_ID_JOYPAD_R2\t\t\t13\n#define RETRO_DEVICE_ID_JOYPAD_L3\t\t\t14\n#define RETRO_DEVICE_ID_JOYPAD_R3\t\t\t15\n\n#define RETRO_DEVICE_JOYPAD\t\t\t\t\t1\n#define RETRO_DEVICE_ANALOG\t\t\t\t\t5\n#define RETRO_DEVICE_INDEX_ANALOG_LEFT\t\t0\n#define RETRO_DEVICE_INDEX_ANALOG_RIGHT\t\t1\n#define RETRO_DEVICE_ID_ANALOG_X\t\t\t0\n#define RETRO_DEVICE_ID_ANALOG_Y\t\t\t1\n"
  },
  {
    "path": "scc/drivers/remotepad.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - remotepad driver\n\nThis is implementation or protocol used by Retroarch's Remote RetroPad core.\nBased on https://github.com/libretro/RetroArch/blob/master/cores/libretro-net-retropad.\n\"\"\"\nfrom scc.tools import find_library\nfrom scc.constants import ControllerFlags\nfrom scc.controller import Controller\nfrom ctypes import CFUNCTYPE, POINTER, byref, cast, c_void_p\nimport logging, socket, ctypes\n\nlog = logging.getLogger(\"remotepad\")\n\n\nclass ControllerInput(ctypes.Structure):\n\t_fields_ = [\n\t\t(\"buttons\",\t\tctypes.c_uint32),\n\t\t(\"ltrig\",\t\tctypes.c_uint8),\n\t\t(\"rtrig\",\t\tctypes.c_uint8),\n\t\t(\"stick_x\",\t\tctypes.c_int16),\n\t\t(\"stick_y\",\t\tctypes.c_int16),\n\t\t(\"lpad_x\",\t\tctypes.c_int16),\n\t\t(\"lpad_y\",\t\tctypes.c_int16),\n\t\t(\"rpad_x\",\t\tctypes.c_int16),\n\t\t(\"rpad_y\",\t\tctypes.c_int16),\n\t\t(\"cpad_x\",\t\tctypes.c_int16),\n\t\t(\"cpad_y\",\t\tctypes.c_int16),\n\t\t(\"gpitch\",\t\tctypes.c_int16),\n\t\t(\"groll\",\t\tctypes.c_int16),\n\t\t(\"gyaw\",\t\tctypes.c_int16),\n\t\t(\"q1\",\t\t\tctypes.c_int16),\n\t\t(\"q2\",\t\t\tctypes.c_int16),\n\t\t(\"q3\",\t\t\tctypes.c_int16),\n\t\t(\"q4\",\t\t\tctypes.c_int16),\n\t]\n\n\nMapperInputCB = CFUNCTYPE(None, c_void_p, POINTER(ControllerInput))\n\n\nclass Mapper(ctypes.Structure):\n\t_fields_ = [\n\t\t(\"input\",\t\tMapperInputCB),\n\t]\n\n\nclass RemotePad(ctypes.Structure):\n\t_fields_ = [\n\t\t(\"mapper\",\t\tPOINTER(Mapper)),\n\t\t(\"input\",\t\tControllerInput),\n\t]\n\n\nclass RemoteJoypadMessage(ctypes.Structure):\n\t_fields_ = [\n\t\t('port',\t\tctypes.c_int),\n\t\t('device',\t\tctypes.c_int),\n\t\t('index',\t\tctypes.c_int),\n\t\t('id',\t\t\tctypes.c_int),\n\t\t('state',\t\tctypes.c_uint16),\n\t]\n\n\nclass RemotePadController(Controller):\n\tflags = ( ControllerFlags.HAS_DPAD | ControllerFlags.NO_GRIPS |\n\t\t\t\tControllerFlags.HAS_RSTICK | ControllerFlags.SEPARATE_STICK )\n\t\n\tdef __init__(self, driver, address):\n\t\tController.__init__(self)\n\t\tself._id = \"rpad%s\" % (self._id, )\n\t\tself._driver = driver\n\t\tself._address = address\n\t\tself._enabled = True\n\t\tself._mapper = Mapper()\n\t\tself._mapper.input = MapperInputCB(self._input)\n\t\tself._old_state = ControllerInput()\n\t\tself._state_size = ctypes.sizeof(ControllerInput)\n\t\tself._pad = RemotePad()\n\t\tself._pad.mapper = POINTER(Mapper)(self._mapper)\n\t\n\tdef get_type(self):\n\t\treturn \"rpad\"\n\t\n\tdef _remove(self, *a):\n\t\tself._driver._remove(self._address)\n\t\n\tdef turnoff(self):\n\t\tlog.debug(\"Disconnecting %s\", self._address)\n\t\tself._disabled = True\n\t\tself._driver.daemon.remove_controller(self)\n\t\tself._driver.daemon.get_scheduler().schedule(10.0, self._remove)\n\t\n\tdef _input(self, trash, data):\n\t\tif self._enabled and self.mapper:\n\t\t\tself.mapper.input(self, self._old_state, data.contents)\n\t\t\tctypes.memmove(byref(self._old_state), data, self._state_size)\n\t\n\tdef get_gui_config_file(self):\n\t\treturn \"remotepad.json\"\n\n\nclass Driver:\n\tPORT = 55400\n\t\n\tdef __init__(self, daemon, config):\n\t\tself._controllers = {}\n\t\tself.daemon = daemon\n\t\tself.config = config\n\t\tself._lib = find_library('libremotepad')\n\t\tself._lib.remotepad_input.argtypes = [ POINTER(RemotePad), POINTER(RemoteJoypadMessage) ]\n\t\tself._lib.remotepad_input.restype = None\n\t\tself._size = ctypes.sizeof(RemoteJoypadMessage)\n\t\tself.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n\t\tself.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n\t\tserver_address = ('0.0.0.0', self.PORT)\n\t\tself.sock.bind(server_address)\n\t\tpoller = self.daemon.get_poller()\n\t\tpoller.register(self.sock.fileno(), poller.POLLIN, self.on_data_ready)\n\t\tlog.info(\"Listening on %s:%s\", *server_address)\n\t\n\tdef _remove(self, address):\n\t\tif address in self._controllers:\n\t\t\tdel self._controllers[address]\n\t\n\tdef on_data_ready(self, *a):\n\t\tdata, source = self.sock.recvfrom(self._size)\n\t\taddress, port = source\n\t\tcontroller = None\n\t\tif address not in self._controllers:\n\t\t\tcontroller = RemotePadController(self, address)\n\t\t\tself._controllers[address] = controller\n\t\t\tself.daemon.add_controller(controller)\n\t\telse:\n\t\t\tcontroller = self._controllers[address]\n\t\t\n\t\tself._lib.remotepad_input(controller._pad, cast(\n\t\t\t\tctypes.c_char_p(data), POINTER(RemoteJoypadMessage)))\n\n\ndef init(daemon, config):\n\t\"\"\" Registers hotplug callback for controller dongle \"\"\"\n\t\n\t_drv = Driver(daemon, config)\n\treturn True\n"
  },
  {
    "path": "scc/drivers/remotepad_controller.c",
    "content": "/**\n * SC Controller - remotepad driver\n *\n * This is implementation or protocol used by Retroarch's Remote RetroPad core.\n *\n * Based on https://github.com/libretro/RetroArch/blob/master/cores/libretro-net-retropad.\n */\n\n#include <stdlib.h>\n#include <stddef.h>\n#include <stdio.h>\n#include \"remotepad.h\"\n\n#define REMOTEPAD_MODULE_VERSION 1\n\nstatic uint32_t next_id = 0;\n\ninline static SCButton libretro_button_to_sc_button(int id) {\n\tswitch (id) {\n\tcase RETRO_DEVICE_ID_JOYPAD_B:\t\t\treturn B_B;\n\tcase RETRO_DEVICE_ID_JOYPAD_Y:\t\t\treturn B_Y;\n\tcase RETRO_DEVICE_ID_JOYPAD_SELECT:\t\treturn B_BACK;\n\tcase RETRO_DEVICE_ID_JOYPAD_START:\t\treturn B_START;\n\tcase RETRO_DEVICE_ID_JOYPAD_A:\t\t\treturn B_A;\n\tcase RETRO_DEVICE_ID_JOYPAD_X:\t\t\treturn B_X;\n\tcase RETRO_DEVICE_ID_JOYPAD_L:\t\t\treturn B_LB;\n\tcase RETRO_DEVICE_ID_JOYPAD_R:\t\t\treturn B_RB;\n\tcase RETRO_DEVICE_ID_JOYPAD_L2:\t\t\treturn B_LT;\n\tcase RETRO_DEVICE_ID_JOYPAD_R2:\t\t\treturn B_RT;\n\tcase RETRO_DEVICE_ID_JOYPAD_L3:\t\t\treturn B_STICKPRESS;\n\tcase RETRO_DEVICE_ID_JOYPAD_R3:\t\t\treturn B_RPADPRESS;\n\tdefault:\n\t\treturn 0;\n\t}\n}\n\n\nvoid remotepad_input(RemotePad* pad, struct remote_joypad_message* msg) {\n\tSCButton b;\n\t// LOG(\"on_data_ready %i %i %i %i\", msg->device, msg->index, msg->id, msg->state);\n\t\n\tif (sizeof(SCButton) != sizeof(uint32_t)) {\n\t\tprintf(\"sizeof(SCButton) != sizeof(uint32_t): %zu != %zu\\n\",\n\t\t\t\tsizeof(SCButton), sizeof(uint32_t));\n\t}\n\t\n\tswitch (msg->device) {\n\tcase RETRO_DEVICE_JOYPAD:\n\t\tb = libretro_button_to_sc_button(msg->id);\n\t\tif (b != 0) {\n\t\t\tif (msg->state)\n\t\t\t\tpad->input.buttons |= b;\n\t\t\telse\n\t\t\t\tpad->input.buttons &= ~b;\n\t\t}\n\t\t\n\t\tswitch (msg->id) {\n\t\tcase RETRO_DEVICE_ID_JOYPAD_UP:\n\t\t\tpad->input.lpad_y = (msg->state) ? STICK_PAD_MAX : 0;\n\t\t\tbreak;\n\t\tcase RETRO_DEVICE_ID_JOYPAD_DOWN:\n\t\t\tpad->input.lpad_y = (msg->state) ? STICK_PAD_MIN : 0;\n\t\t\tbreak;\n\t\tcase RETRO_DEVICE_ID_JOYPAD_LEFT:\n\t\t\tpad->input.lpad_x = (msg->state) ? STICK_PAD_MIN : 0;\n\t\t\tbreak;\n\t\tcase RETRO_DEVICE_ID_JOYPAD_RIGHT:\n\t\t\tpad->input.lpad_x = (msg->state) ? STICK_PAD_MAX : 0;\n\t\t\tbreak;\n\t\tcase RETRO_DEVICE_ID_JOYPAD_L2:\n\t\t\tpad->input.ltrig = (msg->state) ? TRIGGER_MAX : 0;\n\t\t\tbreak;\n\t\tcase RETRO_DEVICE_ID_JOYPAD_R2:\n\t\t\tpad->input.rtrig = (msg->state) ? TRIGGER_MAX : 0;\n\t\t\tbreak;\n\t\tcase RETRO_DEVICE_ID_JOYPAD_SELECT:\n\t\tcase RETRO_DEVICE_ID_JOYPAD_START:\n\t\t\t// If both start+select are pressed, \"C\" button is emulated\n\t\t\tif ((pad->input.buttons & (B_BACK | B_START)) == (B_BACK | B_START)) {\n\t\t\t\tpad->input.buttons &= ~(B_BACK | B_START);\n\t\t\t\tpad->input.buttons |= B_C;\n\t\t\t} else {\n\t\t\t\tpad->input.buttons &= ~B_C;\n\t\t\t}\n\t\t}\n\t\n\tcase RETRO_DEVICE_ANALOG:\n\t\tswitch (msg->index) {\n\t\tcase RETRO_DEVICE_INDEX_ANALOG_LEFT:\n\t\t\tswitch (msg->id) {\n\t\t\tcase RETRO_DEVICE_ID_ANALOG_X:\n\t\t\t\tpad->input.stick_x = (AxisValue)msg->state;\n\t\t\t\tbreak;\n\t\t\tcase RETRO_DEVICE_ID_ANALOG_Y:\n\t\t\t\tpad->input.stick_y = -(AxisValue)msg->state;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase RETRO_DEVICE_INDEX_ANALOG_RIGHT:\n\t\t\tswitch (msg->id) {\n\t\t\tcase RETRO_DEVICE_ID_ANALOG_X:\n\t\t\t\tpad->input.rpad_x = (AxisValue)msg->state;\n\t\t\t\tbreak;\n\t\t\tcase RETRO_DEVICE_ID_ANALOG_Y:\n\t\t\t\tpad->input.rpad_y = -(AxisValue)msg->state;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\t\n\tpad->mapper->input(pad->mapper, &pad->input);\n}\n\n\nconst int remotepad_module_version(void) {\n\treturn REMOTEPAD_MODULE_VERSION;\n}\n"
  },
  {
    "path": "scc/drivers/sc_by_bt.c",
    "content": "#define PY_SSIZE_T_CLEAN\n#include <Python.h>\n#include <inttypes.h>\n#include <stdbool.h>\n#include <limits.h>\n\n#define SC_BY_BT_MODULE_VERSION 3\n\nenum BtInPacketType {\n\tBUTTON   = 0x0010,\n\tTRIGGERS = 0x0020,\n\tSTICK    = 0x0080,\n\tLPAD     = 0x0100,\n\tRPAD     = 0x0200,\n\tGYRO     = 0x1800,\n\tPING     = 0x5000,\n};\n\n#define LONG_PACKET 0x80\n#define SINGLE_PACKET_PAYLOAD_PREFIX 0x40\n#define PACKET_SIZE 20\n\nenum SCButtons {\n\t// This may be moved later to something shared, only this c file needs it right now\n\tSCB_RPADTOUCH\t= 0b10000000000000000000000000000,\n\tSCB_LPADTOUCH\t= 0b01000000000000000000000000000,\n\tSCB_RPAD\t\t= 0b00100000000000000000000000000,\n\tSCB_LPAD\t\t= 0b00010000000000000000000000000, // # Same for stick but without LPadTouch\n\tSCB_STICKPRESS\t= 0b00000000000000000000000000001, // # generated internally, not sent by controller\n\tSCB_RGRIP\t \t= 0b00001000000000000000000000000,\n\tSCB_LGRIP\t \t= 0b00000100000000000000000000000,\n\tSCB_START\t \t= 0b00000010000000000000000000000,\n\tSCB_C\t\t \t= 0b00000001000000000000000000000,\n\tSCB_BACK\t\t= 0b00000000100000000000000000000,\n\tSCB_A\t\t\t= 0b00000000000001000000000000000,\n\tSCB_X\t\t\t= 0b00000000000000100000000000000,\n\tSCB_B\t\t\t= 0b00000000000000010000000000000,\n\tSCB_Y\t\t\t= 0b00000000000000001000000000000,\n\tSCB_LB\t\t\t= 0b00000000000000000100000000000,\n\tSCB_RB\t\t\t= 0b00000000000000000010000000000,\n\tSCB_LT\t\t\t= 0b00000000000000000001000000000,\n\tSCB_RT\t\t\t= 0b00000000000000000000100000000,\n\tSCB_CPADTOUCH\t= 0b00000000000000000000000000100, // # Available on DS4 pad\n\tSCB_CPADPRESS\t= 0b00000000000000000000000000010, // # Available on DS4 pad\n};\n\nstruct SCByBtControllerInput {\n\tuint16_t type;\n\tuint32_t buttons;\n\tuint8_t ltrig;\n\tuint8_t rtrig;\n\tint32_t stick_x;\n\tint32_t stick_y;\n\tint32_t lpad_x;\n\tint32_t lpad_y;\n\tint32_t rpad_x;\n\tint32_t rpad_y;\n\tint32_t accel_x;\n\tint32_t accel_y;\n\tint32_t accel_z;\n\tint32_t gpitch;\n\tint32_t groll;\n\tint32_t gyaw;\n\tint32_t q1;\n\tint32_t q2;\n\tint32_t q3;\n\tint32_t q4;\n};\n\nstruct SCByBtC {\n\tint fileno;\n\tchar buffer[256];\n\tuint8_t long_packet;\n\tstruct SCByBtControllerInput state;\n\tstruct SCByBtControllerInput old_state;\n};\n\ntypedef struct SCByBtC* SCByBtCPtr;\n\n#define BT_BUTTONS_BITS 23\n\nstatic uint32_t BT_BUTTONS[] = {\n\t// Bit to SCButton\n\tSCB_RT,\t\t\t\t\t// 00\n\tSCB_LT,\t\t\t\t\t// 01\n\tSCB_RB,\t\t\t\t\t// 02\n\tSCB_LB,\t\t\t\t\t// 03\n\tSCB_Y,\t\t\t\t\t// 04\n\tSCB_B,\t\t\t\t\t// 05\n\tSCB_X,\t\t\t\t\t// 06\n\tSCB_A,\t\t\t\t\t// 07\n\t0, \t\t\t\t\t\t// 08 - dpad, ignored\n\t0, \t\t\t\t\t\t// 09 - dpad, ignored\n\t0, \t\t\t\t\t\t// 10 - dpad, ignored\n\t0, \t\t\t\t\t\t// 11 - dpad, ignored\n\tSCB_BACK,\t\t\t\t// 12\n\tSCB_C,\t\t\t\t\t// 13\n\tSCB_START,\t\t\t\t// 14\n\tSCB_LGRIP,\t\t\t\t// 15\n\tSCB_RGRIP,\t\t\t\t// 16\n\tSCB_LPAD,\t\t\t\t// 17\n\tSCB_RPAD,\t\t\t\t// 18\n\tSCB_LPADTOUCH,\t\t\t// 19\n\tSCB_RPADTOUCH,\t\t\t// 20\n\t0,\t\t\t\t\t\t// 21 - nothing\n\tSCB_STICKPRESS,\t\t\t// 22\n};\n\nstatic inline void debug_packet(char* buffer, size_t size) {\n\tsize_t i;\n\tfor(i=0; i<size; i++)\n\t\tprintf(\"%02x \", buffer[i] & 0xff);\n\tprintf(\"\\n\");\n}\n\nstatic char tmp_buffer[256];\n\n/** Returns 1 if state has changed, 2 on read error */\nint read_input(SCByBtCPtr ptr) {\n\tif (read(ptr->fileno, tmp_buffer, PACKET_SIZE) < PACKET_SIZE)\n\t{\n\t\treturn 2;\n\t}\n\n\tbool inLongPacket = false;\n\tbool endPayload = false;\n\tbool resetBufferData = false;\n\t{\n\t\tchar checkPayloadByte = tmp_buffer[1];\n\t\tint currentPacketNum = (int)(checkPayloadByte & 0x0F);\n\t\t//printf(\"%i\\n\", currentPacketNum);\n\t\t//bool processPayload = false;\n\n\t\tinLongPacket = ptr->long_packet;\n\t\tif (ptr->long_packet)\n\t\t{\n\t\t\t// Will grab 18 bytes from each partial input. Start idx\n\t\t\t// offset from 20.\n\t\t\tint offset = ((PACKET_SIZE - 2) * currentPacketNum) + 2;\n\t\t\t// Skip copying first two bytes in partial input\n\t\t\t// (report ID and packet payload byte)\n\t\t\tmemcpy(ptr->buffer + offset, tmp_buffer + 2, PACKET_SIZE - 2);\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmemcpy(ptr->buffer, tmp_buffer, PACKET_SIZE);\n\t\t}\n\n\t\tendPayload = (checkPayloadByte & SINGLE_PACKET_PAYLOAD_PREFIX) == SINGLE_PACKET_PAYLOAD_PREFIX;\n\t\tptr->long_packet = !endPayload;\n\n\t\tif (!endPayload)\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t\telse if (inLongPacket && endPayload)\n\t\t{\n\t\t\tresetBufferData = true;\n\t\t}\n\n\t\t//if (endPayload)\n\t\t//{\n\t\t//\tdebug_packet(ptr->buffer, PACKET_SIZE * (currentPacketNum+1));\n\t\t//}\n\t}\n\n\tstruct SCByBtControllerInput* state = &(ptr->state);\n\tstruct SCByBtControllerInput* old_state = &(ptr->old_state);\n\t\n\tint rv = 0;\n\tint bit;\n\tuint16_t type = *((uint16_t*)(ptr->buffer + 2));\n\tchar* data = &ptr->buffer[4];\n\tif ((type & PING) == PING) {\n\t\t// PING packet does nothing\n\n\t\tif (resetBufferData)\n\t\t{\n\t\t\tmemset(ptr->buffer, 0, 256);\n\t\t}\n\n\t\treturn 0;\n\t}\n\tif ((type & BUTTON) == BUTTON) {\n\t\tuint32_t bt_buttons = *((uint32_t*)data);\n\t\tuint32_t sc_buttons = 0;\n\t\tfor (bit=0; bit<BT_BUTTONS_BITS; bit++) {\n\t\t\tif ((bt_buttons & 1) != 0)\n\t\t\t\tsc_buttons |= BT_BUTTONS[bit];\n\t\t\tbt_buttons >>= 1;\n\t\t}\n\t\t\n\t\tif (rv == 0) { *old_state = *state; state->type = type; rv = 1; }\n\t\tstate->buttons = sc_buttons;\n\t\tdata += 3;\n\t}\n\tif ((type & TRIGGERS) == TRIGGERS) {\n\t\tif (rv == 0) { *old_state = *state; state->type = type; rv = 1; }\n\t\tstate->ltrig = *(((uint8_t*)data) + 0);\n\t\tstate->rtrig = *(((uint8_t*)data) + 1);\n\t\tdata += 2;\n\t}\t\n\tif ((type & STICK) == STICK) {\n\t\tif (rv == 0) { *old_state = *state; state->type = type; rv = 1; }\n\t\tstate->stick_x = *(((int16_t*)data) + 0);\n\t\tstate->stick_y = *(((int16_t*)data) + 1);\n\t\tdata += 4;\n\t}\n\tif ((type & LPAD) == LPAD) {\n\t\tif (rv == 0) { *old_state = *state; state->type = type; rv = 1; }\n\t\tstate->lpad_x = *(((int16_t*)data) + 0);\n\t\tstate->lpad_y = *(((int16_t*)data) + 1);\n\t\tdata += 4;\n\t}\n\tif ((type & RPAD) == RPAD) {\n\t\tif (rv == 0) { *old_state = *state; state->type = type; rv = 1; }\n\t\tstate->rpad_x = *(((int16_t*)data) + 0);\n\t\tstate->rpad_y = *(((int16_t*)data) + 1);\n\t\tdata += 4;\n\t}\n\tif ((type & GYRO) == GYRO) {\n\t\tif (rv == 0) { *old_state = *state; state->type = type; rv = 1; }\n\t\tstate->accel_x = *(((int16_t*)data) + 0);\n\t\tstate->accel_y = *(((int16_t*)data) + 1);\n\t\tstate->accel_z = *(((int16_t*)data) + 2);\n\t\tstate->gpitch = *(((int16_t*)data) + 3);\n\t\tstate->groll = *(((int16_t*)data) + 4);\n\t\tstate->gyaw = *(((int16_t*)data) + 5);\n\t\tstate->q1 = *(((int16_t*)data) + 6);\n\t\tstate->q2 = *(((int16_t*)data) + 7);\n\t\tstate->q3 = *(((int16_t*)data) + 8);\n\t\tstate->q4 = *(((int16_t*)data) + 9);\n\t\tdata += 20;\n\t\t/*state->gpitch = *(((int16_t*)data) + 0);\n\t\tstate->groll = *(((int16_t*)data) + 1);\n\t\tstate->gyaw = *(((int16_t*)data) + 2);\n\t\tstate->q1 = *(((int16_t*)data) + 3);\n\t\tstate->q2 = *(((int16_t*)data) + 4);\n\t\tstate->q3 = *(((int16_t*)data) + 5);\n\t\tstate->q4 = *(((int16_t*)data) + 6);\n\t\tdata += 14;\n\t\t*/\n\t}\n\n\tif (resetBufferData)\n\t{\n\t\tmemset(ptr->buffer, 0, 256);\n\t}\n\t\n\treturn rv;\n}\n\nconst int sc_by_bt_module_version(void) {\n\treturn SC_BY_BT_MODULE_VERSION;\n}\n"
  },
  {
    "path": "scc/drivers/sc_by_bt.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Steam Controller Driver\n\nDriver for Steam Controller over bluetooth (evdev)\n\nShares a lot of classes with sc_dongle.py\n\"\"\"\n\nfrom scc.lib.hidraw import HIDRaw\nfrom scc.constants import ControllerFlags, STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.tools import find_library\nfrom .sc_dongle import SCPacketType, SCPacketLength, SCConfigType\nfrom .sc_dongle import SCController\nfrom math import sin, cos\nimport os, sys, struct, ctypes, logging\n\nVENDOR_ID = 0x28de\nPRODUCT_ID = 0x1106\nPACKET_SIZE = 20\n\nlog = logging.getLogger(\"SCBT\")\n\n\nclass SCByBtControllerInput(ctypes.Structure):\n\t_fields_ = [\n\t\t('type', ctypes.c_uint16),\n\t\t('buttons', ctypes.c_uint32),\n\t\t('ltrig', ctypes.c_uint8),\n\t\t('rtrig', ctypes.c_uint8),\n\t\t('stick_x', ctypes.c_int32),\n\t\t('stick_y', ctypes.c_int32),\n\t\t('lpad_x', ctypes.c_int32),\n\t\t('lpad_y', ctypes.c_int32),\n\t\t('rpad_x', ctypes.c_int32),\n\t\t('rpad_y', ctypes.c_int32),\n\t\t('accel_x', ctypes.c_int32),\n\t\t('accel_y', ctypes.c_int32),\n\t\t('accel_z', ctypes.c_int32),\n\t\t('gpitch', ctypes.c_int32),\n\t\t('groll', ctypes.c_int32),\n\t\t('gyaw', ctypes.c_int32),\n\t\t('q1', ctypes.c_int32),\n\t\t('q2', ctypes.c_int32),\n\t\t('q3', ctypes.c_int32),\n\t\t('q4', ctypes.c_int32),\n\t]\n\n\nclass SCByBtC(ctypes.Structure):\n\t_fields_ = [\n\t\t('fileno', ctypes.c_int),\n\t\t('buffer', ctypes.c_char * 256),\n\t\t('long_packet', ctypes.c_uint8),\n\t\t('state', SCByBtControllerInput),\n\t\t('old_state', SCByBtControllerInput),\n\t]\n\n\nSCByBtCPtr = ctypes.POINTER(SCByBtC)\n\n\nclass Driver:\n\t\"\"\" Similar to USB driver, but with hidraw used for backend \"\"\"\n\t# TODO: It should be possible to merge this, usb and hiddrv\n\t\n\tdef __init__(self, daemon, config):\n\t\tself.config = config\n\t\tself.daemon = daemon\n\t\tself.reconnecting = set()\n\t\tself._lib = find_library('libsc_by_bt')\n\t\tread_input = self._lib.read_input\n\t\tread_input.restype = ctypes.c_int\n\t\tread_input.argtypes = [ SCByBtCPtr ]\n\t\tdaemon.get_device_monitor().add_callback(\"bluetooth\",\n\t\t\t\tVENDOR_ID, PRODUCT_ID, self.new_device_callback, None)\n\t\n\t\n\tdef retry(self, syspath):\n\t\t\"\"\"\n\t\tSchedules reconnecting controller after read operation fails.\n\t\t\"\"\"\n\t\tdef reconnect(*a):\n\t\t\tif syspath in self.reconnecting:\n\t\t\t\tself.reconnecting.remove(syspath)\n\t\t\t\tlog.debug(\"Reconnecting to controller...\")\n\t\t\t\tself.new_device_callback(syspath)\n\t\t\n\t\tself.reconnecting.add(syspath)\n\t\tself.daemon.get_device_monitor().add_remove_callback(\n\t\t\tsyspath, self._retry_cancel)\n\t\tself.daemon.get_scheduler().schedule(1.0, reconnect)\n\t\n\t\n\tdef _retry_cancel(self, syspath, vendor, product):\n\t\t\"\"\"\n\t\tCancels reconnection scheduled by 'retry'. Called when device monitor\n\t\treports controller (as in BT device) being disconencted.\n\t\t\"\"\"\n\t\tif syspath in self.reconnecting:\n\t\t\tself.reconnecting.remove(syspath)\n\t\n\t\n\tdef new_device_callback(self, syspath, *whatever):\n\t\thidrawname = self.daemon.get_device_monitor().get_hidraw(syspath)\n\t\tif hidrawname is None:\n\t\t\treturn None\n\t\ttry:\n\t\t\tdev = HIDRaw(open(os.path.join(\"/dev/\", hidrawname), \"w+b\"))\n\t\t\treturn SCByBt(self, syspath, dev)\n\t\texcept Exception as e:\n\t\t\tlog.exception(e)\n\t\t\treturn None\n\n\nclass SCByBt(SCController):\n\tflags = 0 | ControllerFlags.SEPARATE_STICK\n\t\n\tdef __init__(self, driver, syspath, hidrawdev):\n\t\tself._cmsg = []  # controll messages\n\t\tself._transfer_list = []\n\t\tself.driver = driver\n\t\tself.daemon = driver.daemon\n\t\tself.syspath = syspath\n\t\tSCController.__init__(self, self, -1, -1)\n\t\tself._led_level = 30\n\t\tself._device_name = hidrawdev.getName()\n\t\tself._hidrawdev = hidrawdev\n\t\tself._fileno = hidrawdev._device.fileno()\n\t\tself._c_data = SCByBtC(fileno=self._fileno, long_packet=0)\n\t\tself._c_data_ptr = ctypes.byref(self._c_data)\n\t\tself._old_state = self._c_data.old_state\n\t\tself._state = self._c_data.state\n\t\tself._poller = self.daemon.get_poller()\n\t\tif self._poller:\n\t\t\tself._poller.register(self._fileno, self._poller.POLLIN, self._input)\n\t\tself.daemon.get_device_monitor().add_remove_callback(\n\t\t\tsyspath, self.close)\n\t\tself.read_serial()\n\t\tself.configure()\n\t\tself.flush()\n\t\tself.daemon.add_controller(self)\n\t\n\t\n\tdef get_device_name(self):\n\t\t# Method needed by evdev driver\n\t\t# return self._device_name\n\t\treturn \"Steam Controller over Bluetooth\"\n\t\n\t\n\tdef get_type(self):\n\t\treturn \"scbt\"\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<SCByBt %s>\" % (self.get_id(),)\n\t\n\t\n\tdef configure(self, idle_timeout=None, enable_gyros=None, led_level=None):\n\t\t\"\"\"\n\t\tSets and, if possible, sends configuration to controller.\n\t\tSee SCController.configure method in sc_dongle.py;\n\t\t\n\t\tThis method is almost the same, with different set of hardcoded constants.\n\t\t\"\"\"\n\t\t# ------\n\t\t\"\"\"\n\t\tpacket format:\n\t\t - uint8_t type - SCPacketType.CONFIGURE\n\t\t - uint8_t size - SCPacketLength.CONFIGURE_BT or SCPacketLength.LED\n\t\t - uint8_t config_type - SCConfigType.CONFIGURE_BT or SCConfigType.LED\n\t\t - (variable) data\n\t\t\n\t\tFormat for data when configuring controller:\n\t\t - 12B\t\tunknown1 - (hex 0000310200080700070700300)\n\t\t - uint8\tenable gyro sensor - 0x14 enables, 0x00 disables\n\t\t - 2b\t\tunknown2 - (0x00, 0x2e)\n\t\t \n\t\tFormat for data when configuring led:\n\t\t - uint8\tled\n\t\t - 60b\t\tunused\n\t\t\"\"\"\n\t\t# idle_timeout is ignored\n\t\tif enable_gyros is not None : self._enable_gyros = enable_gyros\n\t\tif led_level is not None: self._led_level = int(led_level)\n\t\t\n\t\tunknown1 = b'\\x00\\x00\\x31\\x02\\x00\\x08\\x07\\x00\\x07\\x07\\x00\\x30'\n\t\tunknown2 = b'\\x00\\x2e'\n\t\t\n\t\t# Timeout & Gyros\n\t\tself.overwrite_control(self._ccidx, struct.pack('>BBB12sB2s',\n\t\t\tSCPacketType.CONFIGURE,\n\t\t\tSCPacketLength.CONFIGURE_BT,\n\t\t\tSCConfigType.CONFIGURE_BT,\n\t\t\tunknown1,\n\t\t\t# 0x10 (Gyro) | 0x08 (Accel) | 0x04 (Quat)\n\t\t\t0x1C if self._enable_gyros else 0,\n\t\t\tunknown2))\n\t\t\n\t\t# LED\n\t\tself.overwrite_control(self._ccidx, struct.pack('>BBBB',\n\t\t\tSCPacketType.CONFIGURE,\n\t\t\tSCPacketLength.LED,\n\t\t\tSCConfigType.LED,\n\t\t\tself._led_level\n\t\t))\n\t\n\t\n\tdef read_serial(self):\n\t\tself._serial = (self._hidrawdev\n\t\t\t.getPhysicalAddress().replace(b\":\", b\"\"))\n\t\n\t\n\tdef send_control(self, index, data):\n\t\t\"\"\" Schedules writing control to device \"\"\"\n\t\t# For BT controller, index is ignored\n\t\tzeros = b'\\x00' * (PACKET_SIZE - len(data) - 1)\n\t\tself._cmsg.insert(0, b'\\xc0' + data + zeros)\n\t\n\t\n\tdef overwrite_control(self, index, data):\n\t\t\"\"\"\n\t\tSimilar to send_control, but this one checks and overwrites\n\t\talready scheduled controll for same device/index.\n\t\t\"\"\"\n\t\t# For BT controller, index is ignored\n\t\tfor x in self._cmsg:\n\t\t\t# First byte is reserved, following 3 are for PacketType, size and ConfigType\n\t\t\tif x[0:4] == data[0:4]:\n\t\t\t\tself._cmsg.remove(x)\n\t\t\t\tbreak\n\t\tself.send_control(index, data)\n\t\n\t\n\tdef make_request(self, index, callback, data, size=PACKET_SIZE):\n\t\t\"\"\"\n\t\tThere are no requests one can send to BT controller,\n\t\tso this just causes exception.\n\t\t\"\"\"\n\t\traise RuntimeError(\"make_request over BT not implemented\")\n\t\n\t\n\tdef flush(self):\n\t\t\"\"\" Flushes all prepared control messages to the device \"\"\"\n\t\twhile len(self._cmsg):\n\t\t\tmsg = self._cmsg.pop()\n\t\t\t# Feature report data must be sent with report ID 3\n\t\t\t# or Input/output error will occur with later BlueZ versions (5.64)\n\t\t\t# Does not affect older BlueZ versions\n\t\t\tself._hidrawdev.sendFeatureReport(msg, 3)\n\t\n\t\n\tdef input(self, idata):\n\t\traise RuntimeError(\"This shouldn't be called, ever\")\n\n\tdef turnoff(self):\n\t\tsuper().turnoff()\n\t\t# Need to call flush to make sure packet\n\t\t# is sent to controller\n\t\tself.flush()\n\t\n\t\n\tdef close(self, *a):\n\t\tif self._poller:\n\t\t\tself._poller.unregister(self._fileno)\n\t\tself.daemon.remove_controller(self)\n\t\tself._hidrawdev._device.close()\n\t\n\t\n\tdef disconnected(self):\n\t\tpass\n\t\n\t\n\tdef _input(self, *a):\n\t\tr = self.driver._lib.read_input(self._c_data_ptr)\n\t\t\n\t\tif r == 1:\n\t\t\tif self.mapper is not None:\n\t\t\t\tif self._input_rotation_l and (self._state.type & 0x0100) != 0:\n\t\t\t\t\tlx, ly = self._state.lpad_x, self._state.lpad_y\n\t\t\t\t\ts, c = sin(self._input_rotation_l), cos(self._input_rotation_l)\n\n\t\t\t\t\t# Adjust LX for rotation and clamp\n\t\t\t\t\tvalue = int(lx * c - ly * s)\n\t\t\t\t\tself._state.lpad_x = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\n\t\t\t\t\t# Adjust LY for rotation and clamp\n\t\t\t\t\tvalue = int(lx * s + ly * c)\n\t\t\t\t\tself._state.lpad_y = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\t\t\t\tif self._input_rotation_r and (self._state.type & 0x0200) != 0:\n\t\t\t\t\trx, ry = self._state.rpad_x, self._state.rpad_y\n\t\t\t\t\ts, c = sin(self._input_rotation_r), cos(self._input_rotation_r)\n\n\t\t\t\t\t# Adjust RX for rotation and clamp\n\t\t\t\t\tvalue = int(rx * c - ry * s)\n\t\t\t\t\tself._state.rpad_x = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\n\t\t\t\t\t# Adjust RY for rotation and clamp\n\t\t\t\t\tvalue = int(rx * s + ry * c)\n\t\t\t\t\tself._state.rpad_y = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\t\t\t\t\n\t\t\t\tself.mapper.input(self, self._old_state, self._state)\n\t\t\tself.flush()\n\t\telif r > 1:\n\t\t\tlog.error(\"Read Failed\")\n\t\t\tself.close()\n\t\t\tself.driver.retry(self.syspath)\n\n\ndef hidraw_test(filename):\n\tclass FakeDaemon(object):\n\n\t\tdef add_error(self, id, error):\n\t\t\tlog.error(error)\n\n\t\tdef remove_error(*a): pass\n\n\t\tdef add_mainloop(*a): pass\n\n\t\tdef get_active_ids(*a): return []\n\n\t\tdef get_poller(self):\n\t\t\treturn None\n\t\n\tclass TestSC(SCByBt):\n\t\tdef input(self, tup):\n\t\t\tprint(tup)\n\t\n\tdev = HIDRaw(open(filename, \"w+b\"))\n\tdriver = Driver(FakeDaemon(), {})\n\tc = TestSC(driver, None, dev)\n\tc.configure()\n\tc.flush()\n\twhile True:\n\t\tc._input()\n\t\tprint({ x[0]: getattr(c._state, x[0]) for x in c._state._fields_ })\n\n_drv = None\n\n\ndef init(daemon, config):\n\t\"\"\" Registers hotplug callback for controller dongle \"\"\"\n\n\t# if not (HAVE_EVDEV and config[\"drivers\"].get(\"evdevdrv\")):\n\t# \tlog.warning(\"Evdev driver is not enabled, Steam Controller over Bluetooth support cannot be enabled.\")\n\t# \treturn False\n\t_drv = Driver(daemon, config)\n\treturn True\n\n\nif __name__ == \"__main__\":\n\t\"\"\" Called when executed as script \"\"\"\n\tfrom scc.tools import init_logging, set_logging_level\n\tinit_logging()\n\tset_logging_level(True, True)\n\tsys.exit(hidraw_test(sys.argv[1]))\n"
  },
  {
    "path": "scc/drivers/sc_by_cable.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Steam Controller Driver\n\nCalled and used when single Steam Controller is connected directly by USB cable.\n\nShares a lot of classes with sc_dongle.py\n\"\"\"\n\nfrom scc.lib.usb1 import USBError\nfrom scc.drivers.usb import USBDevice, register_hotplug_device\nfrom .sc_dongle import ControllerInput, TUP_FORMAT\nfrom .sc_dongle import SCStatus, SCController\nimport struct, logging\n\nVENDOR_ID = 0x28de\nPRODUCT_ID = 0x1102\nENDPOINT = 3\nCONTROLIDX = 2\nTIMER_INTERVAL = 0.01\n\nlog = logging.getLogger(\"SCCable\")\n\ndef init(daemon, config):\n\t\"\"\" Registers hotplug callback for controller dongle \"\"\"\n\tdef cb(device, handle):\n\t\treturn SCByCable(device, handle, daemon)\n\t\n\tregister_hotplug_device(cb, VENDOR_ID, PRODUCT_ID)\n\treturn True\n\n\nclass SCByCable(USBDevice, SCController):\n\tFORMAT1 = b'>BBBBB13sB2s'\n\t\n\tdef __init__(self, device, handle, daemon):\n\t\tself.daemon = daemon\n\t\tUSBDevice.__init__(self, device, handle)\n\t\tSCController.__init__(self, self, CONTROLIDX, ENDPOINT)\n\t\tself._ready = False\n\t\tself._last_tup = None\n\t\tdaemon.add_mainloop(self._timer)\n\t\t\n\t\tself.claim_by(klass=3, subclass=0, protocol=0)\n\t\tself.read_serial()\n\t\n\t\n\tdef generate_serial(self):\n\t\tself._serial = \"%s:%s\" % (self.device.getBusNumber(), self.device.getPortNumber())\n\t\n\t\n\tdef disconnected(self):\n\t\t# Overrided to skip returning serial# to pool.\n\t\tpass\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<SCByCable %s>\" % (self.get_id(),)\n\t\n\t\n\tdef on_serial_got(self):\t\n\t\tlog.debug(\"Got wired SC with serial %s\", self._serial)\n\t\tself._id = \"sc%s\" % (self._serial,)\n\t\tself.set_input_interrupt(ENDPOINT, 64, self._wait_input)\t\n\t\n\t\n\tdef _wait_input(self, endpoint, data):\n\t\ttup = ControllerInput._make(struct.unpack(TUP_FORMAT, data))\n\t\tif not self._ready:\n\t\t\tself.daemon.add_controller(self)\n\t\t\tself.configure()\n\t\t\tself._ready = True\n\t\tif tup.status == SCStatus.INPUT:\n\t\t\tself._last_tup = tup\n\t\n\t\n\tdef _timer(self):\n\t\tm = self.get_mapper()\n\t\tif m:\n\t\t\tif self._last_tup:\n\t\t\t\tself.input(self._last_tup)\n\t\t\t\tself._last_tup = None\n\t\t\telse:\n\t\t\t\tm.generate_events()\n\t\t\t\tm.generate_feedback()\n\t\t\ttry:\n\t\t\t\tself.flush()\n\t\t\texcept USBError as e:\n\t\t\t\tlog.exception(e)\n\t\t\t\tlog.error(\"Error while communicating with device, baling out...\")\n\t\t\t\tself.force_restart()\n\t\n\t\n\tdef close(self):\n\t\tif self._ready:\n\t\t\tself.daemon.remove_controller(self)\n\t\t\tself._ready = False\n\t\tself.daemon.remove_mainloop(self._timer)\n\t\tUSBDevice.close(self)\n\t\n\t\n\tdef turnoff(self):\n\t\tlog.warning(\"Ignoring request to turn off wired controller.\")\n"
  },
  {
    "path": "scc/drivers/sc_dongle.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nSC Controller - Steam Controller Wireless Receiver (aka Dongle) Driver\n\nCalled and used when Dongle is detected on USB bus.\nHandles one or multiple controllers connected to dongle.\n\"\"\"\n\nfrom scc.lib import IntEnum\nfrom scc.drivers.usb import USBDevice, register_hotplug_device\nfrom scc.constants import SCButtons, STICKTILT, STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.controller import Controller\nfrom scc.config import Config\nfrom collections import namedtuple\nfrom math import pi as PI, sin, cos\nimport struct, logging\n\nVENDOR_ID = 0x28de\nPRODUCT_ID = 0x1142\nFIRST_ENDPOINT = 2\nFIRST_CONTROLIDX = 1\nINPUT_FORMAT = [\n\t('b',   'type'),\n\t('x',   'ukn_01'),\n\t('B',   'status'),\n\t('x',   'ukn_02'),\n\t('H',   'seq'),\n\t('x',   'ukn_03'),\n\t('I',   'buttons'),\n\t('B',   'ltrig'),\n\t('B',   'rtrig'),\n\t('x',   'ukn_04'),\n\t('x',   'ukn_05'),\n\t('x',   'ukn_06'),\n\t('h',   'lpad_x'),\n\t('h',   'lpad_y'),\n\t('h',   'rpad_x'),\n\t('h',   'rpad_y'),\n\t('4x',  'ukn_06'),\n\t('h',   'accel_x'),\n\t('h',   'accel_y'),\n\t('h',   'accel_z'),\n\t('h',   'gpitch'),\n\t('h',   'groll'),\n\t('h',   'gyaw'),\n\t('h',   'q1'),\n\t('h',   'q2'),\n\t('h',   'q3'),\n\t('h',   'q4'),\n\t('16x', 'ukn_07')]\nFORMATS, NAMES = zip(*INPUT_FORMAT)\nTUP_FORMAT = '<' + ''.join(FORMATS)\nControllerInput = namedtuple('ControllerInput', ' '.join([ x for x in NAMES if not x.startswith('ukn_') ]))\nSCI_NULL = ControllerInput._make(struct.unpack('<' + ''.join(FORMATS), b'\\x00' * 64))\nSTICKPRESS = 0b1000000000000000000000000000000\n\n\nlog = logging.getLogger(\"SCDongle\")\n\ndef init(daemon, config):\n\t\"\"\" Registers hotplug callback for controller dongle \"\"\"\n\tdef cb(device, handle):\n\t\treturn Dongle(device, handle, daemon)\n\t\n\tregister_hotplug_device(cb, VENDOR_ID, PRODUCT_ID)\n\treturn True\n\n\nclass Dongle(USBDevice):\n\tMAX_ENDPOINTS = 4\n\t_available_serials = set()\t\t# used only is ignore_serials option is enabled\n\t\n\tdef __init__(self, device, handle, daemon):\n\t\tself.daemon = daemon\n\t\tUSBDevice.__init__(self, device, handle)\n\t\t\n\t\tself.claim_by(klass=3, subclass=0, protocol=0)\n\t\tself._controllers = {}\n\t\tself._no_serial = []\n\t\tfor i in range(0, Dongle.MAX_ENDPOINTS):\n\t\t\t# Steam dongle apparently can do only 4 controllers at once\n\t\t\tself.set_input_interrupt(FIRST_ENDPOINT + i, 64, self._on_input)\n\t\n\t\n\tdef close(self):\n\t\t# Called when dongle is removed\n\t\tfor c in self._controllers.values():\n\t\t\tself.daemon.remove_controller(c)\n\t\tself._controllers = {}\n\t\tUSBDevice.close(self)\n\t\n\t\n\tdef _add_controller(self, endpoint):\n\t\t\"\"\"\n\t\tCalled when new controller is detected either by HOTPLUG message or\n\t\tby recieving first input event.\n\t\t\"\"\"\n\t\tccidx = FIRST_CONTROLIDX + endpoint - FIRST_ENDPOINT\n\t\tc = SCController(self, ccidx, endpoint)\n\t\tc.configure()\n\t\tc.read_serial()\n\t\tself._controllers[endpoint] = c\n\t\n\t\n\tdef _on_input(self, endpoint, data):\n\t\ttup = ControllerInput._make(struct.unpack(TUP_FORMAT, data))\n\t\tif tup.status == SCStatus.HOTPLUG:\n\t\t\t# Most of INPUT_FORMAT doesn't apply here\n\t\t\tif ord(str(data[4])) == 2:\n\t\t\t\t# Controller connected\n\t\t\t\tif endpoint not in self._controllers:\n\t\t\t\t\tself._add_controller(endpoint)\n\t\t\telse:\n\t\t\t\t# Controller disconnected\n\t\t\t\tif endpoint in self._controllers:\n\t\t\t\t\tself.daemon.remove_controller(self._controllers[endpoint])\n\t\t\t\t\tself._controllers[endpoint].disconnected()\n\t\t\t\t\tdel self._controllers[endpoint]\n\t\telif tup.status == SCStatus.INPUT:\n\t\t\tif endpoint not in self._controllers:\n\t\t\t\tself._add_controller(endpoint)\n\t\t\telif len(self._no_serial):\n\t\t\t\tfor x in self._no_serial:\n\t\t\t\t\tx.read_serial()\n\t\t\t\tself._no_serial = []\n\t\t\telse:\n\t\t\t\tself._controllers[endpoint].input(tup)\n\n\nclass SCStatus(IntEnum):\n\tIDLE = 0x04\n\tINPUT = 0x01\n\tHOTPLUG = 0x03\n\n\nclass SCPacketType(IntEnum):\n\tOFF = 0x9f\n\tAUDIO = 0xb6\n\tCLEAR_MAPPINGS = 0x81\n\tCONFIGURE = 0x87\n\tLED = 0x87\n\tCALIBRATE_JOYSTICK = 0xbf\n\tCALIBRATE_TRACKPAD = 0xa7\n\tSET_AUDIO_INDICES = 0xc1\n\tLIZARD_MODE = 0x8e\n\tFEEDBACK = 0x8f\n\tRESET = 0x95\n\tGET_SERIAL = 0xAE\n\n\nclass SCPacketLength(IntEnum):\n\tLED = 0x03\n\tOFF = 0x04\n\tFEEDBACK = 0x07\n\tCONFIGURE = 0x15\n\tCONFIGURE_BT = 0x0f\n\tGET_SERIAL = 0x15\n\n\nclass SCConfigType(IntEnum):\n\tLED = 0x2d\n\tCONFIGURE = 0x32\n\tCONFIGURE_BT = 0x18\n\n\nclass SCController(Controller):\n\tdef __init__(self, driver, ccidx, endpoint):\n\t\tController.__init__(self)\n\t\tself._driver = driver\n\t\tself._endpoint = endpoint\n\t\tself._idle_timeout = 600\n\t\tself._enable_gyros = False\n\t\tself._input_rotation_l = 0\n\t\tself._input_rotation_r = 0\n\t\tself._led_level = 10\n\t\t# TODO: Is serial really used anywhere?\n\t\tself._serial = \"0000000000\"\n\t\tself._id = self._generate_id() if driver else \"-\"\n\t\tself._old_state = SCI_NULL\n\t\tself._ccidx = ccidx\n\t\n\t\n\tdef get_type(self):\n\t\treturn \"sc\"\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<SCWireless %s>\" % (self.get_id(),)\n\t\n\t\n\tdef input(self, idata):\n\t\told_state, self._old_state = self._old_state, idata\n\t\tif self.mapper:\n\t\t\t#if idata.buttons & SCButtons.LPAD:\n\t\t\t#\t# STICKPRESS button may signalize pressing stick instead\n\t\t\t#\tif (idata.buttons & STICKPRESS) and not (idata.buttons & STICKTILT):\n\t\t\t#\t\tidata = ControllerInput.replace(buttons=idata.buttons & ~SCButtons.LPAD)\n\t\t\t\n\t\t\tif self._input_rotation_l or self._input_rotation_r:\n\t\t\t\tlx, ly = idata.lpad_x, idata.lpad_y\n\t\t\t\trx, ry = idata.rpad_x, idata.rpad_y\n\n\t\t\t\tif self._input_rotation_l and idata.buttons & SCButtons.LPADTOUCH:\n\t\t\t\t\ts, c = sin(self._input_rotation_l), cos(self._input_rotation_l)\n\t\t\t\t\t# Adjust LX for rotation and clamp\n\t\t\t\t\tvalue = int(idata.lpad_x * c - idata.lpad_y * s)\n\t\t\t\t\tlx = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\n\t\t\t\t\t# Adjust LY for rotation and clamp\n\t\t\t\t\tvalue = int(idata.lpad_x * s + idata.lpad_y * c)\n\t\t\t\t\tly = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\n\t\t\t\tif self._input_rotation_r and idata.buttons & SCButtons.RPADTOUCH:\n\t\t\t\t\ts, c = sin(self._input_rotation_r), cos(self._input_rotation_r)\n\n\t\t\t\t\t# Adjust RX for rotation and clamp\n\t\t\t\t\tvalue = int(idata.rpad_x * c - idata.rpad_y * s)\n\t\t\t\t\trx = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\n\t\t\t\t\t# Adjust RY for rotation and clamp\n\t\t\t\t\tvalue = int(idata.rpad_x * s + idata.rpad_y * c)\n\t\t\t\t\try = max(STICK_PAD_MIN, min(STICK_PAD_MAX, value))\n\n\t\t\t\t# TODO: This is awfull :(\n\t\t\t\tidata = ControllerInput(\n\t\t\t\t\t\tidata.type, idata.status, idata.seq, idata.buttons,\n\t\t\t\t\t\tidata.ltrig, idata.rtrig,\n\t\t\t\t\t\tlx, ly, rx, ry,\n\t\t\t\t\t\tidata.accel_x, idata.accel_y, idata.accel_z,\n\t\t\t\t\t\tidata.gpitch, idata.groll, idata.gyaw,\n\t\t\t\t\t\tidata.q1, idata.q2, idata.q3, idata.q4\n\t\t\t\t)\n\n\t\t\tself.mapper.input(self, old_state, idata)\n\t\n\t\n\tdef _generate_id(self):\n\t\t\"\"\"\n\t\tID is generated as 'scX' where where 'X' starts as 0 and increases\n\t\tas more controllers are connected.\n\t\t\n\t\tThis is used only when reading serial numbers from device is disabled.\n\t\tsc_by_cable generates ids in scBUS:PORT format.\n\t\t\"\"\"\n\t\tmagic_number = 1\n\t\ttp = self.get_type()\n\t\tid = None\n\t\twhile id is None or id in self._driver.daemon.get_active_ids():\n\t\t\tid = \"%s%s\" % (tp, magic_number,)\n\t\t\tmagic_number += 1\n\t\treturn id\n\t\n\t\n\tdef read_serial(self):\n\t\t\"\"\" Requests and reads serial number from controller \"\"\"\n\t\tif Config()[\"ignore_serials\"]:\n\t\t\t# Special exception for cases when controller drops instead of\n\t\t\t# sending serial number. See issue #103\n\t\t\tself.generate_serial()\n\t\t\tself.on_serial_got()\n\t\t\treturn\n\t\t\n\t\tdef cb(rawserial):\n\t\t\tsize, serial = struct.unpack(\">xBx12s49x\", rawserial)\n\t\t\tif size > 1:\n\t\t\t\tserial = serial.strip(b\" \\x00\").decode('ASCII')\n\t\t\t\tself._serial = serial\n\t\t\t\tself.on_serial_got()\n\t\t\telse:\n\t\t\t\tself._driver._no_serial.append(self)\n\t\t\n\t\tself._driver.make_request(\n\t\t\tself._ccidx, cb,\n\t\t\tstruct.pack('>BBB61x',\n\t\t\t\tSCPacketType.GET_SERIAL, SCPacketLength.GET_SERIAL, 0x01))\n\t\n\t\n\tdef generate_serial(self):\n\t\t\"\"\" Called only if ignore_serials is enabled \"\"\"\n\t\tif len(self._driver._available_serials) > 0:\n\t\t\tself._serial = self._driver._available_serials.pop()\n\t\telse:\n\t\t\tself._serial = self.get_id()\n\t\tlog.debug(\"Not requesting serial number for SC %s\", self._serial)\n\t\n\t\n\tdef on_serial_got(self):\n\t\ttry:\n\t\t\tlog.debug(\"Got wireless SC with serial %s\", self._serial)\n\t\texcept UnicodeDecodeError:\n\t\t\tlog.debug(\"Failed to decode wireless SC serial\")\n\t\t\tself._serial = self._driver._available_serials.pop()\n\t\tself._id = str(self._serial)\n\t\tself._driver.daemon.add_controller(self)\n\t\n\t\n\tdef apply_config(self, config):\n\t\tself.configure(idle_timeout=int(config['idle_timeout']),\n\t\t\t\tled_level=float(config['led_level']))\n\t\tself._input_rotation_l = float(config['input_rotation_l']) * PI / -180.0\n\t\tself._input_rotation_r = float(config['input_rotation_r']) * PI / -180.0\n\t\n\t\n\tdef disconnected(self):\n\t\t# If ignore_serials config option is enabled, fake serial used by this\n\t\t# controller is stored away and reused when next controller is connected\n\t\tif Config()[\"ignore_serials\"]:\n\t\t\tself._driver._available_serials.add(self._serial)\n\t\n\tFORMAT1 = b'>BBBBB13sB2s43x'\n\t# Has to be overriden in sc_by_cable\n\tFORMAT2 = b'>BBBB59x'\n\t\n\tdef configure(self, idle_timeout=None, enable_gyros=None, led_level=None):\n\t\t\"\"\"\n\t\tSets and, if possible, sends configuration to controller.\n\t\tOnly value that is provided is changed.\n\t\t'idle_timeout' is in seconds.\n\t\t'led_level' is precent (0-100)\n\t\t\"\"\"\n\t\t# ------\n\t\t\"\"\"\n\t\tpacket format:\n\t\t - uint8_t type - SCPacketType.CONFIGURE\n\t\t - uint8_t size - SCPacketLength.CONFIGURE or SCPacketLength.LED\n\t\t - uint8_t config_type - SCConfigType.CONFIGURE or SCConfigType.LED\n\t\t - 61B data\n\t\t\n\t\tFormat for data when configuring controller:\n\t\t - uint16\ttimeout\n\t\t - 13B\t\tunknown1 - (0x18, 0x00, 0x00, 0x31, 0x02, 0x00, 0x08, 0x07, 0x00, 0x07, 0x07, 0x00, 0x30)\n\t\t - uint8\tenable gyro sensor - 0x14 enables, 0x00 disables\n\t\t - 2B\t\tunknown2 - (0x00, 0x2e)\n\t\t - 43B\t\tunused\n\t\t \n\t\tFormat for data when configuring led:\n\t\t - uint8\tled\n\t\t - 60B\t\tunused\n\t\t\"\"\"\n\t\t\n\t\tif idle_timeout is not None : self._idle_timeout = idle_timeout\n\t\tif enable_gyros is not None : self._enable_gyros = enable_gyros\n\t\tif led_level is not None: self._led_level = led_level\n\t\t\n\t\tunknown1 = b'\\x18\\x00\\x00\\x31\\x02\\x00\\x08\\x07\\x00\\x07\\x07\\x00\\x30'\n\t\tunknown2 = b'\\x00\\x2e'\n\t\ttimeout1 = self._idle_timeout & 0x00FF\n\t\ttimeout2 = (self._idle_timeout & 0xFF00) >> 8\n\t\t\n\t\t# Timeout & Gyros\n\t\tself._driver.overwrite_control(self._ccidx, struct.pack(self.FORMAT1,\n\t\t\tSCPacketType.CONFIGURE,\n\t\t\tSCPacketLength.CONFIGURE,\n\t\t\tSCConfigType.CONFIGURE,\n\t\t\ttimeout1, timeout2,\n\t\t\tunknown1,\n\t\t\t# 0x10 (Gyro) | 0x08 (Accel) | 0x04 (Quat)\n\t\t\t0x1C if self._enable_gyros else 0,\n\t\t\tunknown2))\n\t\t\n\t\t# LED\n\t\tself._driver.overwrite_control(self._ccidx, struct.pack(self.FORMAT2,\n\t\t\tSCPacketType.CONFIGURE,\n\t\t\tSCPacketLength.LED,\n\t\t\tSCConfigType.LED,\n\t\t\tint(self._led_level)\n\t\t))\n\t\n\t\n\tdef set_led_level(self, level):\n\t\tlevel = min(100, int(level)) & 0xFF\n\t\tif self._led_level != level:\n\t\t\tself._led_level = level\n\t\t\tself._driver.overwrite_control(self._ccidx, struct.pack('>BBBB59x',\n\t\t\t\tSCPacketType.CONFIGURE,\n\t\t\t\t0x03,\n\t\t\t\tSCConfigType.LED,\n\t\t\t\tself._led_level\n\t\t\t))\n\t\n\t\n\tdef set_gyro_enabled(self, enabled):\t\n\t\tself.configure(enable_gyros = enabled)\n\t\n\t\n\tdef turnoff(self):\n\t\tlog.debug(\"Turning off the controller...\")\n\t\t\n\t\t# Mercilessly stolen from scraw library\n\t\tself._driver.send_control(self._ccidx, struct.pack('<BBBBBB',\n\t\t\t\tSCPacketType.OFF, 0x04, 0x6f, 0x66, 0x66, 0x21))\n\t\n\t\n\tdef get_gyro_enabled(self):\n\t\t\"\"\" Returns True if gyroscope input is currently enabled \"\"\"\n\t\treturn self._enable_gyros\n\t\n\t\n\tdef feedback(self, data):\n\t\tself._feedback(*data.data)\n\t\n\t\n\tdef _feedback(self, position, amplitude=128, period=0, count=1):\n\t\t\"\"\"\n\t\tAdd haptic feedback to be send on next usb tick\n\t\t\n\t\t@param int position\t\thaptic to use 1 for left 0 for right\n\t\t@param int amplitude\tsignal amplitude from 0 to 65535\n\t\t@param int period\t\tsignal period from 0 to 65535\n\t\t@param int count\t\tnumber of period to play\n\t\t\"\"\"\n\t\tif amplitude >= 0:\n\t\t\tself._driver.send_control(self._ccidx, struct.pack('<BBBHHH',\n\t\t\t\t\tSCPacketType.FEEDBACK, 0x07, position,\n\t\t\t\t\tamplitude, period, count))\t\n\n"
  },
  {
    "path": "scc/drivers/scc_future.h",
    "content": "#pragma once\n#include <stdint.h>\n\ntypedef struct ControllerInput ControllerInput;\n\ntypedef uint16_t Axis;\ntypedef uint16_t Keycode;\n\ntypedef uint8_t TriggerValue;\ntypedef int16_t AxisValue;\ntypedef int16_t GyroValue;\n\ntypedef enum SCButton {\n\tB_RPADTOUCH\t\t\t= 0b000010000000000000000000000000000,\n\tB_LPADTOUCH\t\t\t= 0b000001000000000000000000000000000,\n\tB_RPADPRESS\t\t\t= 0b000000100000000000000000000000000,\n\tB_LPADPRESS\t\t\t= 0b000000010000000000000000000000000,\n\tB_RGRIP\t\t\t\t= 0b000000001000000000000000000000000,\n\tB_LGRIP\t\t\t\t= 0b000000000100000000000000000000000,\n\tB_START\t\t\t\t= 0b000000000010000000000000000000000,\n\tB_C\t\t\t\t\t= 0b000000000001000000000000000000000,\n\tB_BACK\t\t\t\t= 0b000000000000100000000000000000000,\n\tB_A\t\t\t\t\t= 0b000000000000000001000000000000000,\n\tB_X\t\t\t\t\t= 0b000000000000000000100000000000000,\n\tB_B\t\t\t\t\t= 0b000000000000000000010000000000000,\n\tB_Y\t\t\t\t\t= 0b000000000000000000001000000000000,\n\tB_LB\t\t\t\t= 0b000000000000000000000100000000000,\n\tB_RB\t\t\t\t= 0b000000000000000000000010000000000,\n\tB_LT\t\t\t\t= 0b000000000000000000000001000000000,\n\tB_RT\t\t\t\t= 0b000000000000000000000000100000000,\n\t// CPADTOUCH and CPADPRESS is used only on DS4 pad\n\tB_CPADTOUCH\t\t\t= 0b000000000000000000000000000000100,\n\tB_CPADPRESS\t\t\t= 0b000000000000000000000000000000010,\n\tB_STICKPRESS\t\t= 0b001000000000000000000000000000000,\n\tB_RSTICKPRESS\t\t= 0b010000000000000000000000000000000,\n\t// SteamDeck only buttons\n\tB_DOTS\t\t\t\t= 0b000000000000000000000000000001000,\n\tB_RGRIP2\t\t\t= 0b000000000000000000000000000100000,\n\tB_LGRIP2\t\t\t= 0b000000000000000000000000000010000,\n\t_SCButton_padding = 0xFFFFFFFF\t// uint32_t\n} SCButton;\n\nstruct GyroInput {\n\tGyroValue\t\t\tgpitch;\n\tGyroValue\t\t\tgroll;\n\tGyroValue\t\t\tgyaw;\n\tGyroValue\t\t\tq1;\n\tGyroValue\t\t\tq2;\n\tGyroValue\t\t\tq3;\n\tGyroValue\t\t\tq4;\n};\n\nstruct ControllerInput {\n\tSCButton\t\t\t\tbuttons;\n\tunion {\n\t\tTriggerValue\t\ttriggers[2];\n\t\tstruct {\n\t\t\tTriggerValue\tltrig;\n\t\t\tTriggerValue\trtrig;\n\t\t};\n\t};\n\tunion {\n\t\tAxisValue\t\t\taxes[12];\n\t\tstruct {\n\t\t\tAxisValue\t\tstick_x;\n\t\t\tAxisValue\t\tstick_y;\n\t\t\tAxisValue\t\tlpad_x;\n\t\t\tAxisValue\t\tlpad_y;\n\t\t\tAxisValue\t\trpad_x;\n\t\t\tAxisValue\t\trpad_y;\n\t\t\tAxisValue\t\tcpad_x;\n\t\t\tAxisValue\t\tcpad_y;\n\t\t\tAxisValue\t\tdpad_x;\n\t\t\tAxisValue\t\tdpad_y;\n\t\t\tAxisValue\t\trstick_x;\n\t\t\tAxisValue\t\trstick_y;\n\t\t};\n\t};\n\tstruct GyroInput\t\tgyro;\n};\n\ntypedef struct Mapper Mapper;\nstruct Mapper {\n\tvoid\t\t(*input)(Mapper* m, ControllerInput* i);\n};\n\n\n#define STICK_PAD_MIN\t\t((AxisValue)-0x8000)\n#define STICK_PAD_MAX\t\t((AxisValue) 0x7FFF)\n#define STICK_PAD_MIN_HALF\t((AxisValue)-0x4000)\n#define STICK_PAD_MAX_HALF\t((AxisValue) 0x3FFF)\n\n#define TRIGGER_MIN\t\t\t((TriggerValue)0)\n#define TRIGGER_HALF\t\t((TriggerValue)50)\n// TRIGGER_CLICK is value on which trigger clicks\n#define TRIGGER_CLICK\t\t((TriggerValue)254)\n#define TRIGGER_MAX\t\t\t((TriggerValue)255)\n#define NO_AXIS\t\t\t\t((Axis)(REL_MAX + 1))\n"
  },
  {
    "path": "scc/drivers/steamdeck.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSCC - Steam Deck Driver\n\nBased on sc_by_cable and steamdeck.c\n\nDeck uses slightly different packed format and so common handle_inpu is not used.\n\nOn top of that, deck will automatically enable lizard mode unless requested\nto not do so periodically.\n\"\"\"\n\nfrom scc.lib import IntEnum\nfrom scc.lib.usb1 import USBError\nfrom scc.drivers.usb import USBDevice, register_hotplug_device\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.constants import SCButtons, ControllerFlags\nfrom scc.drivers.sc_dongle import ControllerInput, SCController, SCPacketType\nimport struct, logging, ctypes\n\n\nVENDOR_ID\t\t\t= 0x28de\nPRODUCT_ID\t\t\t= 0x1205\nENDPOINT\t\t\t= 3\nCONTROLIDX\t\t\t= 2\nPACKET_SIZE\t\t\t= 128\nUNLIZARD_INTERVAL\t= 100\n# Basically, sticks on deck tend to return to non-zero position\nSTICK_DEADZONE\t\t= 3000\n\nlog = logging.getLogger(\"deck\")\n\n\nclass DeckInput(ctypes.Structure):\n\t_fields_ = [\n\t\t('type', ctypes.c_uint8),\n\t\t('_a1', ctypes.c_uint8 * 3),\n\t\t('seq', ctypes.c_uint32),\n\t\t('buttons', ctypes.c_uint64),\n\t\t('lpad_x', ctypes.c_int16),\n\t\t('lpad_y', ctypes.c_int16),\n\t\t('rpad_x', ctypes.c_int16),\n\t\t('rpad_y', ctypes.c_int16),\n\t\t\n\t\t('accel_x', ctypes.c_int16),\n\t\t('accel_y', ctypes.c_int16),\n\t\t('accel_z', ctypes.c_int16),\n\t\t('gpitch', ctypes.c_int16),\n\t\t('groll', ctypes.c_int16),\n\t\t('gyaw', ctypes.c_int16),\n\t\t('q1', ctypes.c_uint16),\n\t\t('q2', ctypes.c_uint16),\n\t\t('q3', ctypes.c_uint16),\n\t\t('q4', ctypes.c_uint16),\n\t\t\n\t\t('ltrig', ctypes.c_uint16),\n\t\t('rtrig', ctypes.c_uint16),\n\t\t('stick_x', ctypes.c_int16),\n\t\t('stick_y', ctypes.c_int16),\n\t\t('rstick_x', ctypes.c_int16),\n\t\t('rstick_y', ctypes.c_int16),\n\t\t\n\t\t# Values above are readed directly from deck\n\t\t# Values bellow are converted so mapper can understand them\n\t\t('dpad_x', ctypes.c_int16),\n\t\t('dpad_y', ctypes.c_int16),\n\t]\n\n\nclass DeckButton(IntEnum):\n\tDOTS\t\t\t\t= 0b100000000000000000000000000000000000000000000000000\n\tRSTICKTOUCH\t\t\t= 0b000100000000000000000000000000000000000000000000000\n\tLSTICKTOUCH\t\t\t= 0b000010000000000000000000000000000000000000000000000\n\tRGRIP2\t\t\t\t= 0b000000001000000000000000000000000000000000000000000\n\tLGRIP2\t\t\t\t= 0b000000000100000000000000000000000000000000000000000\n\tRSTICKPRESS\t\t\t= 0b000000000000000000000000100000000000000000000000000\n\tLSTICKPRESS\t\t\t= 0b000000000000000000000000000010000000000000000000000\n\t# bit 21 unused?\n\tRPADTOUCH\t\t\t= 0b000000000000000000000000000000100000000000000000000\n\tLPADTOUCH\t\t\t= 0b000000000000000000000000000000010000000000000000000\n\tRPADPRESS\t\t\t= 0b000000000000000000000000000000001000000000000000000\n\tLPADPRESS\t\t\t= 0b000000000000000000000000000000000100000000000000000\n\tRGRIP\t\t\t\t= 0b000000000000000000000000000000000010000000000000000\n\tLGRIP\t\t\t\t= 0b000000000000000000000000000000000001000000000000000\n\tSTART\t\t\t\t= 0b000000000000000000000000000000000000100000000000000\n\tC\t\t\t\t\t= 0b000000000000000000000000000000000000010000000000000\n\tBACK\t\t\t\t= 0b000000000000000000000000000000000000001000000000000\n\tDPAD_DOWN\t\t\t= 0b000000000000000000000000000000000000000100000000000\n\tDPAD_LEFT\t\t\t= 0b000000000000000000000000000000000000000010000000000\n\tDPAD_RIGHT\t\t\t= 0b000000000000000000000000000000000000000001000000000\n\tDPAD_UP\t\t\t\t= 0b000000000000000000000000000000000000000000100000000\n\tA\t\t\t\t\t= 0b000000000000000000000000000000000000000000010000000\n\tX\t\t\t\t\t= 0b000000000000000000000000000000000000000000001000000\n\tB\t\t\t\t\t= 0b000000000000000000000000000000000000000000000100000\n\tY\t\t\t\t\t= 0b000000000000000000000000000000000000000000000010000\n\tLB\t\t\t\t\t= 0b000000000000000000000000000000000000000000000001000\n\tRB\t\t\t\t\t= 0b000000000000000000000000000000000000000000000000100\n\tLT\t\t\t\t\t= 0b000000000000000000000000000000000000000000000000010\n\tRT\t\t\t\t\t= 0b000000000000000000000000000000000000000000000000001\n\n\nDIRECTLY_TRANSLATABLE_BUTTONS = (0\n\t| DeckButton.A | DeckButton.B | DeckButton.X | DeckButton.Y\n\t| DeckButton.LB | DeckButton.RB | DeckButton.LT | DeckButton.RT\n\t| DeckButton.START | DeckButton.C | DeckButton.BACK\n\t| DeckButton.RGRIP | DeckButton.LGRIP\n\t| DeckButton.RPADTOUCH | DeckButton.LPADTOUCH\n\t| DeckButton.RPADPRESS | DeckButton.LPADPRESS\n);\n\n\ndef map_button(i, from_, to):\n\treturn to if (i.buttons & from_) else 0\n\n\ndef map_dpad(i, low, hi):\n\tif (i.buttons & low) != 0:\n\t\treturn STICK_PAD_MIN\n\telif (i.buttons & hi) != 0:\n\t\treturn STICK_PAD_MAX\n\telse:\n\t\treturn 0\n\n\ndef apply_deadzone(value, deadzone):\n\tif value > -deadzone and value < deadzone:\n\t\treturn 0\n\treturn value\n\n\nclass Deck(USBDevice, SCController):\n\tflags = ( 0\n\t\t| ControllerFlags.SEPARATE_STICK\n\t\t| ControllerFlags.HAS_DPAD\n\t\t| ControllerFlags.IS_DECK\n\t\t| ControllerFlags.HAS_RSTICK\n\t)\n\t\n\tdef __init__(self, device, handle, daemon):\n\t\tself.daemon = daemon\n\t\tUSBDevice.__init__(self, device, handle)\n\t\tSCController.__init__(self, self, CONTROLIDX, ENDPOINT)\n\t\tself._old_state = DeckInput()\n\t\tself._input = DeckInput()\n\t\tself._ready = False\n\t\t\n\t\tself.claim_by(klass=3, subclass=0, protocol=0)\n\t\tself.read_serial()\n\t\n\tdef generate_serial(self):\n\t\tself._serial = \"%s:%s\" % (self.device.getBusNumber(), self.device.getPortNumber())\n\t\n\tdef disconnected(self):\n\t\t# Overrided to skip returning serial# to pool.\n\t\tpass\n\t\n\tdef set_gyro_enabled(self, enabled):\n\t\t# Always on on deck\n\t\tpass\n\t\n\tdef get_gyro_enabled(self):\n\t\t# Always on on deck\n\t\treturn True\n\t\n\tdef get_type(self):\n\t\treturn \"deck\"\n\t\n\tdef __repr__(self):\n\t\treturn \"<Deck %s>\" % (self.get_id(),)\n\t\n\tdef get_gui_config_file(self):\n\t\treturn \"deck.config.json\"\n\t\n\tdef configure(self, idle_timeout=None, enable_gyros=None, led_level=None):\n\t\tFORMAT = b'>BBBB60x'\n\t\t# Timeout & Gyros\n\t\tself._driver.overwrite_control(self._ccidx, struct.pack(\n\t\t\tFORMAT, SCPacketType.CONFIGURE, 0x03, 0x08, 0x07))\n\t\n\tdef clear_mappings(self):\n\t\tFORMAT = b'>BB62x'\n\t\t# Timeout & Gyros\n\t\tself._driver.overwrite_control(self._ccidx,\n\t\t\tstruct.pack(FORMAT, SCPacketType.CLEAR_MAPPINGS, 0x01))\n\t\n\tdef on_serial_got(self):\n\t\tlog.debug(\"Got SteamDeck with serial %s\", self._serial)\n\t\tself._id = \"deck%s\" % (self._serial,)\n\t\tself.set_input_interrupt(ENDPOINT, 64, self._on_input)\t\n\t\n\tdef _on_input(self, endpoint, data):\n\t\tif not self._ready:\n\t\t\tself.daemon.add_controller(self)\n\t\t\tself.configure()\n\t\t\tself._ready = True\n\n\t\tself._old_state, self._input = self._input, self._old_state\n\t\tctypes.memmove(ctypes.addressof(self._input), data, len(data))\n\t\tif self._input.seq % UNLIZARD_INTERVAL == 0:\n\t\t\t# Keeps lizard mode from happening\n\t\t\tself.clear_mappings()\n\t\t\n\t\t# Handle dpad\n\t\tself._input.dpad_x = map_dpad(self._input, DeckButton.DPAD_LEFT, DeckButton.DPAD_RIGHT)\n\t\tself._input.dpad_y = map_dpad(self._input, DeckButton.DPAD_DOWN, DeckButton.DPAD_UP)\n\t\t# Convert buttons\n\t\tself._input.buttons = (0\n\t\t\t| ((self._input.buttons & DIRECTLY_TRANSLATABLE_BUTTONS) << 8)\n\t\t\t| map_button(self._input, DeckButton.DOTS, SCButtons.DOTS)\n\t\t\t# | map_button(self._input, DeckButton.RSTICKTOUCH, ....)\t// not mapped\n\t\t\t# | map_button(self._input, DeckButton.LSTICKTOUCH, ....) // not mapped\n\t\t\t| map_button(self._input, DeckButton.LSTICKPRESS, SCButtons.STICKPRESS)\n\t\t\t| map_button(self._input, DeckButton.RSTICKPRESS, SCButtons.RSTICKPRESS)\n\t\t\t| map_button(self._input, DeckButton.LGRIP2, SCButtons.LGRIP2)\n\t\t\t| map_button(self._input, DeckButton.RGRIP2, SCButtons.RGRIP2)\n\t\t)\n\t\t# Convert triggers\n\t\tself._input.ltrig >>= 7\n\t\tself._input.rtrig >>= 7\n\t\t# Apply deadzones\n\t\tself._input.stick_x = apply_deadzone(self._input.stick_x, STICK_DEADZONE)\n\t\tself._input.stick_y = apply_deadzone(self._input.stick_y, STICK_DEADZONE)\n\t\tself._input.rstick_x = apply_deadzone(self._input.rstick_x, STICK_DEADZONE)\n\t\tself._input.rstick_y = apply_deadzone(self._input.rstick_y, STICK_DEADZONE)\n\t\t\n\t\t# Invert Gyro Roll to match Steam Controller coordinate system\n\t\tself._input.groll = -self._input.groll\n\n\t\tm = self.get_mapper()\n\t\tif m:\n\t\t\tself.mapper.input(self, self._old_state, self._input)\n\t\n\tdef close(self):\n\t\tif self._ready:\n\t\t\tself.daemon.remove_controller(self)\n\t\t\tself._ready = False\n\t\tUSBDevice.close(self)\n\t\n\tdef turnoff(self):\n\t\tlog.warning(\"Ignoring request to turn off steamdeck.\")\n\n\ndef init(daemon, config):\n\t\"\"\" Registers hotplug callback for controller dongle \"\"\"\n\tdef cb(device, handle):\n\t\treturn Deck(device, handle, daemon)\n\t\n\tregister_hotplug_device(cb, VENDOR_ID, PRODUCT_ID)\n\treturn True\n\n"
  },
  {
    "path": "scc/drivers/usb.py",
    "content": "\"\"\"\nCommon code for all (one) USB-based drivers.\n\nDriver that uses USB has to call\nregister_hotplug_device(callback, vendor_id, product_id, on_failure=None)\nmethod to get notified about connected USB devices.\n\nCallback will be called with following arguments:\n\tcallback(device, handle)\nCallback has to return created USBDevice instance or None.\n\"\"\"\nfrom scc.lib import usb1\n\nimport time, traceback, logging\nlog = logging.getLogger(\"USB\")\n\nclass USBDevice(object):\n\t\"\"\" Base class for all handled usb devices \"\"\"\n\tdef __init__(self, device, handle):\n\t\tself.device = device\n\t\tself.handle = handle\n\t\tself._claimed = []\n\t\tself._cmsg = []\t\t# controll messages\n\t\tself._rmsg = []\t\t# requests (excepts response)\n\t\tself._transfer_list = []\n\t\n\t\n\tdef set_input_interrupt(self, endpoint, size, callback):\n\t\t\"\"\"\n\t\tHelper method for setting up input transfer.\n\t\t\n\t\tcallback(endpoint, data) is called repeadedly with every packed recieved.\n\t\t\"\"\"\n\t\tdef callback_wrapper(transfer):\n\t\t\tif (transfer.getStatus() != usb1.TRANSFER_COMPLETED or\n\t\t\t\ttransfer.getActualLength() != size):\n\t\t\t\treturn\n\t\t\t\n\t\t\tdata = transfer.getBuffer()\n\t\t\ttry:\n\t\t\t\tcallback(endpoint, data)\n\t\t\texcept Exception as e:\n\t\t\t\tlog.error(\"Failed to handle recieved data\")\n\t\t\t\tlog.error(e)\n\t\t\t\tlog.error(traceback.format_exc())\n\t\t\tfinally:\n\t\t\t\ttransfer.submit()\n\t\t\n\t\ttransfer = self.handle.getTransfer()\n\t\ttransfer.setInterrupt(\n\t\t\tusb1.ENDPOINT_IN | endpoint,\n\t\t\tsize,\n\t\t\tcallback=callback_wrapper,\n\t\t)\n\t\ttransfer.submit()\n\t\tself._transfer_list.append(transfer)\n\t\n\t\n\tdef send_control(self, index, data):\n\t\t\"\"\" Schedules writing control to device \"\"\"\n\t\tzeros = b'\\x00' * (64 - len(data))\n\t\t\n\t\tself._cmsg.insert(0, (\n\t\t\t0x21,\t# request_type\n\t\t\t0x09,\t# request\n\t\t\t0x0300,\t# value\n\t\t\tindex,\n\t\t\tdata + zeros,\n\t\t\t0\t\t# Timeout\n\t\t))\n\t\n\t\n\tdef overwrite_control(self, index, data):\n\t\t\"\"\"\n\t\tSimilar to send_control, but this one checks and overwrites\n\t\talready scheduled controll for same device/index.\n\t\t\"\"\"\n\t\tfor x in self._cmsg:\n\t\t\tx_index, x_data, x_timeout = x[-3:]\n\t\t\t# First 3 bytes are for PacketType, size and ConfigType\n\t\t\tif x_index == index and x_data[0:3] == data[0:3]:\n\t\t\t\tself._cmsg.remove(x)\n\t\t\t\tbreak\n\t\tself.send_control(index, data)\n\t\n\t\n\tdef make_request(self, index, callback, data, size=64):\n\t\t\"\"\"\n\t\tSchedules synchronous request that requires response.\n\t\tRequest is done ASAP and provided callback is called with recieved data.\n\t\t\"\"\"\n\t\tself._rmsg.append((\n\t\t\t(\n\t\t\t\t0x21,\t# request_type\n\t\t\t\t0x09,\t# request\n\t\t\t\t0x0300,\t# value\n\t\t\t\tindex, data\n\t\t\t), index, size, callback\n\t\t))\n\t\n\t\n\tdef flush(self):\n\t\t\"\"\" Flushes all prepared control messages to the device \"\"\"\n\t\twhile len(self._cmsg):\n\t\t\tmsg = self._cmsg.pop()\n\t\t\tself.handle.controlWrite(*msg)\n\t\t\n\t\twhile len(self._rmsg):\n\t\t\tmsg, index, size, callback = self._rmsg.pop()\n\t\t\tself.handle.controlWrite(*msg)\n\t\t\tdata = self.handle.controlRead(\n\t\t\t\t0xA1,\t# request_type\n\t\t\t\t0x01,\t# request\n\t\t\t\t0x0300,\t# value\n\t\t\t\tindex, size\n\t\t\t)\n\t\t\tcallback(data)\n\t\n\t\n\tdef force_restart(self):\n\t\t\"\"\"\n\t\tRestarts device, closes handle and tries to re-grab it again.\n\t\tDon't use unless absolutelly necessary.\n\t\t\"\"\"\n\t\ttp = self.device.getVendorID(), self.device.getProductID()\n\t\tself.close()\n\t\t_usb._retry_devices.append(tp)\n\t\n\t\n\tdef claim(self, number):\n\t\t\"\"\"\n\t\tHelper method; Remembers list of claimed interfaces and allows to\n\t\tunclaim them all at once using unclaim() method or automatically when\n\t\tdevice is closed.\n\t\t\"\"\"\n\t\tself.handle.claimInterface(number)\n\t\tself._claimed.append(number)\n\t\n\t\n\tdef claim_by(self, klass, subclass, protocol):\n\t\t\"\"\"\n\t\tClaims all interfaces with specified parameters.\n\t\tReturns number of claimed interfaces\n\t\t\"\"\"\n\t\trv = 0\n\t\tfor inter in self.device[0]:\n\t\t\tfor setting in inter:\n\t\t\t\tnumber = setting.getNumber()\n\t\t\t\tif self.handle.kernelDriverActive(number):\n\t\t\t\t\tself.handle.detachKernelDriver(number)\n\t\t\t\tksp = setting.getClass(), setting.getSubClass(), setting.getProtocol()\n\t\t\t\tif ksp == (klass, subclass, protocol):\n\t\t\t\t\tself.claim(number)\n\t\t\t\t\trv += 1\n\t\treturn rv\n\t\n\t\n\tdef unclaim(self):\n\t\t\"\"\" Unclaims all claimed interfaces \"\"\"\n\t\tfor number in self._claimed:\n\t\t\ttry:\n\t\t\t\tself.handle.releaseInterface(number)\n\t\t\t\tself.handle.attachKernelDriver(number)\n\t\t\texcept usb1.USBErrorNoDevice:\n\t\t\t\t# Safe to ignore, happens when USB is removed\n\t\t\t\tpass\n\t\tself._claimed = []\n\t\n\t\n\tdef close(self):\n\t\t\"\"\" Called after device is disconnected \"\"\"\n\t\ttry:\n\t\t\tself.unclaim()\n\t\texcept: pass\n\t\ttry:\n\t\t\tself.handle.resetDevice()\n\t\t\tself.handle.close()\n\t\texcept: pass\n\n\nclass USBDriver(object):\n\tdef __init__(self):\n\t\tself.daemon = None\n\t\tself._known_ids = {}\n\t\tself._fail_cbs = {}\n\t\tself._devices = {}\n\t\tself._syspaths = {}\n\t\tself._started = False\n\t\tself._retry_devices = []\n\t\tself._retry_devices_timer = 0\n\t\tself._ctx = None\t# Set by start method\n\t\tself._changed = 0\n\t\n\t\n\tdef set_daemon(self, daemon):\n\t\tself.daemon = daemon\n\t\n\t\n\tdef on_exit(self, *a):\n\t\t\"\"\" Closes all devices and unclaims all interfaces \"\"\"\n\t\tif len(self._devices):\n\t\t\tlog.debug(\"Releasing devices...\")\n\t\t\tto_release, self._devices, self._syspaths = self._devices.values(), {}, {}\n\t\t\tfor d in to_release:\n\t\t\t\td.close()\n\t\n\t\n\tdef start(self):\n\t\tself._ctx = usb1.USBContext()\n\t\t\n\t\tdef fd_cb(*a):\n\t\t\tself._changed += 1\n\t\t\n\t\tdef register_fd(fd, events, *a):\n\t\t\tself.daemon.get_poller().register(fd, events, fd_cb)\n\t\t\n\t\tdef unregister_fd(fd, *a):\n\t\t\tself.daemon.get_poller().unregister(fd)\n\t\t\n\t\tself._ctx.setPollFDNotifiers(register_fd, unregister_fd)\n\t\tfor fd, events in self._ctx.getPollFDList():\n\t\t\tregister_fd(fd, events)\t\n\t\tself._started = True\n\t\n\t\n\tdef handle_new_device(self, syspath, vendor, product):\n\t\ttp = vendor, product\n\t\thandle = None\n\t\tif tp not in self._known_ids:\n\t\t\treturn\n\t\tbus, dev = self.daemon.get_device_monitor().get_usb_address(syspath)\n\t\tfor device in self._ctx.getDeviceIterator():\n\t\t\tif (bus, dev) == (device.getBusNumber(), device.getDeviceAddress()):\n\t\t\t\ttry:\n\t\t\t\t\thandle = device.open()\n\t\t\t\t\tbreak\n\t\t\t\texcept usb1.USBError as e:\n\t\t\t\t\tlog.error(\"Failed to open USB device %.4x:%.4x : %s\", tp[0], tp[1], e)\n\t\t\t\t\tif tp in self._fail_cbs:\n\t\t\t\t\t\tself._fail_cbs[tp](syspath, *tp)\n\t\t\t\t\t\treturn\n\t\t\t\t\tif self.daemon:\n\t\t\t\t\t\tself.daemon.add_error(\n\t\t\t\t\t\t\t\"usb:%s:%s\" % (tp[0], tp[1]),\n\t\t\t\t\t\t\t\"Failed to open USB device: %s\" % (e,)\n\t\t\t\t\t\t)\n\t\t\t\t\treturn\n\t\telse:\n\t\t\treturn\n\t\t\n\t\tcallback = self._known_ids[tp]\n\t\thandled_device = None\n\t\ttry:\n\t\t\thandled_device = callback(device, handle)\n\t\texcept usb1.USBErrorBusy as e:\n\t\t\tlog.error(\"Failed to claim USB device %.4x:%.4x : %s\", tp[0], tp[1], e)\n\t\t\tif tp in self._fail_cbs:\n\t\t\t\tdevice.close()\n\t\t\t\tself._fail_cbs[tp](*tp)\n\t\t\t\treturn False\n\t\t\telse:\n\t\t\t\tif self.daemon:\n\t\t\t\t\tself.daemon.add_error(\n\t\t\t\t\t\t\"usb:%s:%s\" % (tp[0], tp[1]),\n\t\t\t\t\t\t\"Failed to claim USB device: %s\" % (e,)\n\t\t\t\t\t)\n\t\t\t\tself._retry_devices.append((syspath, tp))\n\t\t\t\tdevice.close()\n\t\t\t\treturn True\n\t\tif handled_device:\n\t\t\tself._devices[device] = handled_device\n\t\t\tself._syspaths[syspath] = device\n\t\t\tlog.debug(\"USB device added: %.4x:%.4x\", *tp)\n\t\t\tself.daemon.remove_error(\"usb:%s:%s\" % (tp[0], tp[1]))\n\t\t\treturn True\n\t\telse:\n\t\t\tlog.warning(\"Known USB device ignored: %.4x:%.4x\", *tp)\n\t\t\tdevice.close()\n\t\t\treturn False\n\t\n\t\n\tdef handle_removed_device(self, syspath, vendor, product):\n\t\tif syspath in self._syspaths:\n\t\t\tdevice = self._syspaths[syspath]\n\t\t\thandled_device = self._devices[device]\n\t\t\tdel self._syspaths[syspath]\n\t\t\tdel self._devices[device]\n\t\t\thandled_device.close()\n\t\t\ttry:\n\t\t\t\tdevice.close()\n\t\t\texcept usb1.USBErrorNoDevice:\n\t\t\t\t# Safe to ignore, happens when device is physiucally removed\n\t\t\t\tpass\n\t\n\t\n\tdef register_hotplug_device(self, callback, vendor_id, product_id, on_failure):\n\t\tself._known_ids[vendor_id, product_id] = callback\n\t\tif on_failure:\n\t\t\tself._fail_cbs[vendor_id, product_id] = on_failure\n\t\tmonitor = self.daemon.get_device_monitor()\n\t\tmonitor.add_callback(\"usb\", vendor_id, product_id,\n\t\t\t\tself.handle_new_device, self.handle_removed_device)\n\t\tlog.debug(\"Registered USB driver for %.4x:%.4x\", vendor_id, product_id)\n\t\n\t\n\tdef unregister_hotplug_device(self, callback, vendor_id, product_id):\n\t\tif self._known_ids.get((vendor_id, product_id)) == callback:\n\t\t\tdel self._known_ids[vendor_id, product_id]\n\t\t\tif (vendor_id, product_id) in self._fail_cbs:\n\t\t\t\tdel self._fail_cbs[vendor_id, product_id]\n\t\t\tlog.debug(\"Unregistred USB driver for %.4x:%.4x\", vendor_id, product_id)\n\t\n\t\n\tdef mainloop(self):\n\t\tif self._changed > 0:\n\t\t\tself._ctx.handleEventsTimeout()\n\t\t\tself._changed = 0\n\t\t\n\t\tfor d in self._devices.values():\t\t# TODO: don't use .values() here\n\t\t\ttry:\n\t\t\t\td.flush()\n\t\t\texcept usb1.USBErrorPipe:\n\t\t\t\tlog.error(\"USB device %s disconnected durring flush\", d)\n\t\t\t\td.close()\n\t\t\t\tbreak\n\t\tif len(self._retry_devices):\n\t\t\tif time.time() > self._retry_devices_timer:\n\t\t\t\tself._retry_devices_timer = time.time() + 5.0\n\t\t\t\tlst, self._retry_devices = self._retry_devices, []\n\t\t\t\tfor syspath, (vendor, product) in lst:\n\t\t\t\t\tself.handle_new_device(syspath, vendor, product)\n\n\n# USBDriver should be process-wide singleton\n_usb = USBDriver()\n\ndef init(daemon, config):\n\t_usb.set_daemon(daemon)\n\tdaemon.add_on_exit(_usb.on_exit)\n\tdaemon.add_mainloop(_usb.mainloop)\n\treturn True\n\ndef start(daemon):\n\t_usb.start()\n\n\ndef register_hotplug_device(callback, vendor_id, product_id, on_failure=None):\n\t_usb.register_hotplug_device(callback, vendor_id, product_id, on_failure)\n\n\ndef unregister_hotplug_device(callback, vendor_id, product_id):\n\t_usb.unregister_hotplug_device(callback, vendor_id, product_id)\n"
  },
  {
    "path": "scc/foreign/__init__.py",
    "content": "#!/usr/bin/env python2\n\npass"
  },
  {
    "path": "scc/foreign/vdf.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nImports VDF profile and converts it to Profile object.\n\"\"\"\nfrom scc.uinput import Keys, Axes, Rels\nfrom scc.actions import Action, NoAction, ButtonAction, DPadAction, XYAction\nfrom scc.actions import HatRightAction, TriggerAction, MouseAction\nfrom scc.actions import HatUpAction, HatDownAction, HatLeftAction\nfrom scc.actions import AxisAction, RelAreaAction, MultiAction\nfrom scc.special_actions import ChangeProfileAction, GridMenuAction, RadialMenuAction, MenuAction\nfrom scc.modifiers import SensitivityModifier, ClickModifier, FeedbackModifier\nfrom scc.constants import SCButtons, HapticPos, TRIGGER_CLICK, YAW, ROLL\nfrom scc.modifiers import BallModifier, DoubleclickModifier\nfrom scc.modifiers import HoldModifier, ModeModifier\nfrom scc.parser import ActionParser, ParseError\nfrom scc.menu_data import MenuData, MenuItem\nfrom scc.profile import Profile\nfrom scc.lib.vdf import parse_vdf, ensure_list\n\nimport logging\nlog = logging.getLogger(\"import.vdf\")\n\nclass VDFProfile(Profile):\n\tBUTTON_TO_BUTTON = {\n\t\t# maps button keys from vdf file to SCButtons constants\n\t\t# It looks like uppercase button and lowercase button aliases are needed\n\t\t'button_a'\t\t\t: SCButtons.A,\n\t\t'button_A'\t\t\t: SCButtons.A,\n\t\t'button_b'\t\t\t: SCButtons.B,\n\t\t'button_B'\t\t\t: SCButtons.B,\n\t\t'button_x'\t\t\t: SCButtons.X,\n\t\t'button_X'\t\t\t: SCButtons.X,\n\t\t'button_y'\t\t\t: SCButtons.Y,\n\t\t'button_Y'\t\t\t: SCButtons.Y,\n\t\t'button_back_left'\t: SCButtons.LGRIP,\n\t\t'button_back_right'\t: SCButtons.RGRIP,\n\t\t'button_menu'\t\t: SCButtons.BACK,\n\t\t'button_escape'\t\t: SCButtons.START,\t# what what what\n\t\t'left_bumper'\t\t: SCButtons.LB,\n\t\t'right_bumper'\t\t: SCButtons.RB,\n\t\t'left_click'\t\t: SCButtons.LPAD,\n\t\t'right_click'\t\t: SCButtons.RPAD,\n\t}\n\t\n\tSPECIAL_KEYS = {\n\t\t# Maps some key names from vdf file to Keys.* constants.\n\t\t# Rest of key names are converted in convert_key_name.\n\t\t'FORWARD_SLASH' : Keys.KEY_SLASH,\n\t\t'VOLUME_DOWN' : Keys.KEY_VOLUMEDOWN,\n\t\t'VOLUME_UP' : Keys.KEY_VOLUMEUP,\n\t\t'NEXT_TRACK' : Keys.KEY_NEXTSONG,\n\t\t'PREV_TRACK' : Keys.KEY_PREVIOUSSONG,\n\t\t'PAGE_UP' : Keys.KEY_PAGEUP,\n\t\t'PAGE_DOWN' : Keys.KEY_PAGEDOWN,\n\t\t'SINGLE_QUOTE' : Keys.KEY_APOSTROPHE,\n\t\t'DASH' : Keys.KEY_MINUS,\n\t\t'RETURN' : Keys.KEY_ENTER,\n\t\t'ESCAPE' : Keys.KEY_ESC,\n\t\t'PERIOD' : Keys.KEY_DOT,\n\t\t\"LEFT_BRACKET\" : Keys.KEY_LEFTBRACE,\n\t\t\"RIGHT_BRACKET\" : Keys.KEY_RIGHTBRACE,\n\t\t'KEYPAD_DASH' : Keys.KEY_KPMINUS,\n\t\t'KEYPAD_FORWARD_SLASH' : Keys.KEY_KPSLASH,\n\t\t'LEFT_CONTROL' : Keys.KEY_LEFTCTRL,\n\t\t'RIGHT_CONTROL' : Keys.KEY_RIGHTCTRL,\n\t}\n\t\n\tSPECIAL_BUTTONS = {\n\t\t# As SPECIAL_KEYS, but for buttons.\n\t\t'shoulder_left' : Keys.BTN_TL,\n\t\t'shoulder_right' : Keys.BTN_TR,\n\t\t'joystick_left' : Keys.BTN_THUMBL,\n\t\t'joystick_right' : Keys.BTN_THUMBR,\n\t}\n\t\n\tREGION_IMPORT_FACTOR = 0.6\t\t# Bulgarian const.\n\t\n\t\n\tdef __init__(self, name = \"Unnamed\"):\n\t\tProfile.__init__(self, ActionParser())\n\t\tself.name = name\n\t\tself.next_menu_id = 1\n\t\tself.action_set_id = 0\n\t\tself.action_sets = { 'default' : self }\n\t\tself.action_set_switches = set()\n\t\n\t\n\tdef parse_action(self, lst_or_str, button=None):\n\t\t\"\"\"\n\t\tParses action from vdf file. a_string can be either string or list of\n\t\tstrings, in which case MultiAction is returned.\n\t\t\n\t\tReturns Action instance or ParseError if action is not recognized.\n\t\t\"\"\"\n\t\tif type(lst_or_str) == list:\n\t\t\treturn MultiAction.make(*[ self.parse_action(x) for x in lst_or_str ])\n\t\t# Split string into binding type, name and parameters\n\t\tbinding, params = lst_or_str.split(\" \", 1)\n\t\tif \",\" in params:\n\t\t\tparams, name = params.split(\",\", 1)\n\t\telse:\n\t\t\tparams, name = params, None\n\t\tparams = params.split(\" \")\n\t\tif name:\n\t\t\tname = name.strip()\n\t\t# Return apropriate Action for binding type\n\t\tif binding in (\"key_press\", \"mouse_button\"):\n\t\t\tif binding == \"mouse_button\":\n\t\t\t\tb = VDFProfile.convert_button_name(params[0])\n\t\t\telse:\n\t\t\t\tb = VDFProfile.convert_key_name(params[0])\n\t\t\treturn ButtonAction(b).set_name(name)\n\t\telif binding == \"xinput_button\":\n\t\t\t# Special cases, as dpad is apparently button on Windows\n\t\t\tb = params[0].strip().lower()\n\t\t\tif b == \"dpad_up\":\n\t\t\t\treturn HatUpAction(Axes.ABS_HAT0Y)\n\t\t\telif b == \"dpad_down\":\n\t\t\t\treturn HatDownAction(Axes.ABS_HAT0Y)\n\t\t\telif b == \"dpad_left\":\n\t\t\t\treturn HatLeftAction(Axes.ABS_HAT0X)\n\t\t\telif b == \"dpad_right\":\n\t\t\t\treturn HatRightAction(Axes.ABS_HAT0X)\n\t\t\telif b == \"trigger_left\":\n\t\t\t\treturn AxisAction(Axes.ABS_Z)\n\t\t\telif b == \"trigger_right\":\n\t\t\t\treturn AxisAction(Axes.ABS_RZ)\n\t\t\telse:\n\t\t\t\tb = VDFProfile.convert_button_name(b)\n\t\t\t\treturn ButtonAction(b).set_name(name)\n\t\telif binding in (\"mode_shift\"):\n\t\t\tif button is None:\n\t\t\t\tlog.warning(\"Ignoring modeshift assigned to no button: '%s'\" % (lst_or_str,))\n\t\t\t\treturn NoAction()\n\t\t\tif button not in VDFProfile.BUTTON_TO_BUTTON:\n\t\t\t\tlog.warning(\"Ignoring modeshift assigned to unknown button: '%s'\" % (button,))\n\t\t\t\treturn NoAction()\n\t\t\tself.modeshift_buttons[VDFProfile.BUTTON_TO_BUTTON[button]] = (\n\t\t\t\tparams[1], params[0]\n\t\t\t)\n\t\t\treturn NoAction()\n\t\telif binding in (\"controller_action\"):\n\t\t\tif params[0] == \"CHANGE_PRESET\":\n\t\t\t\tid = int(params[1]) - 1\n\t\t\t\tcpa = ChangeProfileAction(\"action_set:%s\" % (id,))\n\t\t\t\tself.action_set_switches.add(cpa)\n\t\t\t\treturn cpa\n\t\t\t\n\t\t\tlog.warning(\"Ignoring controller_action '%s' binding\" % (params[0],))\n\t\t\treturn NoAction()\n\t\telif binding == \"mouse_wheel\":\n\t\t\tif params[0].lower() == \"scroll_down\":\n\t\t\t\treturn MouseAction(Rels.REL_WHEEL, -1)\n\t\t\telse:\n\t\t\t\treturn MouseAction(Rels.REL_WHEEL, 1)\n\t\telif binding == \"game_action\":\n\t\t\tlog.warning(\"Ignoring game_action binding: '%s'\" % (lst_or_str,))\n\t\t\treturn NoAction()\n\n\t\telse:\n\t\t\traise ParseError(\"Unknown binding: '%s'\" % (binding,))\n\t\n\t\n\t@staticmethod\n\tdef parse_modifiers(group, action, side):\n\t\t\"\"\"\n\t\tIf passed group or activator has 'settings' key, converts known\n\t\tsettings to one or more Modifier.\n\t\t\n\t\tReturns resulting Action\n\t\t\"\"\"\n\t\tif \"settings\" in group:\n\t\t\tsettings = group[\"settings\"]\n\t\t\tsens = 1.0, 1.0, 1.0\n\t\t\tif \"sensitivity\" in settings:\n\t\t\t\ts = float(settings[\"sensitivity\"]) / 100.0\n\t\t\t\tsens = s, s, s\n\t\t\tif \"haptic_intensity\" in settings:\n\t\t\t\taction = FeedbackModifier(\n\t\t\t\t\tHapticPos.LEFT if side == Profile.LEFT else HapticPos.RIGHT,\n\t\t\t\t\t512 * int(settings[\"haptic_intensity\"]), 8, action)\n\t\t\tif \"invert_x\" in settings and int(settings[\"invert_x\"]):\n\t\t\t\tsens = -1.0 * sens[0], sens[1], sens[2]\n\t\t\tif \"invert_y\" in settings and int(settings[\"invert_y\"]):\n\t\t\t\tsens = sens[0], -1.0 * sens[1], sens[2]\n\t\t\tif \"invert_z\" in settings and int(settings[\"invert_z\"]):\n\t\t\t\tsens = sens[0], sens[1], -1.0 * sens[2]\n\t\t\t\n\t\t\tif sens != (1.0, 1.0, 1.0):\n\t\t\t\taction = SensitivityModifier(sens[0], sens[1], sens[2], action)\n\t\t\t\n\t\t\n\t\treturn action\n\t\n\t\n\t@staticmethod\n\tdef convert_key_name(name):\n\t\t\"\"\"\n\t\tConverts keys names used in vdf profiles to Keys.KEY_* constants.\n\t\t\"\"\"\n\t\tif name in VDFProfile.SPECIAL_KEYS:\n\t\t\treturn VDFProfile.SPECIAL_KEYS[name]\n\t\telif name.endswith(\"_ARROW\"):\n\t\t\tkey = \"KEY_%s\" % (name[:-6],)\n\t\telif \"KEYPAD_\" in name:\n\t\t\tkey = \"KEY_%s\" % (name.replace(\"KEYPAD_\", \"KP\"),)\n\t\telif \"LEFT_\" in name:\n\t\t\tkey = \"KEY_%s\" % (name.replace(\"LEFT_\", \"LEFT\"),)\n\t\telif \"RIGHT_\" in name:\n\t\t\tkey = \"KEY_%s\" % (name.replace(\"RIGHT_\", \"RIGHT\"),)\n\t\telse:\n\t\t\tkey = \"KEY_%s\" % (name,)\n\t\tif hasattr(Keys, key):\n\t\t\treturn getattr(Keys, key)\n\t\tif hasattr(Keys, key.upper()):\n\t\t\treturn getattr(Keys, key.upper())\n\t\traise ParseError(\"Unknown key: '%s'\" % (name,))\n\t\n\t\n\t@staticmethod\n\tdef convert_button_name(name):\n\t\t\"\"\"\n\t\tConverts button names used in vdf profiles to Keys.BTN_* constants.\n\t\t\"\"\"\n\t\tif name.lower() in VDFProfile.SPECIAL_BUTTONS:\n\t\t\treturn VDFProfile.SPECIAL_BUTTONS[name.lower()]\n\t\tkey = \"BTN_%s\" % (name.upper(),)\n\t\tif hasattr(Keys, key):\n\t\t\treturn getattr(Keys, key)\n\t\traise ParseError(\"Unknown button: '%s'\" % (name,))\t\n\t\n\t\n\tdef parse_button(self, bdef, button=None):\n\t\t\"\"\"\n\t\tParses button definition from vdf file.\n\t\tParameter can be either string, as used in v2, or dict used in v3.\n\t\t\"\"\"\n\t\tif type(bdef) == str:\n\t\t\t# V2\n\t\t\treturn self.parse_action(bdef, button)\n\t\telif type(bdef) == list:\n\t\t\t# V2\n\t\t\treturn MultiAction.make(*[ self.parse_action(x, button) for x in bdef ])\n\t\telif \"activators\" in bdef:\n\t\t\t# V3\n\t\t\tact_actions = []\n\t\t\tfor k in (\"Full_Press\", \"Double_Press\", \"Long_Press\"):\n\t\t\t\ta = NoAction()\n\t\t\t\tif k in bdef[\"activators\"]:\n\t\t\t\t\t# TODO: Handle multiple bindings\n\t\t\t\t\tbindings = ensure_list(bdef[\"activators\"][k])[0]\n\t\t\t\t\ta = self.parse_action(bindings[\"bindings\"][\"binding\"], button)\n\t\t\t\t\ta = VDFProfile.parse_modifiers(bindings, a, Profile.RIGHT)\n\t\t\t\t\t# holly...\n\t\t\t\tact_actions.append(a)\n\t\t\tnormal, double, hold = act_actions\n\t\t\tif not double and not hold:\n\t\t\t\treturn normal\n\t\t\telif hold and not double:\n\t\t\t\treturn HoldModifier(hold, normal)\n\t\t\telse:\n\t\t\t\taction = DoubleclickModifier(double, normal)\n\t\t\t\taction.holdaction = hold\n\t\t\t\treturn action\n\t\telse:\n\t\t\tlog.warning(\"Failed to parse button definition: %s\" % (bdef,))\n\t\n\t\n\t@staticmethod\n\tdef get_inputs(group):\n\t\t\"\"\"\n\t\tReturns 'inputs' or 'bindings', whichever exists in passed group.\n\t\tIf neither exists, return None.\n\t\t\"\"\"\n\t\tif \"inputs\" in group:\n\t\t\treturn group[\"inputs\"]\n\t\tif \"bindings\" in group:\n\t\t\treturn group[\"bindings\"]\n\t\treturn {}\n\t\n\t\n\t@staticmethod\n\tdef find_group(data, id):\n\t\t\"\"\" Returns group with specified ID or None \"\"\"\n\t\tfor g in data.get_all_for('group'):\n\t\t\tif \"id\" in g and g[\"id\"] == id:\n\t\t\t\treturn g\n\t\treturn None\n\t\n\t\n\tdef parse_group(self, group, side):\n\t\t\"\"\"\n\t\tParses output (group) from vdf profile.\n\t\tReturns Action.\n\t\t\"\"\"\n\t\tif not \"mode\" in group:\n\t\t\traise ParseError(\"Group without mode\")\n\t\tmode = group[\"mode\"]\n\t\tinputs = VDFProfile.get_inputs(group)\n\t\t\n\t\tsettings = group[\"settings\"] if \"settings\" in group else {}\n\t\tfor o in (\"output_trigger\", \"output_joystick\"):\n\t\t\tif o in settings:\n\t\t\t\tif int(settings[o]) <= 1:\n\t\t\t\t\tside = Profile.LEFT\n\t\t\t\telse:\n\t\t\t\t\tside = Profile.RIGHT\n\t\t\n\t\tif mode == \"dpad\":\n\t\t\tkeys = []\n\t\t\tfor k in (\"dpad_north\", \"dpad_south\", \"dpad_east\", \"dpad_west\"):\n\t\t\t\tif k in inputs:\n\t\t\t\t\tkeys.append(self.parse_button(inputs[k]))\n\t\t\t\telse:\n\t\t\t\t\tkeys.append(NoAction())\n\t\t\taction = DPadAction(*keys)\n\t\telif mode == \"four_buttons\":\n\t\t\tkeys = []\n\t\t\tfor k in (\"button_y\", \"button_a\", \"button_x\", \"button_b\"):\n\t\t\t\tif k in inputs:\n\t\t\t\t\tkeys.append(self.parse_button(inputs[k]))\n\t\t\t\telse:\n\t\t\t\t\tkeys.append(NoAction())\n\t\t\taction = DPadAction(*keys)\n\t\telif mode == \"joystick_move\":\n\t\t\tif side == Profile.LEFT:\n\t\t\t\t# Left\n\t\t\t\taction = XYAction(AxisAction(Axes.ABS_X), AxisAction(Axes.ABS_Y))\n\t\t\telse:\n\t\t\t\t# Right\n\t\t\t\taction = XYAction(AxisAction(Axes.ABS_RX), AxisAction(Axes.ABS_RY))\n\t\telif mode == \"joystick_camera\":\n\t\t\toutput_joystick = 0\n\t\t\tif 'output_joystick' in settings:\n\t\t\t\toutput_joystick = int(settings['output_joystick'])\n\t\t\tif output_joystick == 0:\n\t\t\t\taction = BallModifier(XYAction(AxisAction(Axes.ABS_X), AxisAction(Axes.ABS_Y)))\n\t\t\telif output_joystick == 1:\n\t\t\t\taction = BallModifier(XYAction(AxisAction(Axes.ABS_RX), AxisAction(Axes.ABS_RY)))\n\t\t\telse:\n\t\t\t\t# TODO: Absolute mouse? Doesn't seems to do anything in Steam\n\t\t\t\taction = BallModifier(SensitivityModifier(0.1, 0.1, MouseAction()))\n\t\telif mode == \"mouse_joystick\":\n\t\t\taction = BallModifier(XYAction(AxisAction(Axes.ABS_RX), AxisAction(Axes.ABS_RY)))\n\t\telif mode == \"scrollwheel\":\n\t\t\taction = BallModifier(XYAction(MouseAction(Rels.REL_HWHEEL), MouseAction(Rels.REL_WHEEL)))\n\t\telif mode == \"touch_menu\":\n\t\t\t# Touch menu is converted to GridMenu\n\t\t\titems = []\n\t\t\tnext_item_id = 1\n\t\t\tfor k in inputs:\n\t\t\t\taction = self.parse_button(inputs[k])\n\t\t\t\titems.append(MenuItem(\n\t\t\t\t\t\"item_%s\" % (next_item_id,),\n\t\t\t\t\taction.describe(Action.AC_BUTTON),\n\t\t\t\t\taction\n\t\t\t\t))\n\t\t\t\tnext_item_id += 1\n\t\t\t# Menu is stored in profile, with generated ID\n\t\t\tmenu_id = \"menu_%s\" % (self.next_menu_id,)\n\t\t\tself.next_menu_id += 1\n\t\t\tself.menus[menu_id] = MenuData(*items)\n\t\t\t\n\t\t\taction = GridMenuAction(menu_id,\n\t\t\t\t'LEFT' if side == Profile.LEFT else 'RIGHT',\n\t\t\t\tSCButtons.LPAD if side == Profile.LEFT else SCButtons.RPAD\n\t\t\t)\n\t\telif mode == \"radial_menu\":\n\t\t\titems = []\n\t\t\tnext_item_id = 1\n\t\t\tfor k in inputs:\n\t\t\t\taction = self.parse_button(inputs[k])\n\t\t\t\titems.append(MenuItem(\n\t\t\t\t\t\"item_%s\" % (next_item_id,),\n\t\t\t\t\taction.describe(Action.AC_BUTTON),\n\t\t\t\t\taction\n\t\t\t\t))\n\t\t\t\tnext_item_id += 1\n\t\t\t# Menu is stored in profile, with generated ID\n\t\t\tmenu_id = \"menu_%s\" % (self.next_menu_id,)\n\t\t\tself.next_menu_id += 1\n\t\t\tself.menus[menu_id] = MenuData(*items)\n\t\t\t\n\t\t\taction = RadialMenuAction(menu_id,\n\t\t\t\t'LEFT' if side == Profile.LEFT else 'RIGHT',\n\t\t\t\tSCButtons.LPAD if side == Profile.LEFT else SCButtons.RPAD\n\t\t\t)\n\t\telif mode == \"absolute_mouse\":\n\t\t\tif \"click\" in inputs:\n\t\t\t\tif side == Profile.LEFT:\n\t\t\t\t\tself.add_by_binding(SCButtons.LPAD,\n\t\t\t\t\t\t\tself.parse_button(inputs[\"click\"]))\n\t\t\t\telse:\n\t\t\t\t\tself.add_by_binding(SCButtons.RPAD,\n\t\t\t\t\t\t\tself.parse_button(inputs[\"click\"]))\n\t\t\tif \"gyro_axis\" in settings:\n\t\t\t\tif int(settings[\"gyro_axis\"]) == 1:\n\t\t\t\t\taction = MouseAction(ROLL)\n\t\t\t\telse:\n\t\t\t\t\taction = MouseAction(YAW)\n\t\t\telse:\n\t\t\t\taction = MouseAction()\n\t\telif mode == \"mouse_wheel\":\n\t\t\taction = BallModifier(XYAction(MouseAction(Rels.REL_HWHEEL),\n\t\t\t \tMouseAction(Rels.REL_WHEEL)))\n\t\telif mode == \"trigger\":\n\t\t\tactions = []\n\t\t\tif \"click\" in inputs:\n\t\t\t\tactions.append(TriggerAction(TRIGGER_CLICK,\n\t\t\t\t\tself.parse_button(inputs[\"click\"])))\n\t\t\t\n\t\t\tif side == Profile.LEFT:\n\t\t\t\tactions.append(AxisAction(Axes.ABS_Z))\n\t\t\telse:\n\t\t\t\tactions.append(AxisAction(Axes.ABS_RZ))\n\t\t\t\n\t\t\taction = MultiAction.make(*actions)\n\t\telif mode == \"mouse_region\":\n\t\t\t# Read value and assume dafaults\n\t\t\tscale = float(settings[\"scale\"]) if \"scale\" in settings else 100.0\n\t\t\tx = float(settings[\"position_x\"]) if \"position_x\" in settings else 50.0\n\t\t\ty = float(settings[\"position_y\"]) if \"position_y\" in settings else 50.0\n\t\t\tw = float(settings[\"sensitivity_horiz_scale\"]) if \"sensitivity_horiz_scale\" in settings else 100.0\n\t\t\th = float(settings[\"sensitivity_vert_scale\"]) if \"sensitivity_vert_scale\" in settings else 100.0\n\t\t\t# Apply scale\n\t\t\tw = w * scale / 100.0\n\t\t\th = h * scale / 100.0\n\t\t\t# Convert to (0, 1) range\n\t\t\tx, y = x / 100.0, 1.0 - (y / 100.0)\n\t\t\tw, h = w / 100.0, h / 100.0\n\t\t\t# Convert to rectangle\n\t\t\tx1 = max(0.0, x - (w * VDFProfile.REGION_IMPORT_FACTOR))\n\t\t\tx2 = min(1.0, x + (w * VDFProfile.REGION_IMPORT_FACTOR))\n\t\t\ty1 = max(0.0, y - (h * VDFProfile.REGION_IMPORT_FACTOR))\n\t\t\ty2 = min(1.0, y + (h * VDFProfile.REGION_IMPORT_FACTOR))\n\t\t\t\n\t\t\taction = RelAreaAction(x1, y1, x2, y2)\n\t\telse:\n\t\t\traise ParseError(\"Unknown mode: '%s'\" % (group[\"mode\"],))\n\t\t\n\t\taction = VDFProfile.parse_modifiers(group, action, side)\n\t\treturn action\n\t\n\t\n\tdef parse_switches(self, group):\n\t\t\"\"\" Used for special cases of input groups that contains buttons \"\"\"\n\t\tinputs = VDFProfile.get_inputs(group)\n\t\tfor button in inputs:\n\t\t\tif button in (\"trigger_left\", \"left_trigger\"):\n\t\t\t\tself.add_by_binding(\n\t\t\t\t\t\"left_trigger\",\n\t\t\t\t\tAxisAction(Axes.ABS_Z)\n\t\t\t\t)\n\t\t\telif button in (\"trigger_right\", \"right_trigger\"):\n\t\t\t\tself.add_by_binding(\n\t\t\t\t\t\"right_trigger\",\n\t\t\t\t\tAxisAction(Axes.ABS_RZ)\n\t\t\t\t)\n\t\t\telif button in VDFProfile.BUTTON_TO_BUTTON:\n\t\t\t\tself.add_by_binding(\n\t\t\t\t\tVDFProfile.BUTTON_TO_BUTTON[button],\n\t\t\t\t\tself.parse_button(inputs[button], button)\n\t\t\t\t)\n\t\t\telse:\n\t\t\t\traise ParseError(\"Unknown button: '%s'\" % (button,))\n\t\n\t\n\tdef parse_input_binding(self, data, group_id, binding):\n\t\tgroup = VDFProfile.find_group(data, group_id)\n\t\tif group and \"mode\" in group:\n\t\t\tif binding.startswith(\"switch\"):\n\t\t\t\tself.parse_switches(group)\n\t\t\telif binding.startswith(\"button_diamond\"):\n\t\t\t\tself.parse_switches(group)\n\t\t\telse:\n\t\t\t\tif binding.startswith(\"right_\"):\n\t\t\t\t\taction = self.parse_group(group, Profile.RIGHT)\n\t\t\t\telse:\n\t\t\t\t\taction = self.parse_group(group, Profile.LEFT)\n\t\t\t\tif binding.endswith(\"modeshift\"):\n\t\t\t\t\tmodeshift_id = (group_id, binding.split(\" \")[0])\n\t\t\t\t\tself.modeshifts[modeshift_id] = action\n\t\t\t\telse:\n\t\t\t\t\tself.set_by_binding(binding, action)\n\t\n\t\n\tdef set_by_binding(self, binding, action):\n\t\t\"\"\"\n\t\tSets action specified by binding, one of group_source_bindings keys\n\t\tused in vdf profile. Also supports SCButtons constants for buttons.\n\t\t\n\t\tThrows ParseError if key is not supported.\n\t\t\"\"\"\n\t\tif binding in SCButtons.__members__.values():\n\t\t\tself.buttons[binding] = action\n\t\telif binding.startswith(\"left_trackpad\"):\n\t\t\tself.pads[Profile.LEFT] = action\n\t\telif binding.startswith(\"right_trackpad\"):\n\t\t\tself.pads[Profile.RIGHT] = action\n\t\telif binding.startswith(\"left_trigger\"):\n\t\t\tself.triggers[Profile.LEFT] = action\n\t\telif binding.startswith(\"right_trigger\"):\n\t\t\tself.triggers[Profile.RIGHT] = action\n\t\telif binding.startswith(\"joystick\"):\n\t\t\tself.stick = action\n\t\telif binding.startswith(\"gyro\"):\n\t\t\tself.gyro = action\n\t\telse:\n\t\t\traise ParseError(\"Unknown group source binding: '%s'\" % (binding,))\n\t\n\t\n\tdef add_by_binding(self, binding, action):\n\t\t\"\"\"\n\t\tAs set_by_binding, but if there is alrady action for specified binding\n\t\tset, creates MultiAction.\n\t\t\"\"\"\n\t\told = self.get_by_binding(binding)\n\t\tnew = MultiAction.make(old, action)\n\t\tif isinstance(new, MultiAction):\n\t\t\tnew = new.deduplicate()\n\t\tself.set_by_binding(binding, new)\n\t\n\t\n\tdef get_by_binding(self, binding):\n\t\t\"\"\"\n\t\tReturns action specified by binding, one of group_source_bindings keys\n\t\tused in vdf profile. Also supports SCButtons constants for buttons.\n\t\t\n\t\tThrows ParseError if key is not supported.\n\t\t\"\"\"\n\t\tif binding in SCButtons.__members__.values():\n\t\t\treturn self.buttons[binding]\n\t\telif binding.startswith(\"left_trackpad\"):\n\t\t\treturn self.pads[Profile.LEFT]\n\t\telif binding.startswith(\"right_trackpad\"):\n\t\t\treturn self.pads[Profile.RIGHT]\n\t\telif binding.startswith(\"left_trigger\"):\n\t\t\treturn self.triggers[Profile.LEFT]\n\t\telif binding.startswith(\"right_trigger\"):\n\t\t\treturn self.triggers[Profile.RIGHT]\n\t\telif binding.startswith(\"joystick\"):\n\t\t\treturn self.stick\n\t\telif binding.startswith(\"gyro\"):\n\t\t\treturn self.gyro\n\t\traise ParseError(\"Unknown group source binding: '%s'\" % (binding,))\n\t\n\t\n\t@staticmethod\n\tdef _load_preset(data, profile, preset):\n\t\tprofile.modeshifts = {}\n\t\tprofile.modeshift_buttons = {}\n\t\tif not 'group_source_bindings' in preset:\n\t\t\t# Empty preset\n\t\t\treturn\n\t\t\t\n\t\tgsb = preset['group_source_bindings']\n\t\tfor group_id in gsb:\n\t\t\tbinding = gsb[group_id]\n\t\t\tif not binding.endswith(\"inactive\"):\n\t\t\t\tprofile.parse_input_binding(data, group_id, binding)\n\t\t\n\t\tif \"switch_bindings\" in preset:\n\t\t\tprofile.parse_switches(preset['switch_bindings'])\n\t\t\n\t\tfor b in profile.modeshift_buttons:\n\t\t\tif profile.modeshift_buttons[b] in profile.modeshifts:\n\t\t\t\t# Should be always\n\t\t\t\tmodeshift = profile.modeshift_buttons[b]\n\t\t\telse:\n\t\t\t\tcontinue\n\t\t\taction = profile.modeshifts[modeshift]\n\t\t\ttrash, binding = modeshift\n\t\t\told = profile.get_by_binding(binding)\n\t\t\tprofile.set_by_binding(binding, ModeModifier(\n\t\t\t\tb, action,\n\t\t\t\told\n\t\t\t))\t\n\t\n\t\n\t@staticmethod\n\tdef _get_preset_name(data, preset):\n\t\t\"\"\" Returns name of specified preset \"\"\"\n\t\tname = preset[\"name\"].lower()\n\t\tif \"actions\" in data and name in data['actions']:\n\t\t\tname = data['actions'][name]['title']\n\t\treturn name\n\t\n\t\n\tdef action_set_by_id(self, id):\n\t\t\"\"\" Returns name of action set with specified id \"\"\"\n\t\tfor s in self.action_sets:\n\t\t\tif self.action_sets[s].action_set_id == id:\n\t\t\t\treturn s\n\t\treturn None\n\t\n\t\n\tdef load(self, filename):\n\t\t\"\"\"\n\t\tLoads profile from vdf file. Returns self.\n\t\tMay raise ValueError.\n\t\t\"\"\"\n\t\tdata = parse_vdf(open(filename, \"r\"))\n\t\tself.load_data(data)\n\t\n\t\n\tdef load_data(self, data):\n\t\tif 'controller_mappings' not in data:\n\t\t\traise ValueError(\"Invalid profile file\")\n\t\tdata = data['controller_mappings']\n\t\tif 'title' in data:\n\t\t\tname = data['title'].strip()\n\t\t\tif name:\n\t\t\t\tself.name = name\n\t\tpresets = ensure_list(data['preset'])\n\t\tfor p in presets:\n\t\t\tid = int(p[\"id\"])\n\t\t\tif id == 0:\n\t\t\t\t# Default profile\n\t\t\t\tVDFProfile._load_preset(data, self, p)\n\t\t\telse:\n\t\t\t\taset = VDFProfile(VDFProfile._get_preset_name(data, p))\n\t\t\t\taset.action_set_id = id\n\t\t\t\taset.action_set_switches = self.action_set_switches\n\t\t\t\tself.action_sets[aset.name] = aset\n\t\t\t\tVDFProfile._load_preset(data, aset, p)\n\t\t\n\t\tfor aset in self.action_sets.values():\n\t\t\taset.buttons[SCButtons.C] = HoldModifier(\n\t\t\t\tMenuAction(\"Default.menu\"), MenuAction(\"Default.menu\")\n\t\t\t)\n\t\t\n\t\treturn self\n\n\nif __name__ == \"__main__\":\n\timport sys\n\tfrom scc.tools import init_logging\n\tinit_logging()\n\tf = VDFProfile().load(sys.argv[1])\n\tf.save(\"output.sccprofile\")\n\n"
  },
  {
    "path": "scc/foreign/vdffz.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nImports VDFFZ profile and converts it to Profile object.\nVDFFZ is just VDF encapsulated in json, so this just gets one value and calls\nVDFProfile to decode rest.\n\"\"\"\nfrom .vdf import VDFProfile\nfrom scc.lib.vdf import parse_vdf\n\nimport json, logging\nlog = logging.getLogger(\"import.vdffz\")\n\nclass VDFFZProfile(VDFProfile):\n\tdef load(self, filename):\n\t\ttry:\n\t\t\tdata = json.loads(open(filename, \"r\").read())\n\t\texcept Exception as e:\n\t\t\traise ValueError(\"Failed to parse JSON\")\n\t\tif 'ConfigData' not in data:\n\t\t\traise ValueError(\"ConfigData missing in JSON\")\n\t\tself.load_data(parse_vdf(data['ConfigData'].encode('utf-8')))\n"
  },
  {
    "path": "scc/gestures.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Gestures\n\nEverything related to non-GUI part of gesture detection lies here.\nIt's technically part of SCC-Daemon, separater into special module just to keep\nit clean.\n\"\"\"\nfrom scc.actions import Action\nfrom scc.tools import circle_to_square, clamp\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX, CPAD, CPAD_MIN\nfrom scc.constants import CPAD_X_MAX, CPAD_Y_MAX\nfrom math import pi as PI, atan2, sqrt\nfrom itertools import groupby\n\nimport logging\nlog = logging.getLogger(\"Gestures\")\n\n\nclass GestureDetector(Action):\n\t\"\"\"\n\tDerived from Action, but not callable in profile.\n\t\n\tWhen daemon decides it's good time to start gesture, be it because of\n\tGestureAction special action or \"Gesture:\" message from client,\n\tit constructs instance of this class and leaves everything to it.\n\t\"\"\"\n\tUP\t\t\t= \"U\"\n\tDOWN\t\t= \"D\"\n\tLEFT\t\t= \"L\" \n\tRIGHT\t\t= \"R\"\n\t\n\t\n\tdef __init__(self, up_direction, on_finished):\n\t\tAction.__init__(self)\n\t\t# TODO: Configurable resolution\n\t\tself._resolution = 3\n\t\tself._deadzone = 1.0 / self._resolution / self._resolution\n\t\tself._up_direction = up_direction\n\t\tself._on_finished = on_finished\n\t\tself._enabled = False\n\t\tself._positions = []\n\t\tself._result = []\n\t\n\t\n\tdef enable(self):\n\t\t\"\"\" GestureDetector doesn't starts do detect anything until this is called \"\"\"\n\t\tself._enabled = True\n\t\tself._result = [ ]\n\t\n\t\n\tdef get_string(self):\n\t\t\"\"\" Returns string representation of (probably unfinished) gesture \"\"\"\n\t\treturn \"\".join(self._result)\n\t\n\t\n\tdef get_positions(self):\n\t\t\"\"\" Returns list of positions used to generate gesture \"\"\"\n\t\treturn self._positions\n\t\n\t\n\tdef get_resolution(self):\n\t\t\"\"\" Returns gesture resolution \"\"\"\n\t\treturn self._resolution\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif self._enabled:\n\t\t\tif (x, y) == (0, 0):\n\t\t\t\t# Pad was released\n\t\t\t\tself._enabled = False\n\t\t\t\tself._on_finished(self, \"\".join(self._result))\n\t\t\t\treturn\n\t\t\telse:\n\t\t\t\t# Convert positions on pad to position on grid\n\t\t\t\tif what == CPAD:\n\t\t\t\t\tx = clamp(0, float(x) / (CPAD_X_MAX - CPAD_MIN), 1.0)\n\t\t\t\t\ty = clamp(0, float(y) / (CPAD_Y_MAX - CPAD_MIN), 1.0)\n\t\t\t\t\tx *= self._resolution\n\t\t\t\t\ty *= self._resolution\n\t\t\t\telse:\n\t\t\t\t\tx -= STICK_PAD_MIN\n\t\t\t\t\ty = STICK_PAD_MAX - y\n\t\t\t\t\tx = float(x) / (float(STICK_PAD_MAX - STICK_PAD_MIN) / self._resolution)\n\t\t\t\t\ty = float(y) / (float(STICK_PAD_MAX - STICK_PAD_MIN) / self._resolution)\n\t\t\t\t# Check for deadzones around grid lines\n\t\t\t\tfor i in range(1, self._resolution):\n\t\t\t\t\tif x > i - self._deadzone and x < i + self._deadzone: return\n\t\t\t\t\tif y > i - self._deadzone and y < i + self._deadzone: return\n\t\t\t\t# Round\n\t\t\t\tx = clamp(0, int(x), self._resolution - 1)\n\t\t\t\ty = clamp(0, int(y), self._resolution - 1)\n\t\t\t\tif self._positions:\n\t\t\t\t\tox, oy = self._positions[-1]\n\t\t\t\t\tif (x, y) != (ox, oy):\n\t\t\t\t\t\tself._positions.append( (x, y) )\n\t\t\t\t\t\twhile (x, y) != (ox, oy):\n\t\t\t\t\t\t\tif x < ox:\n\t\t\t\t\t\t\t\tself._result.append(self.LEFT)\n\t\t\t\t\t\t\t\tx += 1\n\t\t\t\t\t\t\telif x > ox:\n\t\t\t\t\t\t\t\tself._result.append(self.RIGHT)\n\t\t\t\t\t\t\t\tx -= 1\n\t\t\t\t\t\t\telif y < oy:\n\t\t\t\t\t\t\t\tself._result.append(self.UP)\n\t\t\t\t\t\t\t\ty += 1\n\t\t\t\t\t\t\telif y > oy:\n\t\t\t\t\t\t\t\tself._result.append(self.DOWN)\n\t\t\t\t\t\t\t\ty -= 1\n\t\t\t\telse:\n\t\t\t\t\tself._positions.append( (x, y) )\n\n"
  },
  {
    "path": "scc/gui/__init__.py",
    "content": "#!/usr/bin/env python2\nfrom scc.constants import SCButtons\n\nBUTTON_ORDER = (\n\tSCButtons.A, SCButtons.B, SCButtons.X, SCButtons.Y,\n\tSCButtons.BACK, SCButtons.C, SCButtons.START,\n\tSCButtons.LB, SCButtons.RB, SCButtons.LT, SCButtons.RT,\n\tSCButtons.STICKPRESS, SCButtons.LPAD, SCButtons.RPAD,\n\tSCButtons.RGRIP, SCButtons.LGRIP\n)\n"
  },
  {
    "path": "scc/gui/aboutdialog.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - About dialog\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk\nfrom scc.gui.editor import Editor\nimport os, sys\n\nclass AboutDialog(Editor):\n\t\"\"\" Standard looking about dialog \"\"\"\n\tGLADE = \"about.glade\"\n\t\n\tdef __init__(self, app):\n\t\tself.app = app\n\t\tself.setup_widgets()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\t\n\t\t# Get app version\n\t\tapp_ver = \"(unknown version)\"\n\t\ttry:\n\t\t\timport pkg_resources, scc\n\t\t\tif scc.__file__.startswith(pkg_resources.require(\"sccontroller\")[0].location):\n\t\t\t\tapp_ver = \"v\" + pkg_resources.require(\"sccontroller\")[0].version\n\t\texcept:\n\t\t\t# pkg_resources is not available or __version__ file missing\n\t\t\t# There is no reason to crash on this.\n\t\t\tpass\n\t\t# Display version in UI\n\t\tself.builder.get_object(\"lblVersion\").set_label(app_ver)\n\t\n\t\n\tdef show(self, modal_for):\n\t\tif modal_for:\n\t\t\tself.window.set_transient_for(modal_for)\n\t\t\tself.window.set_modal(True)\n\t\tself.window.show()\n\t\n\t\n\tdef on_dialog_response(self, *a):\n\t\tself.close()\n\t"
  },
  {
    "path": "scc/gui/action_editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor\n\nAlso doubles as Menu Item Editor in some cases\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action, XYAction, NoAction, RingAction, TriggerAction\nfrom scc.special_actions import OSDAction, GesturesAction, MenuAction\nfrom scc.modifiers import SmoothModifier, NameModifier, BallModifier\nfrom scc.modifiers import Modifier, ClickModifier, ModeModifier\nfrom scc.modifiers import SensitivityModifier, FeedbackModifier\nfrom scc.modifiers import DeadzoneModifier, RotateInputModifier\nfrom scc.constants import HapticPos, SCButtons\nfrom scc.constants import CUT, ROUND, LINEAR, MINIMUM\nfrom scc.controller import HapticData\nfrom scc.profile import Profile\nfrom scc.macros import Macro\nfrom scc.tools import nameof\nfrom scc.gui.controller_widget import PRESSABLE, TRIGGERS, PADS\nfrom scc.gui.controller_widget import STICKS, GYROS, BUTTONS\nfrom scc.gui.modeshift_editor import ModeshiftEditor\nfrom scc.gui.parser import InvalidAction, GuiActionParser\nfrom scc.gui.simple_chooser import SimpleChooser\nfrom scc.gui.macro_editor import MacroEditor\nfrom scc.gui.ring_editor import RingEditor\nfrom scc.gui.dwsnc import headerbar\nfrom scc.gui.ae import AEComponent\nfrom scc.gui.editor import Editor\nimport os, logging, math, importlib, types\nlog = logging.getLogger(\"ActionEditor\")\n\n\nCOMPONENTS = (\t\t\t\t\t\t\t\t# List of known modules (components) in scc.gui.ae package\n\t'axis',\n\t'axis_action',\n\t'buttons',\n\t'custom',\n\t'dpad',\n\t'gesture',\n\t'gyro',\n\t'gyro_action',\n\t'per_axis',\n\t'special_action',\n\t'tilt',\n\t'trigger',\n\t# OSK-only components\n\t'osk_action',\n\t'osk_buttons',\n)\nXYZ = \"XYZ\"\t\t\t\t\t\t\t\t\t# Sensitivity settings keys\nAFP = (\"Amplitude\", \"Frequency\", \"Period\")\t# Feedback settings keys\nSMT = (\"Level\", \"Weight\", \"Filter\")\t\t\t# Smoothing setting keys\nDZN = (\"Lower\", \"Upper\")\t\t\t\t\t# Deadzone settings key\nFEEDBACK_SIDES = [ HapticPos.LEFT, HapticPos.RIGHT, HapticPos.BOTH ]\nDEADZONE_MODES = [ CUT, ROUND, LINEAR, MINIMUM ]\n\n\nclass ActionEditor(Editor):\n\tGLADE = \"action_editor.glade\"\n\tERROR_CSS = \" #error {background-color:green; color:red;} \"\n\t\n\tAEC_MENUITEM = -1\n\t\n\tMODE_TO_MODS = {\n\t\t# Specified which modifiers are compatibile with which editor mode.\n\t\t# That way, stuff like Rotation settings is not shown when editor\n\t\t# is used to edit menu actions.\n\t\tAction.AC_BUTTON\t: Action.MOD_OSD | Action.MOD_FEEDBACK,\n\t\tAction.AC_TRIGGER\t: Action.MOD_OSD | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_DEADZONE,\n\t\tAction.AC_STICK\t\t: Action.MOD_OSD | Action.MOD_CLICK | Action.MOD_DEADZONE | Action.MOD_ROTATE | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_SMOOTH,\n\t\tAction.AC_PAD\t\t: Action.MOD_OSD | Action.MOD_CLICK | Action.MOD_DEADZONE | Action.MOD_ROTATE | Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK | Action.MOD_SMOOTH | Action.MOD_BALL,\n\t\tAction.AC_GYRO\t\t: Action.MOD_OSD | Action.MOD_SENSITIVITY | Action.MOD_SENS_Z | Action.MOD_DEADZONE | Action.MOD_FEEDBACK,\n\t\tAction.AC_OSK\t\t: 0,\n\t\tAction.AC_MENU\t\t: Action.MOD_OSD,\n\t\tAEC_MENUITEM\t\t: 0,\n\t}\n\t\n\t\n\tdef __init__(self, app, callback):\n\t\tEditor.__init__(self)\n\t\tself.app = app\n\t\tself.id = None\n\t\tself.components = []\t\t\t# List of available components\n\t\tself.loaded_components = {}\t\t# by class name\n\t\tself.c_buttons = {} \t\t\t# Component-to-button dict\n\t\tself.sens_widgets = []\t\t\t# Sensitivity sliders, labels and 'clear' buttons\n\t\tself.feedback_widgets = []\t\t# Feedback settings sliders, labels and 'clear' buttons, plus default value as last item\n\t\tself.smoothing_widgets = []\t\t# Smoothing settings sliders, labels and 'clear' buttons, plus default value as last item\n\t\tself.deadzone_widgets = []\t\t# Deadzone settings sliders, labels and 'clear' buttons, plus default value as last item\n\t\tself.sens = [1.0] * 3\t\t\t# Sensitivity slider values\n\t\tself.sens_defaults = [1.0] * 3\t# Clear button clears to this\n\t\tself.feedback = [0.0] * 3\t\t# Feedback slider values, set later\n\t\tself.deadzone = [0] * 2\t\t\t# Deadzone slider values, set later\n\t\tself.deadzone_mode = None\t\t# None for 'disabled'\n\t\tself.feedback_position = None\t# None for 'disabled'\n\t\tself.smoothing = None\t\t\t# None for 'disabled'\n\t\tself.friction = -1\t\t\t\t# -1 for 'disabled'\n\t\tself.click = False\t\t\t\t# Click modifier value. None for disabled\n\t\tself.rotation_angle = 0\t\t\t# RotateInputModifier angle\n\t\tself.osd = False\t\t\t\t# 'OSD enabled' value.\n\t\tself.first_page_allowed = False\n\t\tself.setup_widgets()\n\t\tself.load_components()\n\t\tself.ac_callback = callback\t\t# This is different callback than ButtonChooser uses\n\t\tEditor.install_error_css()\n\t\tself._action = NoAction()\n\t\tself._replaced_action = None\n\t\tself._selected_component = None\n\t\tself._modifiers_enabled = True\n\t\tself._multiparams = [ None ] * 8\n\t\tself._mode = None\n\t\tself._recursing = False\n\t\n\t\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\theaderbar(self.builder.get_object(\"header\"))\n\t\tfor i in (0, 1, 2):\n\t\t\tself.sens_widgets.append((\n\t\t\t\tself.builder.get_object(\"sclSens%s\" % (XYZ[i],)),\n\t\t\t\tself.builder.get_object(\"lblSens%s\" % (XYZ[i],)),\n\t\t\t\tself.builder.get_object(\"btClearSens%s\" % (XYZ[i],)),\n\t\t\t\tself.builder.get_object(\"cbSensInvert%s\" % (XYZ[i],)),\n\t\t\t))\n\t\tfor key in AFP:\n\t\t\ti = AFP.index(key)\n\t\t\tself.feedback[i] = self.builder.get_object(\"sclF%s\" % (key,)).get_value()\n\t\t\tself.feedback_widgets.append((\n\t\t\t\tself.builder.get_object(\"sclF%s\" % (key,)),\n\t\t\t\tself.builder.get_object(\"lblF%s\" % (key,)),\n\t\t\t\tself.builder.get_object(\"btClearF%s\" % (key,)),\n\t\t\t\tself.feedback[i]\t# default value\n\t\t\t))\n\t\tfor key in SMT:\n\t\t\ti = SMT.index(key)\n\t\t\tself.smoothing_widgets.append((\n\t\t\t\tself.builder.get_object(\"lblSmooth%s\" % (key,)),\n\t\t\t\tself.builder.get_object(\"sclSmooth%s\" % (key,)),\n\t\t\t\tself.builder.get_object(\"btClearSmooth%s\" % (key,)),\n\t\t\t\tself.builder.get_object(\"sclSmooth%s\" % (key,)).get_value()\n\t\t\t))\n\t\tfor key in DZN:\n\t\t\ti = DZN.index(key)\n\t\t\tself.deadzone[i] = self.builder.get_object(\"sclDZ%s\" % (key,)).get_value()\n\t\t\tself.deadzone_widgets.append((\n\t\t\t\tself.builder.get_object(\"lblDZ%s\" % (key,)),\n\t\t\t\tself.builder.get_object(\"sclDZ%s\" % (key,)),\n\t\t\t\tself.builder.get_object(\"btClearDZ%s\" % (key,)),\n\t\t\t\tself.deadzone[i]\t# default value\n\t\t\t))\n\t\t\n\t\tif self.app.osd_mode:\n\t\t\tself.builder.get_object(\"entName\").set_sensitive(False)\n\t\n\t\n\tdef load_components(self):\n\t\t\"\"\" Loads list of editor components \"\"\"\n\t\t# Import and load components\n\t\tfor c in COMPONENTS:\n\t\t\tself.load_component(c)\n\t\tself._selected_component = None\n\t\n\t\n\tdef load_component(self, class_name):\n\t\t\"\"\"\n\t\tLoads and adds new component to editor.\n\t\tReturns component instance.\n\t\t\"\"\"\n\t\tif class_name in self.loaded_components:\n\t\t\treturn self.loaded_components[class_name]\n\t\tmod = importlib.import_module(\"scc.gui.ae.%s\" % (class_name,))\n\t\tfor x in mod.__all__:\n\t\t\tcls = getattr(mod, x)\n\t\t\tif isinstance(cls, type) and issubclass(cls, AEComponent):\n\t\t\t\tif cls is not AEComponent:\n\t\t\t\t\tinstance = cls(self.app, self)\n\t\t\t\t\tself.loaded_components[class_name] = instance\n\t\t\t\t\tself.components.append(instance)\n\t\t\t\t\treturn instance\n\t\n\t\n\tdef on_Dialog_destroy(self, *a):\n\t\tcbPreview = self.builder.get_object(\"cbPreview\")\n\t\tcbPreview.set_active(False)\n\t\tself.remove_added_widget()\n\t\tif self._selected_component is not None:\n\t\t\tself._selected_component.hidden()\n\t\n\t\n\tdef on_Dialog_key_press_event(self, window, event):\n\t\tif self.app.osd_mode and event.keyval == 65471:\n\t\t\tself.on_btOK_clicked()\n\t\n\t\n\tdef set_osd_enabled(self, value):\n\t\t\"\"\"\n\t\tSets value of OSD modifier checkbox, without firing any more events.\n\t\t\"\"\"\n\t\tself._recursing = True\n\t\tself.osd = value\n\t\tself.builder.get_object(\"cbOSD\").set_active(value)\n\t\tself._recursing = False\n\t\n\t\n\tdef show(self, transient_for):\n\t\tEditor.show(self, transient_for)\n\t\n\t\n\tdef close(self):\n\t\tself.on_Dialog_destroy()\n\t\tEditor.close(self)\n\t\n\t\n\tdef get_id(self):\n\t\t\"\"\" Returns ID of input that is being edited \"\"\"\n\t\treturn self.id\n\t\n\t\n\tdef on_link(self, link):\n\t\tparser = GuiActionParser()\n\t\tif link.startswith(\"quick://\"):\n\t\t\taction = parser.restart(link[8:]).parse()\n\t\t\tself.reset_active_component()\n\t\t\tself.set_action(action, from_custom=True)\n\t\telif link == \"grab://trigger_button\":\n\t\t\tdef cb(action):\n\t\t\t\taction = TriggerAction(254, 255, action)\n\t\t\t\tself.set_action(action, from_custom=True)\n\t\t\t\tself.force_page(\"trigger\")\n\t\t\tb = SimpleChooser(self.app, \"buttons\", cb)\n\t\t\tb.set_title(_(\"Select Button\"))\n\t\t\tb.hide_axes()\n\t\t\tb.show(self.window)\n\t\telif link.startswith(\"page://\"):\n\t\t\tdef cb():\n\t\t\t\tself.force_page(link[7:])\n\t\t\tGLib.timeout_add(0.1, cb)\n\t\telif link.startswith(\"advanced://\"):\n\t\t\texMore = self.builder.get_object(\"exMore\")\n\t\t\trvMore = self.builder.get_object(\"rvMore\")\n\t\t\tntbMore = self.builder.get_object(\"ntbMore\")\n\t\t\tassert exMore.get_visible()\n\t\t\texMore.set_expanded(True)\n\t\t\trvMore.set_reveal_child(True)\n\t\t\tif \"#\" in link:\n\t\t\t\tlink, name = link.split(\"#\")\n\t\t\t\tself.blink_widget(name)\n\t\t\tntbMore.set_current_page(int(link.split(\"/\")[-1]))\n\t\telse:\n\t\t\tlog.warning(\"Activated unknown link: %s\", link)\n\t\n\tdef on_action_type_changed(self, clicked_button):\n\t\t\"\"\"\n\t\tCalled when user clicks on one of Action Type buttons.\n\t\t\"\"\"\n\t\t# Prevent recurson\n\t\tif self._recursing : return\n\t\tself._recursing = True\n\t\t# Don't allow user to deactivate buttons - I'm using them as\n\t\t# radio button and you can't 'uncheck' radiobutton by clicking on it\n\t\tif not clicked_button.get_active():\n\t\t\tclicked_button.set_active(True)\n\t\t\tself._recursing = False\n\t\t\treturn\n\t\t\n\t\tcomponent = None\n\t\tfor c in self.c_buttons:\n\t\t\tb = self.c_buttons[c]\n\t\t\tif clicked_button == b:\n\t\t\t\tcomponent = c\n\t\t\telse:\n\t\t\t\tb.set_active(False)\n\t\tself._recursing = False\n\t\t\n\t\tstActionModes = self.builder.get_object(\"stActionModes\")\n\t\tcomponent.set_action(self._mode, self._action)\n\t\tif self._selected_component is not None:\n\t\t\tif self._selected_component != component:\n\t\t\t\tself._selected_component.hidden()\n\t\tself._selected_component = component\n\t\tself._selected_component.shown()\n\t\tstActionModes.set_visible_child(component.get_widget())\n\t\t\n\t\tstActionModes.show_all()\n\t\n\t\n\tdef force_page(self, component, remove_rest=False):\n\t\t\"\"\"\n\t\tForces action editor to display page with specified component.\n\t\tIf 'remove_rest' is True, removes all other pages.\n\t\t\n\t\tReturns 'component'\n\t\t\"\"\"\n\t\tif type(component) is str:\n\t\t\tcomponent = self.load_component(component)\n\t\t\treturn self.force_page(component, remove_rest)\n\t\t\n\t\tstActionModes = self.builder.get_object(\"stActionModes\")\n\t\tcomponent.load()\n\t\tif remove_rest:\n\t\t\tfor c in stActionModes.get_children():\n\t\t\t\tif c != component:\n\t\t\t\t\tstActionModes.remove(c)\n\t\t\n\t\tif component.get_widget() not in stActionModes.get_children():\n\t\t\tstActionModes.add(component.get_widget())\n\t\t\n\t\tcomponent.set_action(self._mode, self._action)\n\t\tif self._selected_component is not None:\n\t\t\tif self._selected_component != component:\n\t\t\t\tself._selected_component.hidden()\n\t\tself._selected_component = component\n\t\tself._selected_component.shown()\n\t\tstActionModes.set_visible_child(component.get_widget())\n\t\tstActionModes.show_all()\n\t\t\n\t\treturn component\n\t\n\t\n\tdef get_name(self):\n\t\t\"\"\" Returns action name as set in editor entry \"\"\"\n\t\tentName = self.builder.get_object(\"entName\")\n\t\treturn entName.get_text().strip(\" \\t\")\n\t\n\t\n\tdef get_current_page(self):\n\t\t\"\"\" Returns currently displayed page (component) \"\"\"\n\t\treturn self._selected_component\n\t\n\t\n\tdef _set_title(self):\n\t\t\"\"\" Copies title from text entry into action instance \"\"\"\n\t\tentName = self.builder.get_object(\"entName\")\n\t\tname = entName.get_text().strip(\" \\t\\r\\n\")\n\t\tif len(name) < 1:\n\t\t\tself._action.name = None\n\t\telif not self._action:\n\t\t\tself._action = NameModifier(name, self._action)\n\t\telse:\n\t\t\t#print(\">>>\", \"_set_title\", self._action, entName)\n\t\t\tself._action.name = name\n\t\n\t\n\tdef blink_widget(self, name, time=500):\n\t\tGROUPS = {\n\t\t\t'cbBallMode': ('cbBallMode', 'lblFriction', 'sclFriction', 'btClearFriction')\n\t\t}\n\t\t\n\t\tdef blink(widgets, count):\n\t\t\tcount = count - 1\n\t\t\tfor widget in widgets:\n\t\t\t\twidget.set_opacity(1.0 if count % 2 == 0 else 0.1)\n\t\t\tif count > 0:\n\t\t\t\tGLib.timeout_add(time, blink, widgets, count)\n\t\t\n\t\tif name in GROUPS:\n\t\t\tblink([self.builder.get_object(x) for x in GROUPS[name]], 7)\n\t\telse:\n\t\t\tblink([self.builder.get_object(name)], 7)\n\t\n\t\n\tdef hide_modifiers(self):\n\t\t\"\"\" Hides (and disables) all modifiers \"\"\"\n\t\tself.set_modifiers_enabled(False)\n\t\tself.builder.get_object(\"exMore\").set_visible(False)\n\t\n\t\n\tdef hide_advanced_settings(self):\n\t\t\"\"\"\n\t\tHides entire 'Advanced Settings' expander.\n\t\t\"\"\"\n\t\tself.builder.get_object(\"exMore\").set_visible(False)\n\t\tself.builder.get_object(\"rvMore\").set_visible(False)\n\t\n\t\n\tdef hide_modeshift(self):\n\t\t\"\"\"\n\t\tHides Mode Shift button.\n\t\tUsed when displaying ActionEditor from ModeshiftEditor\n\t\t\"\"\"\n\t\tself.builder.get_object(\"btModeshift\").set_visible(False)\n\t\n\t\n\tdef hide_macro(self):\n\t\t\"\"\"\n\t\tHides Macro button.\n\t\tUsed when editing macro of pad/stick bindings.\n\t\t\"\"\"\n\t\tself.builder.get_object(\"btMacro\").set_visible(False)\n\t\n\t\n\tdef hide_ring(self):\n\t\t\"\"\"\n\t\tHides Ring Bindings button.\n\t\tUsed when editing anything but pad.\n\t\t\"\"\"\n\t\tself.builder.get_object(\"btInnerRing\").set_visible(False)\n\t\n\t\n\tdef hide_action_buttons(self):\n\t\t\"\"\" Hides action buttons, effectivelly disallowing user to change action type \"\"\"\n\t\tfor x in (\"lblActionType\", \"vbActionButtons\"):\n\t\t\tself.builder.get_object(x).set_visible(False)\n\t\tself.hide_modeshift()\n\t\tself.hide_macro()\n\t\tself.hide_ring()\n\t\n\t\n\tdef hide_action_str(self):\n\t\t\"\"\" Hides bottom part with action displayed as string \"\"\"\n\t\tself.builder.get_object(\"vbActionStr\").set_visible(False)\n\t\tself.builder.get_object(\"grEditor\").set_property(\"margin-bottom\", 30)\n\t\n\t\n\tdef hide_editor(self):\n\t\t\"\"\" Hides everything but action buttons and action name field \"\"\"\n\t\tself.builder.get_object(\"stActionModes\").set_visible(False)\n\t\tself.hide_action_str()\n\t\tself.hide_modeshift()\n\t\tself.hide_macro()\n\t\tself.hide_ring()\n\t\n\t\n\tdef hide_name(self):\n\t\t\"\"\"\n\t\tHides (and clears) name field.\n\t\tUsed when displaying ActionEditor from MacroEditor\n\t\t\"\"\"\n\t\tself.builder.get_object(\"lblName\").set_visible(False)\n\t\tself.builder.get_object(\"entName\").set_visible(False)\n\t\tself.builder.get_object(\"entName\").set_text(\"\")\n\t\n\t\n\tdef hide_clear(self):\n\t\t\"\"\" Hides clear buttton \"\"\"\n\t\tself.builder.get_object(\"btClear\").set_visible(False)\n\t\n\t\n\tdef on_btClearRotation_clicked(self, *a):\n\t\tself.builder.get_object(\"sclRotation\").set_value(0.0)\n\t\n\t\n\tdef on_btClearSens_clicked(self, source, *a):\n\t\ti = 0\n\t\tfor scale, label, button, checkbox in self.sens_widgets:\n\t\t\tif source == button:\n\t\t\t\tscale.set_value(self.sens_defaults[i])\n\t\t\t\ti += 1\n\t\n\t\n\tdef on_btClearFeedback_clicked(self, source, *a):\n\t\tfor scale, label, button, default in self.feedback_widgets:\n\t\t\tif source == button:\n\t\t\t\tscale.set_value(default)\n\t\n\t\n\tdef on_btClearSmoothing_clicked(self, source, *a):\n\t\tfor label, scale, button, default in self.smoothing_widgets:\n\t\t\tif source == button:\n\t\t\t\tscale.set_value(default)\n\t\n\t\n\tdef on_btClearDeadzone_clicked(self, source, *a):\n\t\tfor label, scale, button, default in self.deadzone_widgets:\n\t\t\tif source == button:\n\t\t\t\tscale.set_value(default)\n\t\n\t\n\tdef on_btClear_clicked(self, *a):\n\t\t\"\"\" Handler for clear button \"\"\"\n\t\taction = NoAction()\n\t\tif self.ac_callback is not None:\n\t\t\tself.ac_callback(self.id, action)\n\t\tself.close()\n\t\n\t\n\tdef on_btOK_clicked(self, *a):\n\t\t\"\"\" Handler for OK button \"\"\"\n\t\tif self.ac_callback is not None:\n\t\t\tself._set_title()\n\t\t\tif self._mode == ActionEditor.AEC_MENUITEM:\n\t\t\t\tself.ac_callback(self.id, self)\n\t\t\telse:\n\t\t\t\ta = self.generate_modifiers(self._action, self._selected_component.NAME==\"custom\")\n\t\t\t\tself.ac_callback(self.id, a)\n\t\t\t\tself.ac_callback = None\n\t\t\tif self._selected_component:\n\t\t\t\tself._selected_component.on_ok(a)\n\t\tself.close()\n\t\n\t\n\tdef on_btModeshift_clicked(self, *a):\n\t\t\"\"\" Convert current action into modeshift and send it to ModeshiftEditor \"\"\"\n\t\te = ModeshiftEditor(self.app, self.ac_callback)\n\t\taction = ModeModifier(self.generate_modifiers(self._action, self._selected_component.NAME==\"custom\"))\n\t\te.set_input(self.id, action, mode=self._mode)\n\t\tself.send_added_widget(e)\n\t\tself.close()\n\t\te.show(self.get_transient_for())\n\t\n\t\n\tdef on_btMacro_clicked(self, *a):\n\t\t\"\"\" Convert current action into macro and send it to MacroEditor \"\"\"\n\t\te = MacroEditor(self.app, self.ac_callback)\n\t\taction = Macro(self.generate_modifiers(self._action, self._selected_component.NAME==\"custom\"))\n\t\te.set_input(self.id, action, mode=self._mode)\n\t\tself.send_added_widget(e)\n\t\tself.close()\n\t\te.show(self.get_transient_for())\n\t\n\t\n\tdef on_btInnerRing_clicked(self, *a):\n\t\t\"\"\" Convert current action into ring bindings and send it to RingEditor \"\"\"\n\t\te = RingEditor(self.app, self.ac_callback)\n\t\taction = RingAction(self.generate_modifiers(self._action, self._selected_component.NAME==\"custom\"))\n\t\te.set_input(self.id, action, mode=self._mode)\n\t\tself.send_added_widget(e)\n\t\tself.close()\n\t\te.show(self.get_transient_for())\n\t\n\t\n\tdef on_exMore_activate(self, ex, *a):\n\t\trvMore = self.builder.get_object(\"rvMore\")\n\t\trvMore.set_reveal_child(not ex.get_expanded())\n\n\n\tdef update_modifiers(self, *a):\n\t\t\"\"\"\n\t\tCalled when sensitivity, feedback or other modifier setting changes.\n\t\t\"\"\"\n\t\tif self._recursing : return\n\t\tcbRequireClick = self.builder.get_object(\"cbRequireClick\")\n\t\tcbFeedbackSide = self.builder.get_object(\"cbFeedbackSide\")\n\t\tcbFeedback = self.builder.get_object(\"cbFeedback\")\n\t\tgrFeedback = self.builder.get_object(\"grFeedback\")\n\t\tcbDeadzone = self.builder.get_object(\"cbDeadzone\")\n\t\tcbDeadzoneMode = self.builder.get_object(\"cbDeadzoneMode\")\n\t\tcbSmoothing = self.builder.get_object(\"cbSmoothing\")\n\t\trvSmoothing = self.builder.get_object(\"rvSmoothing\")\n\t\tsclRotation = self.builder.get_object(\"sclRotation\")\n\t\tsclFriction = self.builder.get_object(\"sclFriction\")\n\t\tcbBallMode = self.builder.get_object(\"cbBallMode\")\n\t\tcbOSD = self.builder.get_object(\"cbOSD\")\n\t\tset_action = False\n\t\t\n\t\t# Friction\n\t\tif not self.builder.get_object(\"cbBallMode\").get_active():\n\t\t\tfriction = -1\n\t\telif sclFriction.get_value() == 0:\n\t\t\tfriction = 0\n\t\telse:\n\t\t\tfriction = ((10.0 ** sclFriction.get_value()) / 1000.0)\n\t\tif self.friction != friction:\n\t\t\tself.friction = friction\n\t\t\tset_action = True\n\t\t\n\t\t# Sensitivity\n\t\tfor i in range(0, len(self.sens)):\n\t\t\ttarget = self.sens_widgets[i][0].get_value()\n\t\t\tif self.sens_widgets[i][3].get_active():\n\t\t\t\ttarget = -target\n\t\t\tif self.sens[i] != target:\n\t\t\t\tself.sens[i] = target\n\t\t\t\tset_action = True\n\t\t\n\t\t# Feedback\n\t\tif cbFeedback.get_active():\n\t\t\tfeedback_position = FEEDBACK_SIDES[cbFeedbackSide.get_active()]\n\t\telse:\n\t\t\tfeedback_position = None\n\t\tif self.feedback_position != feedback_position:\n\t\t\tself.feedback_position = feedback_position\n\t\t\tset_action = True\n\t\t\n\t\tfor i in range(0, len(self.feedback)):\n\t\t\tif self.feedback[i] != self.feedback_widgets[i][0].get_value():\n\t\t\t\tself.feedback[i] = self.feedback_widgets[i][0].get_value()\n\t\t\t\tset_action = True\n\t\t\n\t\tfor i in range(0, len(self.feedback)):\n\t\t\tif self.feedback[i] != self.feedback_widgets[i][0].get_value():\n\t\t\t\tself.feedback[i] = self.feedback_widgets[i][0].get_value()\n\t\t\t\tset_action = True\n\t\t\n\t\t# Deadzone\n\t\tmode = (DEADZONE_MODES[cbDeadzoneMode.get_active()]\n\t\t\t\t\tif cbDeadzone.get_active() else None)\n\t\tif self.deadzone_mode != mode:\n\t\t\tself.deadzone_mode = mode\n\t\t\tset_action = True\n\t\t\n\t\tfor i in range(0, len(self.deadzone)):\n\t\t\tif self.deadzone[i] != self.deadzone_widgets[i][1].get_value():\n\t\t\t\tself.deadzone[i] = self.deadzone_widgets[i][1].get_value()\n\t\t\t\tset_action = True\n\t\t\n\t\t\n\t\t# Smoothing\n\t\tif cbSmoothing.get_active():\n\t\t\tsmoothing = (\n\t\t\t\tint(self.smoothing_widgets[0][1].get_value()),\n\t\t\t\tself.smoothing_widgets[1][1].get_value(),\n\t\t\t\tint(self.smoothing_widgets[2][1].get_value()),\n\t\t\t)\n\t\telse:\n\t\t\tsmoothing = None\n\t\tif self.smoothing != smoothing:\n\t\t\tself.smoothing = smoothing\n\t\t\tset_action = True\n\t\t\n\t\t\n\t\t# Rest\n\t\tif self.click is not None:\n\t\t\tif cbRequireClick.get_active() != self.click:\n\t\t\t\tself.click = cbRequireClick.get_active()\n\t\t\t\tset_action = True\n\t\t\n\t\tif self.osd is not None:\n\t\t\tif cbOSD.get_active() != self.osd:\n\t\t\t\tself.osd = cbOSD.get_active()\n\t\t\t\tset_action = True\n\t\t\n\t\tif self.rotation_angle != sclRotation.get_value():\n\t\t\tself.rotation_angle = sclRotation.get_value()\n\t\t\tset_action = True\n\t\t\n\t\tif set_action:\n\t\t\tself.set_action(self._action)\n\t\t\tself._selected_component.modifier_updated()\n\t\n\t\n\tdef generate_modifiers(self, action, from_custom=False):\n\t\t\"\"\"\n\t\tReturns Action with all modifiers from UI applied.\n\t\t\"\"\"\n\t\tif not self._modifiers_enabled and not from_custom:\n\t\t\t# Editing in custom aciton dialog, don't meddle with that\n\t\t\treturn action\n\t\t\n\t\tif isinstance(action, ModeModifier):\n\t\t\targs = []\n\t\t\tfor k in action.mods:\n\t\t\t\tif action.mods[k] is not None:\n\t\t\t\t\targs += [ k, self.generate_modifiers(ActionEditor.strip_modifiers(action.mods[k])) ]\n\t\t\tif action.default:\n\t\t\t\targs += [ self.generate_modifiers(ActionEditor.strip_modifiers(action.default)) ]\n\t\t\treturn ModeModifier(*args)\n\t\t\n\t\tcm = action.get_compatible_modifiers()\n\t\t\n\t\tif (cm & Action.MOD_BALL) != 0:\n\t\t\tif self.friction >= 0:\n\t\t\t\taction = BallModifier(round(self.friction, 3), action)\n\t\t\n\t\tif (cm & Action.MOD_SENSITIVITY) != 0:\n\t\t\t# Strip 1.0's from sensitivity values\n\t\t\tsens = [] + self.sens\n\t\t\twhile len(sens) > 0 and sens[-1] == 1.0:\n\t\t\t\tsens = sens[0:-1]\n\t\t\t\n\t\t\tif len(sens) > 0:\n\t\t\t\t# Build arguments\n\t\t\t\tsens.append(action)\n\t\t\t\t# Create modifier\n\t\t\t\taction = SensitivityModifier(*sens)\n\t\t\n\t\tif (cm & Action.MOD_FEEDBACK) != 0:\n\t\t\tif self.feedback_position != None:\n\t\t\t\t# Strip defaults from feedback values\n\t\t\t\tfeedback = [] + self.feedback\n\t\t\t\twhile len(feedback) > 0 and feedback[-1] == self.feedback_widgets[len(feedback)-1][-1]:\n\t\t\t\t\tfeedback = feedback[0:-1]\n\t\t\t\t\n\t\t\t\tcbFeedbackSide = self.builder.get_object(\"cbFeedbackSide\")\n\t\t\t\tcbFeedback = self.builder.get_object(\"cbFeedback\")\n\t\t\t\tgrFeedback = self.builder.get_object(\"grFeedback\")\n\t\t\t\tif from_custom or (cbFeedback.get_active() and grFeedback.get_sensitive()):\n\t\t\t\t\t# Build FeedbackModifier arguments\n\t\t\t\t\tfeedback = [ FEEDBACK_SIDES[cbFeedbackSide.get_active()] ] + feedback\n\t\t\t\t\tfeedback += [ action ]\n\t\t\t\t\t# Create modifier\n\t\t\t\t\taction = FeedbackModifier(*feedback)\n\t\t\n\t\tif (cm & Action.MOD_SMOOTH) != 0:\n\t\t\tif self.smoothing != None:\n\t\t\t\taction = SmoothModifier(*( list(self.smoothing) + [ action ]))\n\t\t\n\t\tif (cm & Action.MOD_DEADZONE) != 0:\n\t\t\tif self.deadzone_mode is not None:\n\t\t\t\taction = DeadzoneModifier(self.deadzone_mode, self.deadzone[0], self.deadzone[1], action)\n\t\t\n\t\tif (cm & Action.MOD_ROTATE) != 0:\n\t\t\tif self.rotation_angle != 0.0:\n\t\t\t\taction = RotateInputModifier(self.rotation_angle, action)\n\t\t\n\t\tif (cm & Action.MOD_OSD) != 0:\n\t\t\tif self.osd:\n\t\t\t\taction = OSDAction(action)\n\t\t\n\t\tif (cm & Action.MOD_CLICK) != 0:\n\t\t\tif self.click:\n\t\t\t\taction = ClickModifier(action)\n\t\t\n\t\treturn action\n\t\n\t@staticmethod\n\tdef is_editable_modifier(action):\n\t\t\"\"\"\n\t\tReturns True if provided action is instance of modifier that\n\t\tActionEditor can display and edit.\n\t\tReturns False for everything else, even if it is instalce of Modifier\n\t\tsubclass.\n\t\t\"\"\"\n\t\tif isinstance(action, (ClickModifier, SensitivityModifier,\n\t\t\t\tDeadzoneModifier, FeedbackModifier, RotateInputModifier,\n\t\t\t\tSmoothModifier, BallModifier)):\n\t\t\treturn True\n\t\tif isinstance(action, OSDAction):\n\t\t\tif action.action is not None:\n\t\t\t\treturn True\n\t\treturn False\n\t\n\t\n\t@staticmethod\n\tdef strip_modifiers(action):\n\t\t\"\"\"\n\t\tReturns action stripped of all modifiers that are editable by editor.\n\t\t\"\"\"\n\t\twhile action:\n\t\t\tif ActionEditor.is_editable_modifier(action):\n\t\t\t\taction = action.action\n\t\t\telse:\n\t\t\t\treturn action\n\t\treturn action\n\t\n\t\n\tdef load_modifiers(self, action, index=-1):\n\t\t\"\"\"\n\t\tParses action for modifiers and updates UI accordingly.\n\t\tReturns action without parsed modifiers.\n\t\t\"\"\"\n\t\tcbRequireClick = self.builder.get_object(\"cbRequireClick\")\n\t\tsclRotation = self.builder.get_object(\"sclRotation\")\n\t\tcbOSD = self.builder.get_object(\"cbOSD\")\n\t\t\n\t\twhile ActionEditor.is_editable_modifier(action):\n\t\t\tif isinstance(action, RotateInputModifier):\n\t\t\t\tself.rotation_angle = action.angle\n\t\t\t\taction = action.action\n\t\t\tif isinstance(action, OSDAction):\n\t\t\t\tself.osd = True\n\t\t\t\taction = action.action\n\t\t\tif isinstance(action, ClickModifier):\n\t\t\t\tself.click = True\n\t\t\t\taction = action.action\n\t\t\tif isinstance(action, FeedbackModifier):\n\t\t\t\tself.feedback_position = action.haptic.get_position()\n\t\t\t\tself.feedback[0] = action.haptic.get_amplitude()\n\t\t\t\tself.feedback[1] = action.haptic.get_frequency()\n\t\t\t\tself.feedback[2] = action.haptic.get_period()\n\t\t\t\taction = action.action\n\t\t\tif isinstance(action, SmoothModifier):\n\t\t\t\tself.smoothing = ( action.level, action.multiplier, action.filter)\n\t\t\t\taction = action.action\n\t\t\tif isinstance(action, DeadzoneModifier):\n\t\t\t\tself.deadzone_mode = action.mode\n\t\t\t\tself.deadzone[0] = action.lower\n\t\t\t\tself.deadzone[1] = action.upper\n\t\t\t\taction = action.action\n\t\t\tif isinstance(action, SensitivityModifier):\n\t\t\t\tif index < 0:\n\t\t\t\t\tfor i in range(0, len(self.sens)):\n\t\t\t\t\t\tself.sens[i] = action.speeds[i]\n\t\t\t\telse:\n\t\t\t\t\tself.sens[index] = action.speeds[0]\n\t\t\t\taction = action.action\n\t\t\tif isinstance(action, BallModifier):\n\t\t\t\tself.friction = action.friction\n\t\t\t\taction = action.action\n\t\t\n\t\tself._recursing = True\n\t\tcbRequireClick.set_active(self.click)\n\t\tcbOSD.set_active(self.osd)\n\t\tsclRotation.set_value(self.rotation_angle)\n\t\tfor i in range(0, len(self.sens)):\n\t\t\tself.sens_widgets[i][3].set_active(self.sens[i] < 0)\n\t\t\tself.sens_widgets[i][0].set_value(abs(self.sens[i]))\n\t\t# Feedback\n\t\tcbFeedbackSide = self.builder.get_object(\"cbFeedbackSide\")\n\t\tlblFeedbackSide = self.builder.get_object(\"lblFeedbackSide\")\n\t\tif self.feedback_position != None:\n\t\t\tcbFeedback = self.builder.get_object(\"cbFeedback\")\n\t\t\tcbFeedbackSide.set_active(FEEDBACK_SIDES.index(self.feedback_position))\n\t\t\tcbFeedback.set_active(True)\n\t\t\tfor i in range(0, len(self.feedback)):\n\t\t\t\tself.feedback_widgets[i][0].set_value(self.feedback[i])\n\t\tfor grp in self.feedback_widgets:\n\t\t\tfor w in grp[0:-1]:\n\t\t\t\tw.set_sensitive(self.feedback_position is not None)\n\t\tlblFeedbackSide.set_sensitive(self.feedback_position is not None)\n\t\tcbFeedbackSide.set_sensitive(self.feedback_position is not None)\n\t\t\n\t\t# Smoothing\n\t\tcbSmoothing = self.builder.get_object(\"cbSmoothing\")\n\t\tif self.smoothing:\n\t\t\tcbSmoothing.set_active(True)\n\t\t\tfor i in range(0, len(self.smoothing_widgets)):\n\t\t\t\tself.smoothing_widgets[i][1].set_value(self.smoothing[i])\n\t\tfor grp in self.smoothing_widgets:\n\t\t\tfor w in grp[0:-1]:\n\t\t\t\tw.set_sensitive(cbSmoothing.get_active())\n\t\t\n\t\t# Ball\n\t\tsclFriction = self.builder.get_object(\"sclFriction\")\n\t\tcbBallMode = self.builder.get_object(\"cbBallMode\")\n\t\tif self.friction < 0:\n\t\t\tcbBallMode.set_active(False)\n\t\telif self.friction == 0:\n\t\t\tcbBallMode.set_active(True)\n\t\t\tsclFriction.set_value(0)\n\t\telse:\n\t\t\tcbBallMode.set_active(True)\n\t\t\tsclFriction.set_value(math.log(self.friction * 1000.0, 10))\n\t\t\n\t\t# Deadzone\n\t\tcbDeadzoneMode = self.builder.get_object(\"cbDeadzoneMode\")\n\t\tlblDeadzoneMode = self.builder.get_object(\"lblDeadzoneMode\")\n\t\tif self.deadzone_mode is not None:\n\t\t\tcbDeadzone = self.builder.get_object(\"cbDeadzone\")\n\t\t\tcbDeadzone.set_active(True)\n\t\t\tcbDeadzoneMode.set_active(DEADZONE_MODES.index(self.deadzone_mode))\n\t\t\tfor i in range(0, len(self.deadzone)):\n\t\t\t\tself.deadzone_widgets[i][1].set_value(self.deadzone[i])\n\t\t\n\t\tfor grp in self.deadzone_widgets:\n\t\t\tfor w in grp[0:-1]:\n\t\t\t\tw.set_sensitive(self.deadzone_mode is not None)\n\t\tlblDeadzoneMode.set_sensitive(self.deadzone_mode is not None)\n\t\tcbDeadzoneMode.set_sensitive(self.deadzone_mode is not None)\n\t\t\n\t\tself._recursing = False\n\t\t\n\t\treturn action\n\t\n\t\n\tdef allow_first_page(self):\n\t\t\"\"\"\n\t\tAllows first page to be used\n\t\t\"\"\"\n\t\tself.first_page_allowed = True\n\t\n\t\n\tdef reset_active_component(self):\n\t\t\"\"\"\n\t\tForgets what component was selected so next call to set_action\n\t\tselects new one.\n\t\t\"\"\"\n\t\tself._selected_component = None\n\t\n\t\n\tdef set_action(self, action, from_custom=False):\n\t\t\"\"\"\n\t\tUpdates Action field(s) on bottom and recolors apropriate image area,\n\t\tif such area exists.\n\t\t\"\"\"\n\t\tentAction = self.builder.get_object(\"entAction\")\n\t\tcbPreview = self.builder.get_object(\"cbPreview\")\n\t\tbtOK = self.builder.get_object(\"btOK\")\n\t\t\n\t\t# Load modifiers and update UI if needed\n\t\taction = self.load_modifiers(action)\n\t\t\n\t\t# Check for InvalidAction and display error message if found\n\t\tif isinstance(action, InvalidAction):\n\t\t\tbtOK.set_sensitive(False)\n\t\t\tentAction.set_name(\"error\")\n\t\t\tentAction.set_text(str(action.error))\n\t\telse:\n\t\t\tentAction.set_name(\"entAction\")\n\t\t\tbtOK.set_sensitive(True)\n\t\t\tself._action = action\n\t\t\taction = self.generate_modifiers(action, from_custom)\n\t\t\t\n\t\t\tif hasattr(action, 'string') and \"\\n\" not in action.string:\n\t\t\t\t# Stuff generated by my special parser\n\t\t\t\tentAction.set_text(action.string)\n\t\t\telse:\n\t\t\t\t# Actions generated elsewhere\n\t\t\t\tentAction.set_text(action.to_string())\n\t\t\tself.enable_modifiers(self._action)\n\t\t\tself.enable_preview(self._action)\n\t\t\n\t\t# Send changed action into selected component\n\t\tif self._selected_component is None:\n\t\t\tfor component in reversed(sorted(self.components, key = lambda a : a.PRIORITY)):\n\t\t\t\tif (component.CTXS & self._mode) != 0:\n\t\t\t\t\tif component.handles(self._mode, ActionEditor.strip_modifiers(action)):\n\t\t\t\t\t\tself._selected_component = component\n\t\t\t\t\t\tbreak\n\t\t\tif isinstance(action, InvalidAction):\n\t\t\t\tc = self.load_component(\"custom\")\n\t\t\t\tif c in self.components and (self._mode & c.CTXS) != 0:\n\t\t\t\t\tself._selected_component = c\n\t\t\telif not action and self.first_page_allowed:\n\t\t\t\tself._selected_component = self.load_component(\"first_page\")\n\t\t\t\tstActionModes = self.builder.get_object(\"stActionModes\")\n\t\t\t\tself._selected_component.load()\n\t\t\t\tself._selected_component.shown()\n\t\t\t\tstActionModes.add(self._selected_component.get_widget())\n\t\t\t\tstActionModes.set_visible_child(self._selected_component.get_widget())\n\t\t\tif self._selected_component:\n\t\t\t\tif self._selected_component in self.c_buttons:\n\t\t\t\t\tself.c_buttons[self._selected_component].set_active(True)\n\t\t\tif isinstance(action, InvalidAction):\n\t\t\t\tself._selected_component.set_action(self._mode, action)\n\t\telif not self._selected_component.handles(self._mode, ActionEditor.strip_modifiers(action)):\n\t\t\tlog.warning(\"selected_component no longer handles edited action\")\n\t\t\tlog.warning(self._selected_component)\n\t\t\tlog.warning(ActionEditor.strip_modifiers(action).to_string())\n\t\t\tlog.warning(\"(%s)\", action.to_string())\n\t\t\n\t\tif cbPreview.get_sensitive() and cbPreview.get_active():\n\t\t\tself.apply_preview(action)\n\t\n\t\n\tdef apply_preview(self, action):\n\t\tif self._replaced_action is None:\n\t\t\tself._replaced_action = self.ac_callback(self.id, action, mark_changed=False)\n\t\telse:\n\t\t\tself.ac_callback(self.id, action, mark_changed=False)\n\t\n\t\n\tdef on_cbPreview_toggled(self, cb):\n\t\tif cb.get_active():\n\t\t\ta = self.generate_modifiers(self._action, self._selected_component.NAME==\"custom\")\n\t\t\tself.apply_preview(a)\n\t\telif self._replaced_action is not None:\n\t\t\tif self.ac_callback:\n\t\t\t\t# Is None if OK button handler was executed\n\t\t\t\tself.ac_callback(self.id, self._replaced_action, mark_changed=False)\n\t\t\tself._replaced_action = None\n\t\n\t\n\tdef enable_preview(self, action):\n\t\t\"\"\"\n\t\tEnables or disables and hides 'preview immediately' option, based on\n\t\tif currently selected action supports it.\n\t\t\"\"\"\n\t\tcbPreview = self.builder.get_object(\"cbPreview\")\n\t\t\n\t\tenabled = action.strip().get_previewable()\n\t\tcbPreview.set_sensitive(enabled)\n\t\n\t\n\tdef enable_modifiers(self, action):\n\t\t\"\"\"\n\t\tEnables or disables and hides modifier settings according to what\n\t\tis applicable for specified action AND what's allowed for current\n\t\teditor mode.\n\t\t\n\t\tUses value returned by action.get_compatible_modifiers.\n\t\t\"\"\"\n\t\tcm = action.get_compatible_modifiers() & ActionEditor.MODE_TO_MODS[self._mode]\n\t\t\n\t\t# Feedback\n\t\tgrFeedback = self.builder.get_object(\"grFeedback\")\n\t\tgrFeedback.set_sensitive((cm & Action.MOD_FEEDBACK) != 0)\n\t\t\n\t\t# Smoothing\n\t\tgrSmoothing = self.builder.get_object(\"grSmoothing\")\n\t\tgrSmoothing.set_sensitive((cm & Action.MOD_SMOOTH) != 0)\n\t\t\n\t\t# Deadzone\n\t\tgrDeadzone = self.builder.get_object(\"grDeadzone\")\n\t\tgrDeadzone.set_sensitive((cm & Action.MOD_DEADZONE) != 0)\n\t\t\n\t\t# Sensitivity\n\t\tgrSensitivity = self.builder.get_object(\"grSensitivity\")\n\t\tgrSensitivity.set_sensitive((cm & Action.MOD_SENSITIVITY) != 0)\n\t\tfor w in self.sens_widgets[2]:\n\t\t\tw.set_visible((cm & Action.MOD_SENS_Z) != 0)\n\t\t\n\t\t# Rotation\n\t\tfor w in (\"lblRotationHeader\", \"lblRotation\", \"sclRotation\", \"btClearRotation\"):\n\t\t\tself.builder.get_object(w).set_sensitive((cm & Action.MOD_ROTATE) != 0)\n\t\t\n\t\t# Click\n\t\tcbRequireClick = self.builder.get_object(\"cbRequireClick\")\n\t\tcbRequireClick.set_sensitive((cm & Action.MOD_CLICK) != 0)\n\t\t\n\t\t# Ball\n\t\tcbBallMode = self.builder.get_object(\"cbBallMode\")\n\t\tcbBallMode.set_sensitive((cm & Action.MOD_BALL) != 0)\n\t\tfor w in (\"sclFriction\", \"lblFriction\", \"btClearFriction\"):\n\t\t\tself.builder.get_object(w).set_sensitive(cbBallMode.get_active() and ((cm & Action.MOD_BALL) != 0))\n\t\tif cm & Action.MOD_BALL == 0:\n\t\t\tself.builder.get_object(\"cbBallMode\").set_active(False)\n\t\t\n\t\t# OSD\n\t\tcbOSD = self.builder.get_object(\"cbOSD\")\n\t\tcbOSD.set_sensitive(cm & Action.MOD_OSD != 0)\n\n\n\tdef set_sensitivity(self, x, y=1.0, z=1.0):\n\t\t\"\"\" Sets sensitivity for edited action \"\"\"\n\t\tself._recursing = True\n\t\txyz = [ x, y, z ]\n\t\tfor i in range(0, len(self.sens)):\n\t\t\tself.sens[i] = xyz[i]\n\t\t\tself.sens_widgets[i][3].set_active(self.sens[i] < 0)\n\t\t\tself.sens_widgets[i][0].set_value(abs(self.sens[i]))\n\t\tself._recursing = False\n\t\tself.set_action(self._action)\n\t\tself._selected_component.modifier_updated()\n\t\n\t\n\tdef get_sensitivity(self):\n\t\t\"\"\" Returns sensitivity currently set in editor \"\"\"\n\t\treturn tuple(self.sens)\n\t\n\t\n\tdef set_default_sensitivity(self, x, y=1.0, z=1.0):\n\t\t\"\"\"\n\t\tSets default sensitivity values and, if sensitivity\n\t\tis currently set to defaults, updates it to these values\n\t\t\"\"\"\n\t\txyz = x, y, z\n\t\tupdate = False\n\t\tself._recursing = True\n\t\tfor i in (0, 1, 2):\n\t\t\tif self.sens[i] == self.sens_defaults[i]:\n\t\t\t\tself.sens[i] = xyz[i]\n\t\t\t\tself.sens_widgets[i][0].set_value(xyz[i])\n\t\t\t\tupdate = True\n\t\t\tself.sens_defaults[i] = xyz[i]\n\t\tself._recursing = False\n\t\tif update:\n\t\t\tself.update_modifiers()\n\t\n\t\n\tdef get_mode(self):\n\t\treturn self._mode\n\t\n\t\n\tdef _set_mode(self, action, mode):\n\t\t\"\"\" Common part of editor setup \"\"\"\n\t\tself._mode = mode\n\t\t# Clear pages and 'action type' buttons\n\t\tentName = self.builder.get_object(\"entName\")\n\t\tvbActionButtons = self.builder.get_object(\"vbActionButtons\")\n\t\tstActionModes = self.builder.get_object(\"stActionModes\")\n\t\t\n\t\t# Go throgh list of components and display buttons that are usable\n\t\t# with this mode\n\t\tself.c_buttons = {}\n\t\tfor component in reversed(sorted(self.components, key = lambda a : a.PRIORITY)):\n\t\t\tif (mode & component.CTXS) != 0:\n\t\t\t\tb = Gtk.ToggleButton.new_with_label(component.get_button_title())\n\t\t\t\tvbActionButtons.pack_start(b, True, True, 2)\n\t\t\t\tb.connect('toggled', self.on_action_type_changed)\n\t\t\t\tself.c_buttons[component] = b\n\t\t\t\t\n\t\t\t\tcomponent.load()\n\t\t\t\tif component.get_widget() not in stActionModes.get_children():\n\t\t\t\t\tstActionModes.add(component.get_widget())\n\t\t\n\t\tif action.name is None:\n\t\t\tentName.set_text(\"\")\n\t\telse:\n\t\t\tentName.set_text(action.name)\n\t\tif vbActionButtons.get_visible():\n\t\t\tvbActionButtons.show_all()\n\t\n\tdef on_sclFFrequency_format_value(self, scale, value):\n\t\tif value == 1:\n\t\t\t# Special case\n\t\t\treturn \" %0.2fHz\" % (1.0/value,)\n\t\treturn \"%0.2fmHz\" % (100.0/value,)\n\t\n\t\n\tdef on_sclFriction_format_value(self, scale, value):\n\t\tif value <= 0:\n\t\t\treturn \"%0.3f\" % (0,)\n\t\telif value >= 6:\n\t\t\treturn \"%0.3f\" % (1000.00,)\n\t\telse:\n\t\t\treturn \"%0.3f\" % ((10.0**value)/1000.0)\n\t\n\t\n\tdef on_btClearFriction_clicked(self, *a):\n\t\tsclFriction = self.builder.get_object(\"sclFriction\")\n\t\tsclFriction.set_value(math.log(10 * 1000.0, 10))\n\t\n\t\n\tdef set_input(self, id, action, mode=None):\n\t\t\"\"\"\n\t\tSetups action editor for editing specified input.\n\t\tMode (buttton/axis/trigger...) is either provided or chosen based on id.\n\t\tAlso sets title, but that can be overriden by calling set_title after.\n\t\t\"\"\"\n\t\tself.id = id\n\t\tif id in SCButtons.__members__.values() or mode in (Action.AC_MENU, Action.AC_BUTTON):\n\t\t\tif id in PRESSABLE:\n\t\t\t\tself.set_title(_(\"%s Press\") % (nameof(id),))\n\t\t\telif id in SCButtons.__members__.values():\n\t\t\t\tself.set_title(nameof(id),)\n\t\t\tself._set_mode(action, mode or Action.AC_BUTTON)\n\t\t\tself.hide_modifiers()\n\t\t\tself.set_action(action)\n\t\telif id in TRIGGERS:\n\t\t\tself.set_title(_(\"%s Trigger\") % (id,))\n\t\t\tself._set_mode(action, mode or Action.AC_TRIGGER)\n\t\t\tself.set_action(action)\n\t\t\tself.hide_macro()\n\t\t\tself.hide_ring()\n\t\telif id in STICKS:\n\t\t\tif id == Profile.DPAD:\n\t\t\t\tself.set_title(_(\"DPAD\"))\n\t\t\telif id == Profile.RSTICK:\n\t\t\t\tself.set_title(_(\"Right Strick\"))\n\t\t\telif id == Profile.STICK:\n\t\t\t\tself.set_title(_(\"Stick\"))\n\t\t\telse:\n\t\t\t\traise ValueError(\"unknown id %s\" % (id, ))\n\t\t\tself._set_mode(action, mode or Action.AC_STICK)\n\t\t\tself.set_action(action)\n\t\t\tself.hide_macro()\n\t\t\tself.id = id\n\t\telif id in GYROS:\n\t\t\tself.set_title(_(\"Gyro\"))\n\t\t\tself._set_mode(action, mode or Action.AC_GYRO)\n\t\t\tself.set_action(action)\n\t\t\tself.hide_modeshift()\n\t\t\tself.hide_macro()\n\t\t\tself.hide_ring()\n\t\t\tself.id = Profile.GYRO\n\t\telif id in PADS:\n\t\t\tself._set_mode(action, mode or Action.AC_PAD)\n\t\t\tself.set_action(action)\n\t\t\tself.hide_macro()\n\t\t\tif id == Profile.LPAD:\n\t\t\t\tself.set_title(_(\"Left Pad\"))\n\t\t\telif id == Profile.RPAD:\n\t\t\t\tself.set_title(_(\"Right Pad\"))\n\t\t\telse:\n\t\t\t\tself.set_title(_(\"Touch Pad\"))\n\t\tif mode == Action.AC_OSK:\n\t\t\tself.hide_name()\n\t\t\tself.hide_modeshift()\n\t\t\tself.hide_macro()\n\t\t\tself.hide_ring()\n\t\t\t# self.hide_rotation()\n\t\telif mode == Action.AC_MENU:\n\t\t\tself.hide_modeshift()\n\t\t\tself.hide_macro()\n\t\t\tself.hide_ring()\n\t\n\t\n\tdef set_menu_item(self, item, title_for_name_label=None):\n\t\t\"\"\"\n\t\tSetups action editor in way that allows editing only action name.\n\t\t\n\t\tIn this mode, callback is called with editor instance instead of\n\t\tgenerated action as 2nd argument.\n\t\t\"\"\"\n\t\tentName = self.builder.get_object(\"entName\")\n\t\tself._mode = ActionEditor.AEC_MENUITEM\n\t\tif hasattr(item, \"label\") and item.label:\n\t\t\tentName.set_text(item.label)\n\t\telse:\n\t\t\tentName.set_text(\"\")\n\t\t# self.hide_osd()\n\t\tself.hide_action_buttons()\n\t\tself.hide_modifiers()\n\t\tself.set_action(NoAction())\n\t\tself.id = item.id\n\t\tif title_for_name_label:\n\t\t\tself.builder.get_object(\"lblName\").set_label(title_for_name_label)\n\t\n\t\n\tdef set_modifiers_enabled(self, enabled):\n\t\texMore = self.builder.get_object(\"exMore\")\n\t\trvMore = self.builder.get_object(\"rvMore\")\n\t\tif self._modifiers_enabled != enabled and not enabled:\n\t\t\texMore.set_expanded(False)\n\t\t\trvMore.set_reveal_child(False)\n\t\texMore.set_sensitive(enabled)\n\t\tself._modifiers_enabled = enabled\n\n"
  },
  {
    "path": "scc/gui/ae/__init__.py",
    "content": "#!/usr/bin/env python2\n\"\"\" ae - Action Editor components \"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action, NoAction, XYAction\nfrom scc.gui.editor import ComboSetter\nfrom scc.tools import ensure_size\n\nimport os, logging\nlog = logging.getLogger(\"AE\")\n\nclass AEComponent(ComboSetter):\n\tGLADE = None\n\tNAME = None\n\tPRIORITY = 0\n\t# Bit mask of contexes (Action.AC_BUTTON | Action.AC_TRIGGER...) that this\n\t# compoment can handle.\n\tCTXS = 0\n\t\n\tdef __init__(self, app, editor):\n\t\tself.app = app\n\t\tself.editor = editor\n\t\tself.loaded = False\n\t\n\t\n\tdef get_button_title(self):\n\t\traise Exception(\"Implement me!\")\n\t\n\t\n\t# TODO: Rename this to on_shown\n\tdef shown(self):\n\t\t\"\"\" Called after user switches TO page \"\"\"\n\t\tpass\n\t\n\t\n\t# TODO: Rename this to on_shown\n\tdef hidden(self):\n\t\t\"\"\" Called after user switches AWAY from page \"\"\"\n\t\tpass\n\t\n\t\n\tdef on_ok(self, action):\n\t\t\"\"\"\n\t\tCalled when user presses OK, after action is send to main window\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef load(self):\n\t\t\"\"\"\n\t\tPerforms whatever component needs to get loaded.\n\t\tCan be called multiple times without breaking anything, but returns\n\t\tTrue when called first time and then False every to signalize repeated\n\t\tcall.\n\t\t\"\"\"\n\t\tif self.loaded:\n\t\t\treturn False\n\t\tself.builder = Gtk.Builder()\n\t\tself.builder.add_from_file(os.path.join(self.app.gladepath, self.GLADE))\n\t\tself.widget = self.builder.get_object(self.NAME)\n\t\tself.builder.connect_signals(self)\n\t\tself.loaded = True\n\t\treturn True\n\t\n\t\n\tdef is_loaded(self):\n\t\treturn self.loaded\n\t\n\t\n\tdef handles(self, mode, action):\n\t\t\"\"\"\n\t\tReturns True if component can display and edit specified action.\n\t\tIf more than one component returns True from 'handles',\n\t\thigher PRIORITY is used\n\t\t\"\"\"\n\t\treturn False\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\t\"\"\"\n\t\tSetups component widgets to display currently set action.\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef modifier_updated(self):\n\t\t\"\"\"\n\t\tCalled when values of any modifier is changed.\n\t\t\"\"\"\n\t\tpass\n\t\n\t\n\tdef get_widget(self):\n\t\treturn self.widget\n\n\n\ndef describe_action(mode, cls, v):\n\t\"\"\"\n\tReturns action description with 'v' as parameter, unless unless v is None.\n\tReturns \"not set\" if v is None\n\t\"\"\"\n\tif v is None or type(v) in (int, float, str,):\n\t\treturn _('(not set)')\n\telif isinstance(v, Action):\n\t\tif not mode:\n\t\t\tdsc = v.describe(Action.AC_STICK if cls == XYAction else Action.AC_BUTTON)\n\t\telse:\n\t\t\tdsc = v.describe(mode)\n\n\t\tif \"\\n\" in dsc:\n\t\t\tdsc = \"<small>\" + \"\\n\".join(dsc.split(\"\\n\")[0:2]) + \"</small>\"\n\t\treturn dsc\n\telse:\n\t\treturn (cls(v)).describe(mode)\n"
  },
  {
    "path": "scc/gui/ae/axis.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Axis Component\n\nHandles specific XYActions\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import ButtonAction, MultiAction, NoAction\nfrom scc.actions import Action, AxisAction, MouseAction\nfrom scc.gui.ae import AEComponent, describe_action\nfrom scc.gui.area_to_action import action_to_area\nfrom scc.gui.simple_chooser import SimpleChooser\nfrom scc.gui.chooser import Chooser\n\nimport os, logging\nlog = logging.getLogger(\"AE.Axis\")\n\n__all__ = [ 'AxisComponent' ]\n\n\nclass AxisComponent(AEComponent, Chooser):\n\tGLADE = \"ae/axis.glade\"\n\tNAME = \"axis\"\n\tIMAGES = { \"axis\" : \"axistrigger.svg\" }\n\tCTXS = 0\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tChooser.__init__(self, app)\n\t\tself.full = None\n\t\n\t\n\tdef load(self):\n\t\tif not self.loaded:\n\t\t\tAEComponent.load(self)\n\t\t\tself.setup_image(grid_columns=2)\n\t\n\t\n\tdef area_action_selected(self, area, action):\n\t\tif area:\n\t\t\tself.set_active_area(area)\n\t\tif self.full:\n\t\t\taction = MultiAction(ButtonAction(None, self.full), action)\n\t\tself.editor.set_action(action)\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif self.handles(mode, action):\n\t\t\tif isinstance(action, MultiAction) and len(action.actions) == 2:\n\t\t\t\t# axis + button on fully pressed trigger\n\t\t\t\tself.full = action.actions[0].button2\n\t\t\t\tself.builder.get_object(\"lblFullPressed\").set_label(describe_action(Action.AC_BUTTON, ButtonAction, self.full))\n\t\t\t\taction = action.actions[1]\n\t\t\tarea = action_to_area(action)\n\t\t\tif area is not None:\n\t\t\t\tself.set_active_area(area)\n\t\t\t\treturn\n\t\tself.set_active_area(None)\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Trigger or Axis\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif isinstance(action, MultiAction) and len(action.actions) == 2:\n\t\t\t# Handles combination of axis + button on fully pressed trigger\n\t\t\tif not isinstance(action.actions[0], ButtonAction):\n\t\t\t\treturn False\n\t\t\taction = action.actions[1]\n\t\treturn isinstance(action, (AxisAction, MouseAction))\n"
  },
  {
    "path": "scc/gui/ae/axis_action.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Axis Component\n\nAssigns emulated axis to trigger\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gdk, GdkX11, GLib\nfrom ctypes import cast, POINTER\nfrom scc.actions import Action, NoAction, AxisAction, MouseAction\nfrom scc.actions import XYAction, RelXYAction\nfrom scc.actions import MultiAction, RelWinAreaAction, ButtonAction\nfrom scc.actions import AreaAction, WinAreaAction, RelAreaAction\nfrom scc.modifiers import BallModifier, CircularModifier\nfrom scc.uinput import Keys, Axes, Rels\nfrom scc.constants import SCButtons\nfrom scc.lib import xwrappers as X\nfrom scc.osd.timermanager import TimerManager\nfrom scc.osd.area import Area\nfrom scc.gui.parser import GuiActionParser, InvalidAction\nfrom scc.gui.simple_chooser import SimpleChooser\nfrom scc.gui.controller_widget import STICKS\nfrom scc.gui.ae import AEComponent\n\nimport os, logging, math\nlog = logging.getLogger(\"AE.AxisAction\")\n\n__all__ = [ 'AxisActionComponent' ]\n\n\nclass AxisActionComponent(AEComponent, TimerManager):\n\tGLADE = \"ae/axis_action.glade\"\n\tNAME = \"axis_action\"\n\tCTXS = Action.AC_STICK | Action.AC_PAD\n\tPRIORITY = 3\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tTimerManager.__init__(self)\n\t\tself._recursing = False\n\t\tself.relative_area = False\n\t\tself.osd_area_instance = None\n\t\tself.on_wayland = False\n\t\tself.circular_axis = MouseAction(Rels.REL_WHEEL)\n\t\tself.circular_buttons = [ None, None ]\n\t\tself.button = None\n\t\tself.parser = GuiActionParser()\n\t\n\t\n\tdef load(self):\n\t\tif self.loaded : return\n\t\tAEComponent.load(self)\n\t\tcbAreaType = self.builder.get_object(\"cbAreaType\")\n\t\tcbAreaType.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == \"-\" )\n\t\tself.on_wayland = \"WAYLAND_DISPLAY\" in os.environ or not isinstance(Gdk.Display.get_default(), GdkX11.X11Display)\n\t\tif self.on_wayland:\n\t\t\tself.builder.get_object(\"lblArea\").set_text(_(\"Note: Mouse Region option is not available with Wayland-based display server\"))\n\t\t\tself.builder.get_object(\"grArea\").set_sensitive(False)\n\t\t\n\t\t# Remove options that are not applicable to currently editted input\n\t\tif self.editor.get_id() in STICKS:\n\t\t\t# Remove \"Mouse Region\", \"Mouse\" and \"Mouse (Emulate Stick)\" options\n\t\t\t# when editing stick bindings\n\t\t\tcb = self.builder.get_object(\"cbAxisOutput\")\n\t\t\tfor row in cb.get_model():\n\t\t\t\tif row[2] in (\"wheel_pad\", \"area\", \"mouse\", \"mouse_pad\"):\n\t\t\t\t\tcb.get_model().remove(row.iter)\n\t\telse:\n\t\t\t# Remove \"Mouse\" option when editing pads\n\t\t\t# (it's effectivelly same as Trackpad)\n\t\t\tcb = self.builder.get_object(\"cbAxisOutput\")\n\t\t\tfor row in cb.get_model():\n\t\t\t\tif row[2] in (\"wheel_stick\", \"mouse_stick\"):\n\t\t\t\t\tcb.get_model().remove(row.iter)\n\t\n\t\n\tdef hidden(self):\n\t\tself.update_osd_area(None)\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif self.handles(mode, action):\n\t\t\tcb = self.builder.get_object(\"cbAxisOutput\")\n\t\t\tif isinstance(action, AreaAction):\n\t\t\t\tself.load_area_action(action)\n\t\t\t\tself.set_cb(cb, \"area\", 2)\n\t\t\t\tself.update_osd_area(action)\n\t\t\t\treturn\n\t\t\tself.update_osd_area(None)\n\t\t\tif isinstance(action, MouseAction):\n\t\t\t\tself.load_mouse_action(action)\n\t\t\telif isinstance(action, CircularModifier):\n\t\t\t\tself.load_circular_action(action)\n\t\t\telif isinstance(action, ButtonAction):\n\t\t\t\tself.load_button_action(action)\n\t\t\telif isinstance(action, XYAction):\n\t\t\t\tif self.editor.friction > 0:\n\t\t\t\t\tself.load_mouse_action(action)\n\t\t\t\telse:\n\t\t\t\t\tp = [ None, None ]\n\t\t\t\t\tfor x in (0, 1):\n\t\t\t\t\t\tif len(action.actions[0].strip().parameters) >= x:\n\t\t\t\t\t\t\tif len(action.actions[x].strip().parameters) > 0:\n\t\t\t\t\t\t\t\tp[x] = action.actions[x].strip().parameters[0]\n\t\t\t\t\tif p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:\n\t\t\t\t\t\tself.set_cb(cb, \"lstick\", 2)\n\t\t\t\t\telif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:\n\t\t\t\t\t\tif isinstance(action, RelXYAction):\n\t\t\t\t\t\t\tself.set_cb(cb, \"rstick_rel\", 2)\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tself.set_cb(cb, \"rstick\", 2)\n\t\t\t\t\telif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:\n\t\t\t\t\t\tself.set_cb(cb, \"wheel_pad\", 2)\n\t\t\t\t\t\tself.set_cb(cb, \"wheel_stick\", 2)\n\t\t\telse:\n\t\t\t\tself.set_cb(cb, \"none\", 2)\n\t\n\t\n\tdef update_osd_area(self, action):\n\t\t\"\"\" Updates preview area displayed on screen \"\"\"\n\t\tif action:\n\t\t\tif self.osd_area_instance is None:\n\t\t\t\tif self.on_wayland:\n\t\t\t\t\t# Cannot display preview with non-X11 backends\n\t\t\t\t\treturn\n\t\t\t\tself.osd_area_instance = Area()\n\t\t\t\tself.osd_area_instance.show()\n\t\t\taction.update_osd_area(self.osd_area_instance, FakeMapper(self.editor))\n\t\t\tself.timer(\"area\", 0.5, self.update_osd_area, action)\n\t\telif self.osd_area_instance:\n\t\t\tself.osd_area_instance.quit()\n\t\t\tself.osd_area_instance = None\n\t\t\tself.cancel_timer(\"area\")\n\t\n\t\n\tdef load_circular_action(self, action):\n\t\tcbAxisOutput = self.builder.get_object(\"cbAxisOutput\")\n\t\tbtCircularAxis = self.builder.get_object(\"btCircularAxis\")\n\t\tbtCircularButton0 = self.builder.get_object(\"btCircularButton0\")\n\t\tbtCircularButton1 = self.builder.get_object(\"btCircularButton1\")\n\t\t\n\t\t# Turn action into list of subactions (even if it's just single action)\n\t\tif isinstance(action.action, MultiAction):\n\t\t\tactions = action.action.actions\n\t\telse:\n\t\t\tactions = [ action.action ]\n\t\t\n\t\t# Parse that list\n\t\tself.circular_axis, self.circular_buttons = NoAction(), [ None, None ]\n\t\tfor action in actions:\n\t\t\tif isinstance(action, ButtonAction):\n\t\t\t\tself.circular_buttons = [ action.button, action.button2 ]\n\t\t\telse:\n\t\t\t\tself.circular_axis = action\n\t\t\n\t\t# Set labels\n\t\tb0, b1 = self.circular_buttons\n\t\tbtCircularButton0.set_label(ButtonAction.describe_button(b0))\n\t\tbtCircularButton1.set_label(ButtonAction.describe_button(b1))\n\t\tbtCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD))\n\t\t\n\t\tself.set_cb(cbAxisOutput, \"circular\", 2)\n\t\n\t\n\tdef load_button_action(self, action):\n\t\tself.button = action\n\t\tcbAxisOutput = self.builder.get_object(\"cbAxisOutput\")\n\t\tbtSingleButton = self.builder.get_object(\"btSingleButton\")\n\t\tbtSingleButton.set_label(self.button.describe(Action.AC_PAD))\n\t\tself.set_cb(cbAxisOutput, \"button\", 2)\n\t\n\t\n\tdef load_mouse_action(self, action):\n\t\tcbMouseOutput = self.builder.get_object(\"cbMouseOutput\")\n\t\tcbAxisOutput = self.builder.get_object(\"cbAxisOutput\")\n\t\tself._recursing = True\n\t\tif isinstance(action, MouseAction):\n\t\t\tself.set_cb(cbMouseOutput, \"mouse\", 1)\n\t\t\tself.set_cb(cbAxisOutput, \"mouse\", 2)\n\t\telif isinstance(action, XYAction):\n\t\t\tif isinstance(action.x, AxisAction):\n\t\t\t\tif action.x.parameters[0] == Axes.ABS_X:\n\t\t\t\t\tself.set_cb(cbMouseOutput, \"left\", 1)\n\t\t\t\telse:\n\t\t\t\t\tself.set_cb(cbMouseOutput, \"right\", 1)\n\t\t\t\tself.set_cb(cbAxisOutput, \"mouse\", 2)\n\t\t\telif isinstance(action.x, MouseAction):\n\t\t\t\tif self.editor.get_id() in STICKS:\n\t\t\t\t\tself.set_cb(cbAxisOutput, \"wheel_stick\", 2)\n\t\t\t\telse:\n\t\t\t\t\tself.set_cb(cbAxisOutput, \"wheel_pad\", 2)\n\t\tself._recursing = False\n\t\n\t\n\tdef load_area_action(self, action):\n\t\t\"\"\"\n\t\tLoad AreaAction values into UI.\n\t\t\"\"\"\n\t\tcbAreaType = self.builder.get_object(\"cbAreaType\")\n\t\t\n\t\tx1, y1, x2, y2 = action.coords\n\t\tself.relative_area = False\n\t\tif isinstance(action, RelAreaAction):\n\t\t\tkey = \"screensize\"\n\t\t\tself.relative_area = True\n\t\t\tx1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0\n\t\telif isinstance(action, RelWinAreaAction):\n\t\t\tkey = \"windowsize\"\n\t\t\tself.relative_area = True\n\t\t\tx1, y1, x2, y2 = x1 * 100.0, y1 * 100.0, x2 * 100.0, y2 * 100.0\n\t\telse:\n\t\t\tt1 = \"1\" if x1 < 0 and x2 < 0 else \"0\"\n\t\t\tt2 = \"1\" if y1 < 0 and y2 < 0 else \"0\"\n\t\t\tx1, y1, x2, y2 = abs(x1), abs(y1), abs(x2), abs(y2)\n\t\t\tif x2 < x1 : x1, x2 = x2, x1\n\t\t\tif y2 < y1 : y1, y2 = y2, y1\n\t\t\tif isinstance(action, WinAreaAction):\n\t\t\t\tkey = \"window-%s%s\" % (t1, t2)\n\t\t\telse:\n\t\t\t\tkey = \"screen-%s%s\" % (t1, t2)\n\t\t\n\t\tself._recursing = True\n\t\tself.builder.get_object(\"sbAreaX1\").set_value(x1)\n\t\tself.builder.get_object(\"sbAreaY1\").set_value(y1)\n\t\tself.builder.get_object(\"sbAreaX2\").set_value(x2)\n\t\tself.builder.get_object(\"sbAreaY2\").set_value(y2)\n\t\tself.builder.get_object(\"cbAreaOSDEnabled\").set_active(self.editor.osd)\n\t\tself.builder.get_object(\"cbAreaClickEnabled\").set_active(self.pressing_pad_clicks())\n\t\tfor row in cbAreaType.get_model():\n\t\t\tif key == row[1]:\n\t\t\t\tcbAreaType.set_active_iter(row.iter)\n\t\t\t\tbreak\n\t\tself._recursing = False\n\t\n\t\n\tdef on_btCircularAxis_clicked(self, *a):\n\t\tdef cb(action):\n\t\t\tself.circular_axis = action\n\t\t\tbtCircularAxis = self.builder.get_object(\"btCircularAxis\")\n\t\t\tbtCircularAxis.set_label(action.describe(Action.AC_PAD))\n\t\t\tself.editor.set_action(self.make_circular_action())\n\t\t\n\t\tb = SimpleChooser(self.app, \"axis\", cb)\n\t\tb.set_title(_(\"Select Axis\"))\n\t\tb.display_action(Action.AC_STICK, self.circular_axis)\n\t\tb.show(self.editor.window)\n\t\n\t\n\tdef on_btCircularButton_clicked(self, button, *a):\n\t\tindex = 0 if button == self.builder.get_object(\"btCircularButton0\") else 1\n\t\tdef cb(action):\n\t\t\tself.circular_buttons[index] = action.button\n\t\t\tbtCircularButton = self.builder.get_object(\"btCircularButton%s\" % (index, ))\n\t\t\tbtCircularButton.set_label(action.describe(Action.AC_PAD))\n\t\t\tself.editor.set_action(self.make_circular_action())\n\t\t\n\t\tb = SimpleChooser(self.app, \"buttons\", cb)\n\t\tb.set_title(_(\"Select Button\"))\n\t\tb.display_action(Action.AC_STICK, self.circular_axis)\n\t\tb.show(self.editor.window)\n\t\n\t\n\tdef on_btClearCircularAxis_clicked(self, *a):\n\t\tbtCircularAxis = self.builder.get_object(\"btCircularAxis\")\n\t\tself.circular_axis = NoAction()\n\t\tbtCircularAxis.set_label(self.circular_axis.describe(Action.AC_PAD))\n\t\tself.editor.set_action(self.make_circular_action())\n\t\n\t\n\tdef on_btClearCircularButtons_clicked(self, *a):\n\t\tbtCircularButton0 = self.builder.get_object(\"btCircularButton0\")\n\t\tbtCircularButton1 = self.builder.get_object(\"btCircularButton1\")\n\t\tself.circular_buttons = [ None, None ]\n\t\tbtCircularButton0.set_label(NoAction().describe(Action.AC_PAD))\n\t\tbtCircularButton1.set_label(NoAction().describe(Action.AC_PAD))\n\t\tself.editor.set_action(self.make_circular_action())\n\t\n\t\n\tdef on_btSingleButton_clicked(self, *a):\n\t\tdef cb(action):\n\t\t\tself.button = action\n\t\t\tbtSingleButton = self.builder.get_object(\"btSingleButton\")\n\t\t\tbtSingleButton.set_label(self.button.describe(Action.AC_PAD))\n\t\t\tself.editor.set_action(self.button)\n\t\t\n\t\tb = SimpleChooser(self.app, \"buttons\", cb)\n\t\tb.set_title(_(\"Select Button\"))\n\t\tb.display_action(Action.AC_STICK, self.circular_axis)\n\t\tb.show(self.editor.window)\n\t\n\t\n\tdef on_cbAreaOSDEnabled_toggled(self, *a):\n\t\tself.editor.builder.get_object(\"cbOSD\").set_active(\n\t\t\tself.builder.get_object(\"cbAreaOSDEnabled\").get_active())\n\t\n\t\n\tdef pressing_pad_clicks(self):\n\t\t\"\"\"\n\t\tReturns True if currently edited pad is set to press left mouse\n\t\tbutton when pressed.\n\t\t(yes, this is used somewhere)\n\t\t\"\"\"\n\t\tside = getattr(SCButtons, self.editor.get_id())\n\t\tc_action = self.app.current.buttons[side]\n\t\tif isinstance(c_action, ButtonAction):\n\t\t\treturn c_action.button == Keys.BTN_LEFT\n\t\treturn False\n\t\n\t\n\tdef on_ok(self, action):\n\t\tif isinstance(action.strip(), AreaAction):\n\t\t\t# Kinda hacky way to set action on LPAD press or RPAD press\n\t\t\t# when user selects Mouse Area as ouput and checks\n\t\t\t# 'Pressing the Pad Clicks' checkbox\n\t\t\tside = getattr(SCButtons, self.editor.get_id())\n\t\t\tclicks = self.pressing_pad_clicks()\n\t\t\t\n\t\t\tif self.builder.get_object(\"cbAreaClickEnabled\").get_active():\n\t\t\t\tif not clicks:\n\t\t\t\t\t# Turn pad press into mouse clicks\n\t\t\t\t\tself.app.set_action(self.app.current, side, ButtonAction(Keys.BTN_LEFT))\n\t\t\telse:\n\t\t\t\tif clicks:\n\t\t\t\t\t# Clear action created above if checkbox is uncheck\n\t\t\t\t\tself.app.set_action(self.app.current, side, NoAction())\n\t\n\t\n\tdef on_mouse_options_changed(self, *a):\n\t\tif self._recursing : return\n\t\taction = self.make_mouse_action()\n\t\tself.editor.set_action(action)\n\t\n\t\n\tdef make_mouse_action(self):\n\t\t\"\"\"\n\t\tLoads values from UI into trackball-related action\n\t\t\"\"\"\n\t\tcbMouseOutput = self.builder.get_object(\"cbMouseOutput\")\n\t\ta_str = cbMouseOutput.get_model().get_value(cbMouseOutput.get_active_iter(), 2)\n\t\treturn self.parser.restart(a_str).parse()\n\t\n\t\n\tdef make_circular_action(self):\n\t\t\"\"\"\n\t\tConstructs Circular Modifier\n\t\t\"\"\"\n\t\tif self.circular_axis and any(self.circular_buttons):\n\t\t\treturn CircularModifier(MultiAction(\n\t\t\t\tself.circular_axis, ButtonAction(*self.circular_buttons)))\n\t\telif any(self.circular_buttons):\n\t\t\treturn CircularModifier(ButtonAction(*self.circular_buttons))\n\t\telse:\n\t\t\treturn CircularModifier(self.circular_axis)\n\t\n\t\n\tdef make_area_action(self):\n\t\t\"\"\"\n\t\tLoads values from UI into new AreaAction or subclass.\n\t\t\"\"\"\n\t\t# Prepare\n\t\tcbAreaType = self.builder.get_object(\"cbAreaType\")\n\t\t# Read numbers\n\t\tx1 = self.builder.get_object(\"sbAreaX1\").get_value()\n\t\ty1 = self.builder.get_object(\"sbAreaY1\").get_value()\n\t\tx2 = self.builder.get_object(\"sbAreaX2\").get_value()\n\t\ty2 = self.builder.get_object(\"sbAreaY2\").get_value()\n\t\t# Determine exact action type by looking into Area Type checkbox\n\t\t# (this part may seem little crazy)\n\t\t# ... numbers\n\t\tkey = cbAreaType.get_model().get_value(cbAreaType.get_active_iter(), 1)\n\t\tif \"-\" in key:\n\t\t\tif key[-2] == \"1\":\n\t\t\t\t# Before-last character ius \"1\", that means that X coords are\n\t\t\t\t# counted from other side and has to be negated\n\t\t\t\tx1, x2 = -x1, -x2\n\t\t\tif key[-1] == \"1\":\n\t\t\t\t# Key ends with \"1\". Same thing as above but for Y coordinate\n\t\t\t\ty1, y2 = -y1, -y2\n\t\tif \"size\" in key:\n\t\t\tx1, y1, x2, y2 = x1 / 100.0, y1 / 100.0, x2 / 100.0, y2 / 100.0\n\t\t# ... class \n\t\tif \"window-\" in key:\n\t\t\tcls = WinAreaAction\n\t\t\tself.relative_area = False\n\t\telif \"screensize\" == key:\n\t\t\tcls = RelAreaAction\n\t\t\tself.relative_area = True\n\t\telif \"windowsize\" == key:\n\t\t\tcls = RelWinAreaAction\n\t\t\tself.relative_area = True\n\t\telse: # \"screen\" in key\n\t\t\tcls = AreaAction\n\t\t\tself.relative_area = False\n\t\tif not self.relative_area:\n\t\t\tx1, y1, x2, y2 = int(x1), int(y1), int(x2), int(y2)\n\t\t\t\n\t\treturn cls(x1, y1, x2, y2)\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Joystick / Mouse\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif isinstance(action, (NoAction, MouseAction, CircularModifier,\n\t\t\t\t\tInvalidAction, AreaAction, ButtonAction)):\n\t\t\treturn True\n\t\tif isinstance(action, BallModifier):\n\t\t\tif isinstance(action.action, XYAction):\n\t\t\t\treturn (\n\t\t\t\t\tisinstance(action.action.x, (AxisAction, MouseAction))\n\t\t\t\t\tand isinstance(action.action.x, (AxisAction, MouseAction))\n\t\t\t\t)\n\t\t\treturn isinstance(action.action, MouseAction)\n\t\tif isinstance(action, XYAction):\n\t\t\tp = [ None, None ]\n\t\t\tfor x in (0, 1):\n\t\t\t\tif len(action.actions[0].strip().parameters) >= x:\n\t\t\t\t\tif len(action.actions[x].strip().parameters) > 0:\n\t\t\t\t\t\tp[x] = action.actions[x].strip().parameters[0]\n\t\t\tif p[0] == Axes.ABS_X and p[1] == Axes.ABS_Y:\n\t\t\t\treturn True\n\t\t\telif p[0] == Axes.ABS_RX and p[1] == Axes.ABS_RY:\n\t\t\t\treturn True\n\t\t\telif p[0] == Axes.ABS_HAT0X and p[1] == Axes.ABS_HAT0Y:\n\t\t\t\treturn True\n\t\t\telif p[0] == Rels.REL_HWHEEL and p[1] == Rels.REL_WHEEL:\n\t\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef on_area_options_changed(self, *a):\n\t\tif self._recursing : return\n\t\taction = self.make_area_action()\n\t\tself.editor.set_action(action)\n\t\tself.update_osd_area(action)\n\t\tfor x in ('sbAreaX1', 'sbAreaX2', 'sbAreaY1', 'sbAreaY2'):\n\t\t\tspin = self.builder.get_object(x)\n\t\t\tif self.relative_area:\n\t\t\t\tspin.get_adjustment().set_upper(100)\n\t\t\telse:\n\t\t\t\tspin.get_adjustment().set_upper(1000)\n\t\t\tself.on_sbArea_output(spin)\n\t\n\t\n\tdef on_sbArea_output(self, button, *a):\n\t\tif self.relative_area:\n\t\t\tbutton.set_text(\"%s %%\" % (button.get_value()))\n\t\telse:\n\t\t\tbutton.set_text(\"%s px\" % (int(button.get_value())))\n\t\n\t\n\tdef on_sbArea_focus_out_event(self, button, *a):\n\t\tGLib.idle_add(self.on_sbArea_output, button)\n\t\n\t\n\tdef on_sbArea_changed(self, button, *a):\n\t\tself.on_sbArea_output(button)\n\t\tself.on_area_options_changed(button)\n\t\n\t\n\tdef on_lblSetupTrackball_activate_link(self, trash, link):\n\t\tself.editor.on_link(link)\n\t\n\t\n\tdef on_cbAxisOutput_changed(self, *a):\n\t\tcbAxisOutput = self.builder.get_object(\"cbAxisOutput\")\n\t\tstActionData = self.builder.get_object(\"stActionData\")\n\t\tkey = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 2)\n\t\tif key == 'area':\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"grArea\"))\n\t\t\taction = self.make_area_action()\n\t\t\tself.update_osd_area(action)\n\t\telif key == \"button\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"vbButton\"))\n\t\t\tself.button = self.button or ButtonAction(Keys.BTN_GAMEPAD)\n\t\t\taction = self.button\n\t\telif key == \"circular\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"grCircular\"))\n\t\t\taction = self.make_circular_action()\n\t\telif key == 'mouse':\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"vbMose\"))\n\t\t\tif not self._recursing and self.editor.friction == 0:\n\t\t\t\t# When switching to mouse, enable trackball by default\n\t\t\t\tself.editor.friction = 10\n\t\t\taction = self.make_mouse_action()\n\t\telse:\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"nothing\"))\n\t\t\taction = cbAxisOutput.get_model().get_value(cbAxisOutput.get_active_iter(), 0)\n\t\t\taction = self.parser.restart(action).parse()\n\t\t\tself.update_osd_area(None)\n\t\t\n\t\tself.editor.set_action(action)\n\n\nclass FakeMapper(object):\n\t\"\"\"\n\tClass that pretends to be mapper used when calling update_osd_area.\n\tIt has two purposes: To provide get_xdisplay() method that does what it says\n\tand get_active_window() that returns any other window but window that\n\tbelongs to SC-Controller gui application.\n\t\"\"\"\n\tdef __init__(self, editor):\n\t\tself._xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))\n\t\tself.editor = editor\n\t\n\tdef get_xdisplay(self):\n\t\treturn self._xdisplay\n\t\n\tdef get_current_window(self):\n\t\t\"\"\"\n\t\tGets last active window that was not part of SC-Controller.\n\t\tUses _NET_CLIENT_LIST_STACKING property of root window, value provided\n\t\tby window manager. If that fail (because of no or not ICCM compilant WM),\n\t\tsimply returns root window.\n\t\t\"\"\"\n\t\troot = X.get_default_root_window(self._xdisplay)\n\t\tNET_WM_WINDOW_TYPE_NORMAL = X.intern_atom(self._xdisplay,\n\t\t\t\tbytes(\"_NET_WM_WINDOW_TYPE_NORMAL\", \"utf-8\"), False)\n\t\tmy_windows = [ x.get_xid() for x\n\t\t\t\tin Gdk.Screen.get_default().get_toplevel_windows() ]\n\t\tnitems, prop = X.get_window_prop(self._xdisplay,\n\t\t\t\troot, bytes(\"_NET_CLIENT_LIST_STACKING\", \"utf-8\"), max_size=0x8000)\n\t\t\n\t\tif nitems > 0:\n\t\t\tfor i in reversed(range(0, nitems)):\n\t\t\t\twindow = cast(prop, POINTER(X.XID))[i]\n\t\t\t\tif window in my_windows:\n\t\t\t\t\t# skip over my own windows\n\t\t\t\t\tcontinue\n\t\t\t\tif not X.is_window_visible(self._xdisplay, window):\n\t\t\t\t\t# skip minimized and invisible windows\n\t\t\t\t\tcontinue\n\t\t\t\ttp = X.get_window_prop(self._xdisplay, window,\n\t\t\t\t\t\tbytes(\"_NET_WM_WINDOW_TYPE\", \"utf-8\"))[-1]\n\t\t\t\tif tp is not None:\n\t\t\t\t\ttpval = cast(tp, POINTER(X.Atom)).contents.value\n\t\t\t\t\tX.free(tp)\n\t\t\t\t\tif tpval != NET_WM_WINDOW_TYPE_NORMAL:\n\t\t\t\t\t\t# skip over non-normal windows\n\t\t\t\t\t\tcontinue\n\t\t\t\tX.free(prop)\n\t\t\t\treturn window\n\t\tif prop is not None:\n\t\t\tX.free(prop)\n\t\t\n\t\t# Failed to get property or there is not any usable window\n\t\treturn root\n"
  },
  {
    "path": "scc/gui/ae/buttons.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Button Component\n\nAssigns emulated button to physical button\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action, ButtonAction, MouseAction\nfrom scc.actions import AxisAction, MultiAction, NoAction\nfrom scc.macros import Macro, Cycle, PressAction, ReleaseAction\nfrom scc.uinput import Rels, Keys\nfrom scc.gui.area_to_action import action_to_area\nfrom scc.gui.key_grabber import KeyGrabber\nfrom scc.gui.parser import InvalidAction\nfrom scc.gui.chooser import Chooser\nfrom scc.gui.ae import AEComponent\n\nimport os, logging\nlog = logging.getLogger(\"AE.Buttons\")\n\n__all__ = [ 'ButtonsComponent' ]\n\n\nclass ButtonsComponent(AEComponent, Chooser):\n\tGLADE = \"ae/buttons.glade\"\n\tNAME = \"buttons\"\n\tIMAGES = { \"buttons\" : \"buttons.svg\" }\n\tCTXS = Action.AC_BUTTON | Action.AC_MENU\n\tPRIORITY = 1\n\tMODIFIER_KEYS = ( Keys.KEY_LEFTSHIFT, Keys.KEY_LEFTMETA, Keys.KEY_LEFTALT,\n\t\tKeys.KEY_LEFTCTRL, Keys.KEY_RIGHTMETA, Keys.KEY_RIGHTSHIFT,\n\t\tKeys.KEY_RIGHTCTRL, Keys.KEY_RIGHTALT,\n\t)\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tChooser.__init__(self, app)\n\t\tself.axes_allowed = True\n\t\tself.keys = set()\n\t\n\t\n\tdef load(self):\n\t\tif not self.loaded:\n\t\t\tAEComponent.load(self)\n\t\t\tself.setup_image()\n\t\t\tif self.app.osd_mode:\n\t\t\t\tself.builder.get_object('btnGrabKey').set_sensitive(False)\n\t\t\t\tself.builder.get_object('btnGrabAnother').set_sensitive(False)\n\t\n\t\n\tdef area_action_selected(self, area, action):\n\t\tself.set_active_area(area)\n\t\tself.editor.set_action(action)\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tcbToggle = self.builder.get_object(\"cbToggle\")\n\t\tcbRepeat = self.builder.get_object(\"cbRepeat\")\n\t\tif self.handles(mode, action):\n\t\t\tself.keys = set()\n\t\t\tis_togle, is_repeat = False, False\n\t\t\tif isinstance(action, MultiAction):\n\t\t\t\tfor a in action.actions:\n\t\t\t\t\tif isinstance(a, ButtonAction):\n\t\t\t\t\t\tself.keys.add(a.button)\n\t\t\telif isinstance(action, ButtonAction):\n\t\t\t\tself.keys.add(action.button)\n\t\t\telif isinstance(action, Macro):\n\t\t\t\t# Macro goes here only if it is button repeat\n\t\t\t\tself.keys.add(action.actions[0].button)\n\t\t\t\tis_repeat = True\n\t\t\telif isinstance(action, Cycle):\n\t\t\t\t# There is only one case when self.handles returns True for Cycle\n\t\t\t\tself.keys.add(action.actions[0].action.button)\n\t\t\t\tis_togle = True\n\t\t\tcbToggle.set_active(is_togle)\n\t\t\tcbRepeat.set_active(is_repeat)\n\t\t\tarea = action_to_area(action)\n\t\t\tif area is not None:\n\t\t\t\tself.set_active_area(area)\n\t\t\t\treturn\n\t\tself.set_active_area(None)\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Key or Button\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\t# Handles ButtonAction and MultiAction if all subactions are ButtonAction\n\t\tif isinstance(action, (ButtonAction, NoAction, InvalidAction)):\n\t\t\treturn True\n\t\tif isinstance(action, AxisAction):\n\t\t\treturn len(action.parameters) == 1\n\t\tif isinstance(action, MouseAction):\n\t\t\tif action.get_axis() == Rels.REL_WHEEL:\n\t\t\t\treturn True\n\t\tif is_button_togle(action):\n\t\t\treturn True\n\t\tif is_button_repeat(action):\n\t\t\treturn True\n\t\tif isinstance(action, MultiAction):\n\t\t\tif len(action.actions) > 0:\n\t\t\t\tfor a in action.actions:\n\t\t\t\t\tif not isinstance(a, ButtonAction):\n\t\t\t\t\t\treturn False\n\t\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef on_key_grabbed(self, keys):\n\t\t\"\"\" Handles selecting key using \"Grab the Key\" dialog \"\"\"\n\t\tself.keys = set(keys)\n\t\tself.apply_keys()\n\t\n\t\n\tdef on_additional_key_grabbed(self, keys):\n\t\tself.keys.update(keys)\n\t\tself.apply_keys()\n\t\n\t\n\t@staticmethod\n\tdef modifiers_first(key):\n\t\tif key in ButtonsComponent.MODIFIER_KEYS:\n\t\t\treturn 0\n\t\treturn 1\n\t\n\t\n\tdef apply_keys(self, *a):\n\t\t\"\"\" Common part of on_*key_grabbed \"\"\"\n\t\tcbToggle = self.builder.get_object(\"cbToggle\")\n\t\tcbRepeat = self.builder.get_object(\"cbRepeat\")\n\t\tkeys = list(sorted(self.keys, key=ButtonsComponent.modifiers_first))\n\t\taction = ButtonAction(keys[0])\n\t\tif len(keys) > 1:\n\t\t\tactions = [ ButtonAction(k) for k in keys ]\n\t\t\taction = MultiAction(*actions)\n\t\tif cbRepeat.get_active():\n\t\t\taction = Macro(action)\n\t\t\taction.repeat = True\n\t\telif cbToggle.get_active():\n\t\t\taction = Cycle(PressAction(action), ReleaseAction(action))\n\t\tself.editor.set_action(action)\n\t\n\t\n\tdef on_btnGrabKey_clicked(self, *a):\n\t\t\"\"\"\n\t\tCalled when user clicks on 'Grab a Key' button.\n\t\tDisplays additional dialog.\n\t\t\"\"\"\n\t\tkg = KeyGrabber(self.app)\n\t\tkg.grab(self.editor.window, self.editor._action, self.on_key_grabbed)\n\t\n\t\n\tdef on_btnGrabAnother_clicked(self, *a):\n\t\t\"\"\"\n\t\tSame as above, but adds another key to action\n\t\t\"\"\"\n\t\tkg = KeyGrabber(self.app)\n\t\tkg.grab(self.editor.window, self.editor._action,\n\t\t\t\tself.on_additional_key_grabbed)\n\t\n\t\n\tdef on_cbToggle_toggled(self, cbToggle):\n\t\tcbRepeat = self.builder.get_object(\"cbRepeat\")\n\t\tif cbToggle.get_active() and cbRepeat.get_active():\n\t\t\tcbRepeat.set_active(False)\n\t\tself.apply_keys()\n\t\n\t\n\tdef on_cbRepeat_toggled(self, cbRepeat):\n\t\tcbToggle = self.builder.get_object(\"cbToggle\")\n\t\tif cbToggle.get_active() and cbRepeat.get_active():\n\t\t\tcbToggle.set_active(False)\n\t\tself.apply_keys()\n\t\n\t\n\tdef hide_toggle(self):\n\t\t\"\"\" Hides 'set as toggle button' option \"\"\"\n\t\tcbToggle = self.builder.get_object(\"cbToggle\")\n\t\tbtnGrabAnother = self.builder.get_object(\"btnGrabAnother\")\n\t\treplacement = Gtk.Label(\"\")\n\t\treplacement.set_size_request(*cbToggle.get_size_request())\n\t\tbtnGrabAnother.set_visible(False)\n\t\tparent = cbToggle.get_parent()\n\t\tparent.remove(cbToggle)\n\t\tparent.pack_start(replacement, True, True, 0)\n\t\tparent.reorder_child(replacement, 0)\n\t\treplacement.set_visible(True)\n\t\n\t\n\tdef hide_axes(self):\n\t\t\"\"\" Prevents user from selecting axes \"\"\"\n\t\tself.axes_allowed = False\n\n\ndef is_button_togle(action):\n\tif not isinstance(action, Cycle):\n\t\treturn False\n\tif len(action.actions) != 2:\n\t\treturn False\n\tif isinstance(action.actions[0], PressAction):\n\t\tif isinstance(action.actions[1], ReleaseAction):\n\t\t\tif isinstance(action.actions[0].action, ButtonAction):\n\t\t\t\tif isinstance(action.actions[1].action, ButtonAction):\n\t\t\t\t\treturn action.actions[0].action.button == action.actions[1].action.button\n\treturn False\n\n\ndef is_button_repeat(action):\n\tif isinstance(action, Cycle):\n\t\treturn False\n\tif isinstance(action, Macro) and action.repeat:\n\t\tif len(action.actions) == 1:\n\t\t\treturn isinstance(action.actions[0], ButtonAction)\n\treturn False\n"
  },
  {
    "path": "scc/gui/ae/custom.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Custom action\n\nCustom Action page in Action Editor window\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.gui.parser import GuiActionParser, InvalidAction\nfrom scc.gui.ae import AEComponent\nfrom scc.actions import Action\n\nimport os, logging\nlog = logging.getLogger(\"AE.Custom\")\n\n__all__ = [ 'CustomActionComponent' ]\n\nclass CustomActionComponent(AEComponent):\n\tGLADE = \"ae/custom.glade\"\n\tNAME = \"custom\"\n\tPRIORITY = -1\n\tCTXS = Action.AC_ALL\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tself.parser = GuiActionParser()\n\t\n\t\n\tdef handles(self, mode, action):\n\t\t# Custom Action Editor handles all actions\n\t\treturn isinstance(action, Action)\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Custom Action\")\n\t\n\t\n\tdef load(self):\n\t\tif self.loaded : return\n\t\tAEComponent.load(self)\n\t\ttry:\n\t\t\ttxCustomAction = self.builder.get_object(\"txCustomAction\")\n\t\t\ttxCustomAction.set_monospace(True)\n\t\texcept: pass\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\taction = self.editor.generate_modifiers(action, from_custom=True)\n\t\ttbCustomAction = self.builder.get_object(\"tbCustomAction\")\n\t\ttbCustomAction.set_text(action.to_string(True))\n\t\n\t\n\tdef on_tbCustomAction_changed(self, tbCustomAction, *a):\n\t\t\"\"\"\n\t\tConverts text from Custom Action text area into action instance and\n\t\tsends that instance back to editor.\n\t\t\"\"\"\n\t\ttxCustomAction = self.builder.get_object(\"txCustomAction\")\n\t\ttxt = tbCustomAction.get_text(tbCustomAction.get_start_iter(), tbCustomAction.get_end_iter(), True)\n\t\tif len(txt.strip(\" \\t\\r\\n\")) > 0:\n\t\t\taction = self.parser.restart(txt).parse()\n\t\t\tself.editor.set_action(action, from_custom=True)\n\t\n\t\n\tdef shown(self):\n\t\tself.editor.set_modifiers_enabled(False)\n\t\n\t\n\tdef hidden(self):\n\t\tself.editor.set_modifiers_enabled(True)\n"
  },
  {
    "path": "scc/gui/ae/dpad.py",
    "content": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\"\"\"\nSC-Controller - Action Editor - \"DPAD or Menu\"\n\nSetups DPAD emulation or menu display\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import HatUpAction, HatDownAction, HatLeftAction,HatRightAction\nfrom scc.actions import Action, NoAction, DPadAction, DPad8Action, ButtonAction\nfrom scc.constants import LEFT, RIGHT, STICK, SAME, DEFAULT, SCButtons\nfrom scc.modifiers import NameModifier\nfrom scc.special_actions import MenuAction\nfrom scc.uinput import Keys, Axes\nfrom scc.gui.ae import AEComponent, describe_action\nfrom scc.gui.ae.menu_action import MenuActionCofC\nfrom scc.gui.binding_editor import BindingEditor\nfrom scc.gui.action_editor import ActionEditor\n\n\nimport os, logging\nlog = logging.getLogger(\"AE.DPAD\")\n\n__all__ = [ 'DPADComponent' ]\n\n\nclass DPADComponent(AEComponent, MenuActionCofC, BindingEditor):\n\tGLADE = \"ae/dpad.glade\"\n\tNAME = \"dpad\"\n\tCTXS = Action.AC_STICK | Action.AC_PAD\n\tPRIORITY = 2\n\t\n\tDPAD8_WIDGETS = [ 'btDPAD4', 'btDPAD5', 'btDPAD6', 'btDPAD7' ]\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tMenuActionCofC.__init__(self)\n\t\tBindingEditor.__init__(self, app)\n\t\tself._recursing = False\n\t\tself._userdata_load_started = False\n\t\tself.actions = [ NoAction() ] * 8\n\t\n\t\n\tdef load(self):\n\t\tif self.loaded : return\n\t\tAEComponent.load(self)\n\t\tcbConfirmWith = self.builder.get_object(\"cbConfirmWith\")\n\t\tcbCancelWith = self.builder.get_object(\"cbCancelWith\")\n\t\tcbConfirmWith.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == \"-\" )\n\t\tcbCancelWith.set_row_separator_func( lambda model, iter : model.get_value(iter, 0)  == \"-\" )\n\t\n\t\n\tdef shown(self):\n\t\tif not self._userdata_load_started:\n\t\t\tself._userdata_load_started = True\n\t\t\tself.load_menu_list()\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tcbm = self.builder.get_object(\"cbMenuType\")\n\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\tscl = self.builder.get_object(\"sclDiagonalRange\")\n\t\tif isinstance(action, DPadAction):\n\t\t\tself.set_cb(cbm, \"menu\", 1)\n\t\t\tscl.set_value(action.diagonal_rage)\n\t\t\tif isinstance(action, DPad8Action):\n\t\t\t\tself.set_cb(cb, \"dpad8\", 1)\n\t\t\telse:\n\t\t\t\tself.set_cb(cb, \"dpad\", 1)\n\t\t\tself.update_button_desc(action)\n\t\telif MenuActionCofC.handles(self, None, action):\n\t\t\tself.set_cb(cb, \"menu\", 1)\n\t\t\tself.load_menu_data(action)\n\t\tself.on_cbActionType_changed()\n\t\n\t\n\tdef update_button_desc(self, action):\n\t\tfor i in range(0, len(action.actions)):\n\t\t\tself.actions[i] = action.actions[i]\n\t\tfor i in range(0, 8):\n\t\t\tself.set_button_desc(i)\n\t\n\t\n\tdef set_button_desc(self, i):\n\t\tdesc = describe_action(Action.AC_BUTTON, None, self.actions[i])\n\t\tl = self.builder.get_object(\"lblDPAD%s\" % (i,))\n\t\tif l is None:\n\t\t\tl = self.builder.get_object(\"btDPAD%s\" % (i,)).get_children()[0]\n\t\tl.set_markup(desc)\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"DPAD / Menu\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif MenuActionCofC.handles(self, mode, action):\n\t\t\treturn True\n\t\treturn isinstance(action, DPadAction) # DPad8Action is derived from DPadAction\n\t\n\t\n\tdef update(self):\n\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\tscl = self.builder.get_object(\"sclDiagonalRange\")\n\t\tkey = cb.get_model().get_value(cb.get_active_iter(), 1)\n\t\tif key == \"dpad8\":\n\t\t\t# 8-way dpad\n\t\t\tself.editor.set_action(DPad8Action(scl.get_value(), *self.actions))\n\t\telif key == \"dpad\":\n\t\t\t# 4-way dpad\n\t\t\tself.editor.set_action(DPadAction(scl.get_value(),\n\t\t\t\t*self.actions[0:4]))\n\t\telif key == \"wsad\":\n\t\t\t# special case of 4-way dpad\n\t\t\ta = DPadAction(scl.get_value(),\n\t\t\t\tButtonAction(Keys.KEY_W), ButtonAction(Keys.KEY_S),\n\t\t\t\tButtonAction(Keys.KEY_A), ButtonAction(Keys.KEY_D))\n\t\t\tself.actions = [ NoAction() ] * 8\n\t\t\tself.editor.set_action(a)\n\t\t\tself.update_button_desc(a)\n\t\telif key == \"arrows\":\n\t\t\t# special case of 4-way dpad\n\t\t\ta = DPadAction(scl.get_value(),\n\t\t\t\tButtonAction(Keys.KEY_UP), ButtonAction(Keys.KEY_DOWN),\n\t\t\t\tButtonAction(Keys.KEY_LEFT), ButtonAction(Keys.KEY_RIGHT))\n\t\t\tself.actions = [ NoAction() ] * 8\n\t\t\tself.editor.set_action(a)\n\t\t\tself.update_button_desc(a)\n\t\telif key == \"actual_dpad\":\n\t\t\t# maps to dpad as real gamepad usually has\n\t\t\ta = DPadAction(scl.get_value(),\n\t\t\t\tHatUpAction(Axes.ABS_HAT0Y), HatDownAction(Axes.ABS_HAT0Y),\n\t\t\t\tHatLeftAction(Axes.ABS_HAT0X), HatRightAction(Axes.ABS_HAT0X))\n\t\t\tself.actions = [ NoAction() ] * 8\n\t\t\tself.editor.set_action(a)\n\t\t\tself.update_button_desc(a)\n\t\telse:\n\t\t\t# Menu\n\t\t\tself.on_cbMenus_changed()\n\t\n\t\n\tdef on_cbActionType_changed(self, *a):\n\t\tif self._recursing: return\n\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\tstActionData = self.builder.get_object(\"stActionData\")\n\t\tkey = cb.get_model().get_value(cb.get_active_iter(), 1)\n\t\tif key in (\"dpad\", \"dpad8\", \"wsad\", \"arrows\", \"actual_dpad\"):\n\t\t\tfor i in self.DPAD8_WIDGETS:\n\t\t\t\tself.builder.get_object(i).set_visible(key == \"dpad8\")\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"grDPAD\"))\n\t\telse: # key == \"menu\"\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"grMenu\"))\n\t\tself.update()\n\t\n\t\n\tdef on_action_chosen(self, i, action, mark_changed=True):\n\t\tself.actions[i] = action\n\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\tkey = cb.get_model().get_value(cb.get_active_iter(), 1)\n\t\tif key != \"dpad8\":\n\t\t\t# When user chooses WSAD, Arrows or DPAD emulation and then changes\n\t\t\t# one of actions, swap back to 'Simple DPAD' mode.\n\t\t\tself.set_cb(cb, \"dpad\", 1)\n\t\t#if action.name:\n\t\t#\taction = NameModifier(action.name, action)\n\t\tself.set_button_desc(i)\n\t\tself.update()\n\t\n\t\n\tdef on_sclDiagonalRange_format_value(self, scale, value):\n\t\treturn _(\"%s°\") % (value,)\n\t\n\t\n\tdef on_btClearDiagonalRange_clicked(self, *a):\n\t\tscl = self.builder.get_object(\"sclDiagonalRange\")\n\t\tscl.set_value(DPadAction.DEFAULT_DIAGONAL_RANGE)\n\t\n\t\n\tdef on_sclDiagonalRange_value_changed(self, *a):\n\t\tself.update()\n\t\n\t\n\tdef on_btDPAD_clicked(self, b):\n\t\t\"\"\" 'Select DPAD Left Action' handler \"\"\"\n\t\ti = int(b.get_name())\n\t\taction = self.actions[i]\n\t\t#f isinstance(action, NameModifier):\n\t\t#action.action.name = action.name\n\t\t#action = action.action\n\t\tae = self.choose_editor(action, \"\")\n\t\t# ae = ActionEditor(self.app, self.on_choosen)\n\t\tae.set_title(_(\"Select DPAD Action\"))\n\t\tae.set_input(i, action, mode = Action.AC_BUTTON)\n\t\tae.show(self.editor.window)\n\t\n\t\n\tdef get_default_confirm(self):\n\t\t\"\"\"\n\t\tReturns default confirm button for pads/stick - LPAD, RPAD or STICKPRESS\n\t\t\"\"\"\n\t\tif self.editor.id == STICK:\n\t\t\treturn SCButtons.STICKPRESS\n\t\treturn getattr(SCButtons, self.editor.id)\n\t\n\t\n\tdef get_default_cancel(self):\n\t\t\"\"\"\n\t\tReturns default cancel button for stick/pad - SAME or B\n\t\t\"\"\"\n\t\tif self.editor.id == STICK:\n\t\t\treturn SCButtons.B\n\t\treturn SAME\n\t\n\t\n\tdef get_control_with(self):\n\t\t\"\"\"\n\t\t'control_with' argument is ignored when menu is used with stick/pad.\n\t\t\"\"\"\n\t\treturn DEFAULT\n\t\n\t\n\tdef on_exMenuControl_activate(self, ex, *a):\n\t\trvMenuControl = self.builder.get_object(\"rvMenuControl\")\n\t\trvMenuControl.set_reveal_child(not ex.get_expanded())\n\t\n\t\n\tdef on_exMenuPosition_activate(self, ex, *a):\n\t\trvMenuPosition = self.builder.get_object(\"rvMenuPosition\")\n\t\trvMenuPosition.set_reveal_child(not ex.get_expanded())\n"
  },
  {
    "path": "scc/gui/ae/first_page.py",
    "content": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\"\"\"\nSC-Controller - Action Editor - First Page\n\nProvides links for quick settings.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action\nfrom scc.gui.ae import AEComponent\nfrom scc.tools import nameof\n\n\nimport os, logging\nlog = logging.getLogger(\"AE.1st\")\n\n__all__ = [ 'FirstPage' ]\n\nMARKUP_BUTTON = \"\"\"\n<big>%(what)s: Quick settings</big>\n\n  • Map to <a href='page://buttons'>Button</a>\n  \n  • Use to <a href='quick://menu(\"Default.menu\")'>display on-screen menu</a>\n\"\"\"\n\nMARKUP_TRIGGER = \"\"\"\n<big>%(what)s: Quick settings</big>\n\n  • Map to <a href='quick://axis(Axes.ABS_Z)'>Left</a> or <a href='quick://axis(Axes.ABS_RZ)'>Right</a> Trigger\n  \n  • Map to <a href='quick://trigger(50, 255, button(Keys.BTN_LEFT))'>Left</a> or <a href='quick://trigger(50, 255, button(Keys.BTN_RIGHT))'>Right</a> mouse button\n  \n  • Map to <a href='grab://trigger_button'>Button</a>\n\"\"\"\n\n\n# TODO: Add haptics here\nMARKUP_PAD = \"\"\"\n<big>%(what)s: Quick settings</big>\n\n  • Use as <a href='quick://sens(1.2, 1.2, XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y)))'>Left</a> or <a href='quick://sens(1.2, 1.2, XY(axis(Axes.ABS_RX), raxis(Axes.ABS_RY)))'>Right</a> gamepad stick, or <a href='quick://dpad(hatup(Axes.ABS_HAT0Y), hatdown(Axes.ABS_HAT0Y), hatleft(Axes.ABS_HAT0X), hatright(Axes.ABS_HAT0X))'>DPAD</a>\n\n  • Use as keyboard: <a href='quick://dpad(button(Keys.KEY_W), button(Keys.KEY_S), button(Keys.KEY_A), button(Keys.KEY_D))'>WSAD</a> or <a href='quick://dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))'>Arrows</a>\n\n  • Use as <a href='quick://smooth(8, 0.78, 2.0, feedback(RIGHT, 256, ball(mouse())))'>mouse</a> or <a href='quick://feedback(LEFT, 4096, 16.0, ball(XY(mouse(Rels.REL_HWHEEL), mouse(Rels.REL_WHEEL))))'>mouse wheel</a>\n\n  • Create <a href='quick://feedback(LEFT, 4096, 16.0, menu(\"Default.menu\",DEFAULT,A,B))'>touch menu</a>\n\n  • Enable <a href='page://gesture'>gesture recognition</a>\n\"\"\"\n\nMARKUP_STICK = \"\"\"\n<big>Stick: Quick settings</big>\n\n  • Use as <a href='quick://sens(1.2, 1.2, XY(axis(Axes.ABS_X), raxis(Axes.ABS_Y)))'>Left</a> or <a href='quick://sens(1.2, 1.2, XY(axis(Axes.ABS_RX), raxis(Axes.ABS_RY)))'>Right</a> gamepad stick, or <a href='quick://dpad(hatup(Axes.ABS_HAT0Y), hatdown(Axes.ABS_HAT0Y), hatleft(Axes.ABS_HAT0X), hatright(Axes.ABS_HAT0X))'>DPAD</a>\n\n  • Use as keyboard: <a href='quick://dpad(button(Keys.KEY_W), button(Keys.KEY_S), button(Keys.KEY_A), button(Keys.KEY_D))'>WSAD</a> or <a href='quick://dpad(button(Keys.KEY_UP), button(Keys.KEY_DOWN), button(Keys.KEY_LEFT), button(Keys.KEY_RIGHT))'>Arrows</a>\n\n  • Use as <a href='quick://smooth(8, 0.78, 2.0, feedback(RIGHT, 256, ball(mouse())))'>mouse</a> or <a href='quick://feedback(LEFT, 4096, 16.0, ball(XY(mouse(Rels.REL_HWHEEL), mouse(Rels.REL_WHEEL))))'>mouse wheel</a>\n\"\"\"\n\nMARKUP_GYRO = \"\"\"\n<big>Gyro: Quick settings</big>\n\n  • Setup for aiming when right <a href='quick://mode(RPADTOUCH, mouse(ROLL), None)'>pad is touched</a>\n  \n  • Setup for aiming when right <a href='quick://mode(LT >= 0.7, mouse(ROLL), None)'>trigger is pushed</a>\n  \n  • <a href='quick://sens(3.5, 3.5, 3.5, mouse(ROLL))'>Use as mouse</a>\n\"\"\"\n\n\nclass FirstPage(AEComponent):\n\tGLADE = \"ae/first_page.glade\"\n\tNAME = \"first_page\"\n\tCTXS = 0\n\tPRIORITY = 999\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\n\tdef load(self):\n\t\tif AEComponent.load(self):\n\t\t\tmarkup = \"\"\n\t\t\tif self.editor.get_mode() == Action.AC_PAD:\n\t\t\t\tmarkup = MARKUP_PAD\n\t\t\telif self.editor.get_mode() == Action.AC_STICK:\n\t\t\t\tmarkup = MARKUP_STICK\n\t\t\telif self.editor.get_mode() == Action.AC_GYRO:\n\t\t\t\tmarkup = MARKUP_GYRO\n\t\t\telif self.editor.get_mode() == Action.AC_TRIGGER:\n\t\t\t\tmarkup = MARKUP_TRIGGER\n\t\t\telse:\n\t\t\t\tmarkup = MARKUP_BUTTON\n\t\t\t\n\t\t\tlong_names = {\n\t\t\t\t'LPAD' : _(\"Left Pad\"),\n\t\t\t\t'RPAD' : _(\"Right Pad\"),\n\t\t\t\t'LGRIP' : _(\"Left Grip\"),\n\t\t\t\t'RGRIP' : _(\"Right Grip\"),\n\t\t\t\t'LB' : _(\"Left Bumper\"),\n\t\t\t\t'RB' : _(\"Right Bumper\"),\n\t\t\t\t'LEFT' : _(\"Left Trigger\"),\n\t\t\t\t'RIGHT' : _(\"Right Trigger\"),\n\t\t\t\t'STICK' : _(\"Stick\"),\n\t\t\t}\n\t\t\t\n\t\t\tmarkup = markup % {\n\t\t\t\t'what' : long_names.get(nameof(self.editor.get_id()),\n\t\t\t\t\t\t\t\tnameof(self.editor.get_id()).title())\n\t\t\t}\n\t\t\tself.builder.get_object(\"lblMarkup\").set_markup(markup.strip(\" \\r\\n\\t\"))\n\t\t\treturn True\n\t\n\tdef on_lblMarkup_activate_link(self, trash, link):\n\t\tself.editor.on_link(link)\n"
  },
  {
    "path": "scc/gui/ae/gesture.py",
    "content": "#!/usr/bin/env python2\n# coding=utf-8\n\"\"\"\nSC-Controller - Action Editor - Gesture Component\n\nHandles gesture recognition settings.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib, GdkX11, GObject\nfrom scc.gui.ae import AEComponent, describe_action\nfrom scc.gui.area_to_action import action_to_area\nfrom scc.gui.simple_chooser import SimpleChooser\nfrom scc.gui.action_editor import ActionEditor\nfrom scc.gui.parser import GuiActionParser\nfrom scc.special_actions import GesturesAction, OSDAction\nfrom scc.osd.gesture_display import GestureDisplay\nfrom scc.actions import Action, NoAction, XYAction\nfrom scc.modifiers import NameModifier\nfrom scc.tools import strip_gesture\n\nimport os, logging\nlog = logging.getLogger(\"AE.PerAxis\")\n\n__all__ = [ 'GestureComponent' ]\n\n\nclass GestureComponent(AEComponent):\n\tGLADE = \"ae/gesture.glade\"\n\tNAME = \"gesture\"\n\tCTXS = Action.AC_STICK | Action.AC_PAD\n\tPRIORITY = 1\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tself.on_wayland = False\n\t\tself._edited_gesture = None\n\t\tself._grabber = None\n\t\n\t\n\tdef load(self):\n\t\tif AEComponent.load(self):\n\t\t\t# Unlike mose region, gesutres kinda work with XWayland\n\t\t\tself.on_wayland = not isinstance(Gdk.Display.get_default(), GdkX11.X11Display)\n\t\t\tif self.on_wayland:\n\t\t\t\tself.builder.get_object(\"lblGestureMessage\").set_text(_(\"Note: Gestures are not available with Wayland-based display server\"))\n\t\t\t\tself.builder.get_object(\"lblGestureMessage\").set_visible(True)\n\t\t\t\tself.builder.get_object(\"gesture\").set_sensitive(False)\n\t\t\telse:\n\t\t\t\tself._grabber = GestureGrabber(self.editor, self.builder)\n\t\t\treturn True\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tlstGestures = self.builder.get_object(\"lstGestures\")\n\t\tlstGestures.clear()\n\t\tif isinstance(action, GesturesAction):\n\t\t\tfor gstr in action.gestures:\n\t\t\t\tself._add_gesture(gstr, action.gestures[gstr])\n\t\n\t\n\tdef _add_gesture(self, gstr, action, select=False):\n\t\tlstGestures = self.builder.get_object(\"lstGestures\")\n\t\to = GObject.GObject()\n\t\to.gstr = gstr\n\t\to.action = action\n\t\titer = lstGestures.append( (\n\t\t\tGestureComponent.nice_gstr(gstr),\n\t\t\taction.describe(Action.AC_MENU),\n\t\t\to\n\t\t) )\n\t\tif select:\n\t\t\ttvGestures = self.builder.get_object(\"tvGestures\")\n\t\t\ttvGestures.get_selection().select_iter(iter)\n\t\t\tself.on_tvGestures_cursor_changed()\n\t\t\tself.on_btEditAction_clicked()\n\t\n\t\n\tARROWS = { 'U' : '↑', 'D' : '↓', 'L' : '←', 'R' : '→', }\n\t@staticmethod\n\tdef nice_gstr(gstr):\n\t\t\"\"\"\n\t\tReplaces characters UDLR in gesture string with unicode arrows.\n\t\t← → ↑ ↓\n\t\t\"\"\"\n\t\tif \"i\" in gstr:\n\t\t\tgstr = strip_gesture(gstr)\n\t\tl = lambda x : GestureComponent.ARROWS[x] if x in GestureComponent.ARROWS else \"\"\n\t\treturn \"\".join(map(l, gstr))\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Gestures\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\treturn isinstance(action, GesturesAction)\n\t\n\t\n\tdef on_tvGestures_cursor_changed(self, *a):\n\t\ttvGestures = self.builder.get_object(\"tvGestures\")\n\t\tbtEditAction = self.builder.get_object(\"btEditAction\")\n\t\tbtRemove = self.builder.get_object(\"btRemove\")\n\t\tmodel, iter = tvGestures.get_selection().get_selected()\n\t\tif iter is None:\n\t\t\tbtEditAction.set_sensitive(False)\n\t\t\tbtRemove.set_sensitive(False)\n\t\telse:\n\t\t\tbtEditAction.set_sensitive(True)\n\t\t\tbtRemove.set_sensitive(True)\n\t\n\t\n\tdef on_btEditAction_clicked(self, *a):\n\t\t\"\"\" Handler for \"Edit Action\" button \"\"\"\n\t\ttvGestures = self.builder.get_object(\"tvGestures\")\n\t\ttxGesture = self.builder.get_object(\"txGesture\")\n\t\tcbIgnoreStroke = self.builder.get_object(\"cbIgnoreStroke\")\n\t\tgesture_editor = self.builder.get_object(\"gesture_editor\")\n\t\t\n\t\tmodel, iter = tvGestures.get_selection().get_selected()\n\t\titem = model.get_value(iter, 2)\n\t\tself._edited_gesture = item.gstr\n\t\tcbIgnoreStroke.set_active(\"i\" in self._edited_gesture)\n\t\ttxGesture.set_text(GestureComponent.nice_gstr(self._edited_gesture))\n\t\t# Setup editor\n\t\te = ActionEditor(self.app, self.on_action_chosen)\n\t\te.set_title(_(\"Edit Gesture Action\"))\n\t\te.set_input(\"ID\", item.action, mode = Action.AC_BUTTON)\n\t\te.add_widget(_(\"Gesture\"), gesture_editor)\n\t\te.hide_modeshift()\n\t\t# Display editor\n\t\te.show(self.editor.window)\n\t\n\t\n\tdef on_btChangeGesture_clicked(self, *a):\n\t\ttxGesture = self.builder.get_object(\"txGesture\")\n\t\tdef grabbed(gesture):\n\t\t\tself._edited_gesture = gesture\n\t\t\ttxGesture.set_text(GestureComponent.nice_gstr(self._edited_gesture))\n\t\tself._grabber.grab(grabbed)\n\t\n\t\n\tdef on_cbIgnoreStroke_toggled(self, cb):\n\t\ttxGesture = self.builder.get_object(\"txGesture\")\n\t\tif cb.get_active() and \"i\" not in self._edited_gesture:\n\t\t\tself._edited_gesture = \"i\" + self._edited_gesture\n\t\t\ttxGesture.set_text(GestureComponent.nice_gstr(self._edited_gesture))\n\t\telif not cb.get_active() and \"i\" in self._edited_gesture:\n\t\t\tself._edited_gesture = self._edited_gesture.strip(\"i\")\n\t\t\ttxGesture.set_text(GestureComponent.nice_gstr(self._edited_gesture))\n\t\n\t\n\tdef on_btRemove_clicked(self, *a):\n\t\ttvGestures = self.builder.get_object(\"tvGestures\")\n\t\tmodel, iter = tvGestures.get_selection().get_selected()\n\t\tmodel.remove(iter)\n\t\tself.on_tvGestures_cursor_changed()\n\t\n\t\n\tdef on_btAdd_clicked(self, *a):\n\t\tdef grabbed(gesture):\n\t\t\tself._add_gesture(gesture, NoAction(), True)\n\t\ttvGestures = self.builder.get_object(\"tvGestures\")\n\t\tif len(tvGestures.get_model()) == 0:\n\t\t\t# I believe user will not actually find this option, so OSD checkbox\n\t\t\t# is automatically enabled when first item is added\n\t\t\tself.editor.set_osd_enabled(True)\n\t\tself._grabber.grab(grabbed)\n\t\n\t\n\tdef on_sclPrecision_format_value(self, scl, value):\n\t\treturn \"%s%%\" % (int(value * 100.0),)\n\t\n\t\n\tdef on_sclPrecision_value_changed(self, *a):\n\t\tself.update()\n\t\n\t\n\tdef on_btClearTolerance_clicked(self, *a):\n\t\tself.builder.get_object(\"sclPrecision\").set_value(GesturesAction.DEFAULT_PRECISION)\n\t\n\t\n\tdef on_action_chosen(self, id, action, mark_changed=True):\n\t\ttvGestures = self.builder.get_object(\"tvGestures\")\n\t\tmodel, iter = tvGestures.get_selection().get_selected()\n\t\titem = model.get_value(iter, 2)\n\t\titem.gstr = self._edited_gesture\n\t\titem.action = action\n\t\tmodel.set_value(iter, 0, GestureComponent.nice_gstr(item.gstr))\n\t\tmodel.set_value(iter, 1, action.describe(Action.AC_MENU))\n\t\tself.update()\n\t\n\t\n\tdef update(self):\n\t\ta = GesturesAction()\n\t\ttvGestures = self.builder.get_object(\"tvGestures\")\n\t\tmodel, iter = tvGestures.get_selection().get_selected()\n\t\tfor row in model:\n\t\t\titem = row[2]\n\t\t\ta.gestures[item.gstr] = item.action\n\t\t\tif item.action.name:\n\t\t\t\ta.gestures[item.gstr] = NameModifier(item.action.name, item.action)\n\t\ta.precision = self.builder.get_object(\"sclPrecision\").get_value()\n\t\ta = OSDAction(a)\n\t\tself.editor.set_action(a)\n\n\nclass GestureGrabber(object):\n\tdef __init__(self, editor, builder):\n\t\tself.editor = editor\n\t\tself.builder = builder\n\t\tself._callback = None\n\t\tself._gd = None\n\t\tself._signals = None\n\t\tself._gesture = None\n\t\tself._repeats = 0\n\t\tself.gesture_grabber = self.builder.get_object(\"gesture_grabber\")\n\t\tself.txGestureGrab = self.builder.get_object(\"txGestureGrab\")\n\t\tself.lblGestureGrabberTitle = self.builder.get_object(\"lblGestureGrabberTitle\")\n\t\tself.lblGestureStatus = self.builder.get_object(\"lblGestureStatus\")\n\t\tself.rvGestureGrab = self.builder.get_object(\"rvGestureGrab\")\n\t\t# Can't use autoconnect for this :(\n\t\tself.gesture_grabber.connect(\"delete-event\", self.close)\n\t\tself.gesture_grabber.connect(\"destroy\", self.close)\n\t\tself.builder.get_object(\"btnStartGestureOver\").connect(\"clicked\", self.start_over)\n\t\tself.builder.get_object(\"btnConfirmGesutre\").connect(\"clicked\", self.use)\n\t\n\t\n\tdef fail(self, *a):\n\t\t\"\"\"\n\t\tCalled when something goes bad, usually because there is\n\t\tno controller connected.\n\t\t\"\"\"\n\t\tlog.error(\"Failed to grab gesture: %s\", a)\n\t\n\t\n\tdef disconnect_signals(self):\n\t\t\"\"\"\n\t\tDisconnects redundant signal handlers.\n\t\tCurrently only one created in lock_buttons.\n\t\t\"\"\"\n\t\tif self._signals:\n\t\t\tfor source, eid in self._signals:\n\t\t\t\tsource.disconnect(eid)\n\t\t\tself._signals = []\n\t\n\t\n\tdef lock_buttons(self):\n\t\tself.disconnect_signals()\n\t\ttry:\n\t\t\tc = self.editor.app.dm.get_controllers()[0]\n\t\t\tc.lock(\n\t\t\t\tlambda *a: True,\t# success_cb\n\t\t\t\tself.fail,\t\t\t# error_cb\n\t\t\t\t'A', 'Y'\n\t\t\t)\n\t\t\tself._signals = [ (c, c.connect('event', self.on_event)) ]\n\t\texcept IndexError as e:\n\t\t\t# No controllers\n\t\t\tself.fail()\n\t\n\t\n\tdef on_event(self, c, button, data):\n\t\tif self.rvGestureGrab.get_reveal_child():\n\t\t\tif button == \"A\" and data[0] == 0:\n\t\t\t\tself.use()\n\t\t\telif button == \"Y\" and data[0] == 0:\n\t\t\t\tself.start_over()\n\t\n\t\n\tdef grab(self, callback):\n\t\tself._callback = callback\n\t\tself.start_over()\n\t\tself.gesture_grabber.set_transient_for(self.editor.window)\n\t\tself.gesture_grabber.show()\n\t\tself._create_gd()\n\t\n\t\n\tdef use(self, *a):\n\t\tself._callback(self._gesture)\n\t\tself.close()\n\t\n\t\n\tdef close(self, *a):\n\t\tif self._gd:\n\t\t\tself._gd.quit()\n\t\tself._gd = None\n\t\tself.gesture_grabber.hide()\n\t\treturn True\n\t\n\t\n\tdef start_over(self, *a):\n\t\tif self.editor.get_id() == \"RPAD\":\n\t\t\tself.lblGestureGrabberTitle.set_text(_(\"Draw gesture on RIGHT pad...\"))\n\t\telif self.editor.get_id() == \"CPAD\":\n\t\t\tself.lblGestureGrabberTitle.set_text(_(\"Draw gesture on Touchpad...\"))\n\t\telse:\n\t\t\tself.lblGestureGrabberTitle.set_text(_(\"Draw gesture on LEFT pad...\"))\n\t\tself.lblGestureStatus.set_label(\"\")\n\t\tself.txGestureGrab.set_text(\"\")\n\t\tself.rvGestureGrab.set_reveal_child(False)\n\t\tself._gesture = None\n\t\tself._repeats = 0\n\t\n\t\n\tdef _create_gd(self):\n\t\t\"\"\" Creates GestureDisplay object \"\"\"\n\t\tif self._gd:\n\t\t\tself._gd.quit()\n\t\tself._gd = GestureDisplay(self.editor.app.config)\n\t\tif self.editor.get_id() == \"RPAD\":\n\t\t\tself._gd.parse_argumets([ \"GestureDisplay\", \"--control-with\", \"RIGHT\" ])\n\t\telif self.editor.get_id() == \"CPAD\":\n\t\t\tself._gd.parse_argumets([ \"GestureDisplay\", \"--control-with\", \"CPAD\" ])\n\t\telse:\n\t\t\tself._gd.parse_argumets([ \"GestureDisplay\", \"--control-with\", \"LEFT\" ])\n\t\tself._gd.use_daemon(self.editor.app.dm)\n\t\tself._gd.show()\n\t\tself._gd.connect('gesture-updated', self.on_gesture_updated)\n\t\tself._gd.connect('destroy', self.on_gesture_recognized)\n\t\tself.lock_buttons()\n\t\n\t\n\tdef on_gesture_updated(self, gd, gstr):\n\t\ttxt = GestureComponent.nice_gstr(gstr)\n\t\tself.txGestureGrab.set_text(txt)\n\t\tself.txGestureGrab.set_position(len(txt))\n\t\n\t\n\tdef on_gesture_recognized(self, gd):\n\t\tself.disconnect_signals()\n\t\tif gd.get_exit_code() != 0:\n\t\t\t# Canceled or cannot grab controller\n\t\t\treturn\n\t\tif gd.get_gesture():\n\t\t\tself.on_gesture_updated(gd, gd.get_gesture())\n\t\t\tif self._gesture == None:\n\t\t\t\tself.lblGestureGrabberTitle.set_text(_(\"Repeat same gesture or press A button to confirm...\"))\n\t\t\t\tself.rvGestureGrab.set_reveal_child(True)\n\t\t\t\tself._gesture = gd.get_gesture()\n\t\t\telif self._gesture == gd.get_gesture():\n\t\t\t\tself._repeats += 1\n\t\t\t\tself.lblGestureStatus.set_label(_(\"Repeated %s times\") % (self._repeats,))\n\t\t\telse:\n\t\t\t\tself.lblGestureStatus.set_label(_(\"Gesture differs\"))\n\t\t\n\t\tself._create_gd()\n"
  },
  {
    "path": "scc/gui/ae/gyro.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Gyro -> Per Axis component\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.actions import Action, NoAction, AxisAction, MultiAction\nfrom scc.actions import GyroAction, GyroAbsAction, RangeOP\nfrom scc.modifiers import ModeModifier\nfrom scc.constants import SCButtons, STICK\nfrom scc.tools import ensure_size, nameof\nfrom scc.gui.ae.gyro_action import TRIGGERS, is_gyro_enable, fill_buttons\nfrom scc.gui.ae import AEComponent, describe_action\nfrom scc.gui.simple_chooser import SimpleChooser\n\nimport logging\nimport itertools\nlog = logging.getLogger(\"AE.Gyro\")\n\n__all__ = [ 'GyroComponent' ]\n\n\nclass GyroComponent(AEComponent):\n\tGLADE = \"ae/gyro.glade\"\n\tNAME = \"gyro\"\n\tCTXS = Action.AC_GYRO\n\tPRIORITY = 2\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tself._recursing = False\n\t\tself.axes = [ None, None, None ]\n\t\n\t\n\tdef load(self):\n\t\tif self.loaded : return\n\t\tAEComponent.load(self)\n\t\tcbGyroButton = self.builder.get_object(\"cbGyroButton\")\n\t\tself._recursing = True\n\t\tcbGyroButton = self.builder.get_object(\"cbGyroButton\")\n\t\tfill_buttons(cbGyroButton)\n\t\tself._recursing = False\n\t\tself.buttons = [ self.builder.get_object(x) for x in (\"btPitch\", \"btYaw\", \"btRoll\") ]\n\t\tself.cbs = [ self.builder.get_object(x) for x in (\"cbPitchAbs\", \"cbYawAbs\", \"cbRollAbs\") ]\n\t\tself.labels = [ self.builder.get_object(x) for x in (\"lblPitch\", \"lblYaw\", \"lblRoll\") ]\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif self.handles(mode, action):\n\t\t\tif isinstance(action, ModeModifier):\n\t\t\t\tself._recursing = True\n\t\t\t\tself.builder.get_object(\"cbInvertGyro\").set_active(bool(action.default))\n\t\t\t\tself._recursing = False\n\t\t\t\tb = next(itertools.islice(action.mods.keys(), 0, 1))\n\t\t\t\taction = action.mods[b] or action.default\n\t\t\t\tself.select_gyro_button(b)\n\t\t\telse:\n\t\t\t\tself.select_gyro_button(None)\n\t\t\t\n\t\t\tactions = [ action ]\n\t\t\tif isinstance(action, MultiAction):\n\t\t\t\tactions = action.actions\n\t\t\t\n\t\t\tself._recursing = True\n\t\t\tfor a in actions:\n\t\t\t\tif isinstance(a, GyroAction):\n\t\t\t\t\tpars = ensure_size(3, a.parameters)\n\t\t\t\t\tfor i in range(0, 3):\n\t\t\t\t\t\tif pars[i] is not None:\n\t\t\t\t\t\t\tself.axes[i] = pars[i]\n\t\t\t\t\t\t\tself.cbs[i].set_active(isinstance(a, GyroAbsAction))\n\t\t\tself.update()\n\t\t\tself._recursing = False\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Per Axis\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif is_gyro_enable(action):\n\t\t\taction = next(itertools.islice(action.mods.values(), 0, 1))\n\t\tif isinstance(action, GyroAction):\t# Takes GyroAbsAction as well\n\t\t\treturn True\n\t\tif isinstance(action, MultiAction):\n\t\t\tfor a in action.actions:\n\t\t\t\tif not isinstance(a, GyroAction):\n\t\t\t\t\treturn False\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef on_select_axis(self, source, *a):\n\t\ti = self.buttons.index(source)\n\t\tdef cb(action):\n\t\t\tself.axes[i] = action.parameters[0]\n\t\t\tself.update()\n\t\t\tself.send()\n\t\tb = SimpleChooser(self.app, \"axis\", cb)\n\t\tb.set_title(_(\"Select Axis\"))\n\t\tb.hide_mouse()\n\t\tb.display_action(Action.AC_STICK, AxisAction(self.axes[i]))\n\t\tb.show(self.editor.window)\n\t\n\t\n\tdef on_abs_changed(self, source, *a):\n\t\tif self._recursing : return\n\t\tself.send()\n\t\n\t\n\tdef select_gyro_button(self, item):\n\t\t\"\"\" Just sets combobox value \"\"\"\n\t\tcb = self.builder.get_object(\"cbGyroButton\")\n\t\trvSoftLevel = self.builder.get_object(\"rvSoftLevel\")\n\t\tsclSoftLevel = self.builder.get_object(\"sclSoftLevel\")\n\t\tlblSoftLevel = self.builder.get_object(\"lblSoftLevel\")\n\t\tmodel = cb.get_model()\n\t\tself._recursing = True\n\t\tbutton = None\n\t\tif isinstance(item, RangeOP):\n\t\t\tbutton = nameof(item.what)\n\t\t\tsclSoftLevel.set_value(item.value)\n\t\t\trvSoftLevel.set_reveal_child(True)\n\t\t\tif item.what == STICK:\n\t\t\t\tlblSoftLevel.set_label(_(\"Stick deadzone\"))\n\t\t\telse:\n\t\t\t\tlblSoftLevel.set_label(_(\"Trigger Pull Level\"))\n\t\telif item is not None:\n\t\t\tbutton = nameof(item.name)\n\t\tfor row in model:\n\t\t\tif button == row[0] and row[1] != None:\n\t\t\t\tcb.set_active_iter(row.iter)\n\t\t\t\tself._recursing = False\n\t\t\t\treturn\n\t\tself._recursing = False\n\t\n\t\n\tdef on_cbInvertGyro_toggled(self, cb, *a):\n\t\tlblGyroEnable = self.builder.get_object(\"lblGyroEnable\")\n\t\tif cb.get_active():\n\t\t\tlblGyroEnable.set_label(_(\"Gyro Disable Button\"))\n\t\telse:\n\t\t\tlblGyroEnable.set_label(_(\"Gyro Enable Button\"))\n\t\tif not self._recursing:\n\t\t\tself.send()\n\t\n\t\n\tdef on_sclSoftLevel_format_value(self, scale, value):\n\t\treturn  \"%s%%\" % (int(value * 100.0),)\n\t\n\t\n\tdef update(self, *a):\n\t\tfor i in range(0, 3):\n\t\t\tself.labels[i].set_label(describe_action(Action.AC_STICK, AxisAction, self.axes[i]))\n\t\n\t\n\tdef send(self, *a):\n\t\tif self._recursing : return\n\t\t\n\t\trvSoftLevel = self.builder.get_object(\"rvSoftLevel\")\n\t\tsclSoftLevel = self.builder.get_object(\"sclSoftLevel\")\n\t\tcbGyroButton = self.builder.get_object(\"cbGyroButton\")\n\t\tcbInvertGyro = self.builder.get_object(\"cbInvertGyro\")\n\t\titem = cbGyroButton.get_model().get_value(cbGyroButton.get_active_iter(), 0)\n\t\trvSoftLevel.set_reveal_child(item in TRIGGERS)\n\t\t\n\t\tnormal, n_set    = [ None, None, None ], False\n\t\tabsolute, a_set  = [ None, None, None ], False\n\t\t\n\t\tfor i in range(0, 3):\n\t\t\t# Fix case when axis id is zero (ABS_X)\n\t\t\tif self.axes[i] != None:\n\t\t\t\tif self.cbs[i].get_active():\n\t\t\t\t\tabsolute[i] = self.axes[i]\n\t\t\t\t\ta_set = True\n\t\t\t\telse:\n\t\t\t\t\tnormal[i] = self.axes[i]\n\t\t\t\t\tn_set = True\n\n\t\tif n_set and a_set:\n\t\t\taction = MultiAction(GyroAction(*normal), GyroAbsAction(*absolute))\n\t\telif n_set:\n\t\t\taction = GyroAction(*normal)\n\t\telif a_set:\n\t\t\taction = GyroAbsAction(*absolute)\n\t\telse:\n\t\t\taction = NoAction()\n\t\t\n\t\tif item and action:\n\t\t\twhat = getattr(SCButtons, item)\n\t\t\tif item in TRIGGERS:\n\t\t\t\twhat = RangeOP(what, \">=\", sclSoftLevel.get_value())\n\t\t\tif cbInvertGyro.get_active():\n\t\t\t\taction = ModeModifier(what, NoAction(), action)\n\t\t\telse:\n\t\t\t\taction = ModeModifier(what, action)\n\t\t\n\t\tself.editor.set_action(action)\n"
  },
  {
    "path": "scc/gui/ae/gyro_action.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Gyro -> Joystick or Mouse component\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.actions import Action, NoAction, MouseAction, MultiAction, RangeOP\nfrom scc.actions import GyroAction, GyroAbsAction, MouseAbsAction\nfrom scc.special_actions import CemuHookAction\nfrom scc.modifiers import ModeModifier, SensitivityModifier\nfrom scc.uinput import Axes, Rels\nfrom scc.constants import SCButtons, STICK, YAW, ROLL\nfrom scc.gui.parser import GuiActionParser\nfrom scc.gui.ae import AEComponent\nfrom scc.tools import nameof\n\nimport logging, re\nimport itertools\nlog = logging.getLogger(\"AE.GyroAction\")\n\n__all__ = [ 'GyroActionComponent' ]\nTRIGGERS = ( nameof(SCButtons.LT), nameof(SCButtons.RT) )\n\n\nclass GyroActionComponent(AEComponent):\n\tGLADE = \"ae/gyro_action.glade\"\n\tNAME = \"gyro_action\"\n\tCTXS = Action.AC_GYRO\n\tPRIORITY = 3\n\t\n\tBUTTONS = (\t# in order as displayed in combobox\n\t\t(None,\t\t\t\t\t_('Always Active')),\n\t\t(None, None),\n\t\t(SCButtons.LT,\t\t\t_('Left Trigger') ),\n\t\t(SCButtons.RT,\t\t\t_('Right Trigger') ),\n\t\t(SCButtons.LB,\t\t\t_('Left Bumper') ),\n\t\t(SCButtons.RB,\t\t\t_('Right Bumper') ),\n\t\t(None, None),\n\t\t(SCButtons.LPADTOUCH,\t_('Left Pad Touched') ),\n\t\t(SCButtons.RPADTOUCH,\t_('Right Pad Touched') ),\n\t\t(SCButtons.LPAD,\t\t_('Left Pad Pressed') ),\n\t\t(SCButtons.RPAD,\t\t_('Right Pad Pressed') ),\n\t\t(None, None),\n\t\t(SCButtons.LGRIP,\t\t_('Left Grip') ),\n\t\t(SCButtons.RGRIP,\t\t_('Right Grip') ),\n\t\t(STICK,\t\t\t\t\t_('Stick Tilted') ),\n\t\t(None, None),\n\t\t(SCButtons.A,\t\t\t_('A') ),\n\t\t(SCButtons.B,\t\t\t_('B') ),\n\t\t(SCButtons.X,\t\t\t_('X') ),\n\t\t(SCButtons.Y,\t\t\t_('Y') ),\n\t\t(None, None),\n\t\t(SCButtons.BACK,\t\t_('Back (select)') ),\n\t\t(SCButtons.C,\t\t\t_('Center') ),\n\t\t(SCButtons.START,\t\t_('Start') ),\n\t\t(None, None),\n\t\t(SCButtons.CPADTOUCH,\t_('Touch Touchpad')),\n\t\t(SCButtons.CPADPRESS,\t_('Press Touchpad')),\n\t)\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tself._recursing = False\n\t\tself.parser = GuiActionParser()\n\t\n\t\n\tdef load(self):\n\t\tif self.loaded : return\n\t\tAEComponent.load(self)\n\t\tself._recursing = True\n\t\tcbGyroButton = self.builder.get_object(\"cbGyroButton\")\n\t\tfill_buttons(cbGyroButton)\n\t\tself._recursing = False\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif self.handles(mode, action):\n\t\t\tif isinstance(action, NoAction):\n\t\t\t\tself.select_gyro_output(\"none\")\n\t\t\t\tself.select_gyro_button(SCButtons.RPADTOUCH)\n\t\t\t\treturn\n\t\t\tif isinstance(action, ModeModifier):\n\t\t\t\tself._recursing = True\n\t\t\t\tself.builder.get_object(\"cbInvertGyro\").set_active(bool(action.default))\n\t\t\t\tself._recursing = False\n\t\t\t\tb = next(itertools.islice(action.mods.keys(), 0, 1))\n\t\t\t\taction = action.mods[b] or action.default\n\t\t\t\tself.select_gyro_button(b)\n\t\t\telse:\n\t\t\t\tself.select_gyro_button(None)\n\t\t\tif isinstance(action, SensitivityModifier) and isinstance(action.action, MouseAction):\n\t\t\t\t# Mouse (Desktop)\n\t\t\t\tself.select_gyro_output(\"mouse\")\n\t\t\t\tif len(action.action.parameters) > 0 and action.action.parameters[0] == YAW:\n\t\t\t\t\tself.select_yaw_roll(YAW)\n\t\t\t\telse:\n\t\t\t\t\tself.select_yaw_roll(ROLL)\n\t\t\t\tself.editor.set_default_sensitivity(3.5, 3.5, 3.5)\n\t\t\t\tself.editor.set_sensitivity(*action.speeds)\n\t\t\telif isinstance(action, MouseAction):\n\t\t\t\t# Mouse (Camera)\n\t\t\t\tself.select_gyro_output(\"mouse_cam\")\n\t\t\t\tif len(action.parameters) > 0 and action.parameters[0] == YAW:\n\t\t\t\t\tself.select_yaw_roll(YAW)\n\t\t\t\telse:\n\t\t\t\t\tself.select_yaw_roll(ROLL)\n\t\t\telif isinstance(action, GyroAction):\n\t\t\t\tap = action.parameters\n\t\t\t\tif len(ap) == 2:\n\t\t\t\t\tself.select_yaw_roll(YAW)\n\t\t\t\telse:\n\t\t\t\t\tself.select_yaw_roll(ROLL)\n\t\t\t\tif ap[0] == Axes.ABS_X and ap[-1] == Axes.ABS_Y:\n\t\t\t\t\tif isinstance(action, GyroAbsAction):\n\t\t\t\t\t\tself.select_gyro_output(\"left_abs\")\n\t\t\t\t\telse:\n\t\t\t\t\t\tself.select_gyro_output(\"left\")\n\t\t\t\telif ap[0] == Axes.ABS_RX and ap[-1] == Axes.ABS_RY:\n\t\t\t\t\tif isinstance(action, GyroAbsAction):\n\t\t\t\t\t\tself.select_gyro_output(\"right_abs\")\n\t\t\t\t\telse:\n\t\t\t\t\t\tself.select_gyro_output(\"right\")\n\t\t\t\telif ap[0] == Rels.REL_Y and ap[-1] == Rels.REL_X:\n\t\t\t\t\tself.select_gyro_output(\"mouse_stick\")\n\t\t\telif isinstance(action, CemuHookAction):\n\t\t\t\t\tself.select_gyro_output(\"cemuhook\")\n\t\t\tself.modifier_updated()\n\t\n\t\n\tdef modifier_updated(self):\n\t\tcbInvertY = self.builder.get_object(\"cbInvertY\")\n\t\tsens = self.editor.get_sensitivity()\n\t\tinverted = len(sens) >= 2 and sens[1] < 0\n\t\tif cbInvertY.get_active() != inverted:\n\t\t\tself._recursing = True\n\t\t\tcbInvertY.set_active(inverted)\n\t\t\tself._recursing = False\n\n\t\tself.update()\n\t\n\t\n\tdef cbInvertY_toggled_cb(self, cb, *a):\n\t\tif self._recursing: return\n\t\tsens = list(self.editor.get_sensitivity())\n\t\t# Ensure that editor accepts Y sensitivity\n\t\tif len(sens) >= 2:\n\t\t\tsens[1] = abs(sens[1])\n\t\t\tif cb.get_active():\n\t\t\t\t# Ensure that Y sensitivity is negative\n\t\t\t\tsens[1] *= -1\n\t\tself.editor.set_sensitivity(*sens)\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Joystick or Mouse\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif isinstance(action, NoAction):\n\t\t\treturn True\n\t\tif is_gyro_enable(action):\n\t\t\taction = next(itertools.islice(action.mods.values(), 0, 1)) or action.default\n\t\t\tif isinstance(action, SensitivityModifier):\n\t\t\t\taction = action.action\n\t\tif isinstance(action, GyroAction):\t# Takes GyroAbsAction as well\n\t\t\tap = action.parameters\n\t\t\tif (len(ap) == 3 and not ap[1]) or len(ap) == 2:\n\t\t\t\tif ap[0] == Axes.ABS_X and ap[-1] == Axes.ABS_Y:\n\t\t\t\t\treturn True\n\t\t\t\telif ap[0] == Axes.ABS_RX and ap[-1] == Axes.ABS_RY:\n\t\t\t\t\treturn True\n\t\t\t\telif ap[0] == Rels.REL_Y and ap[-1] == Rels.REL_X:\n\t\t\t\t\treturn True\n\t\t\treturn False\n\t\tif isinstance(action, (MouseAction, MouseAbsAction, CemuHookAction)):\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef select_gyro_output(self, key):\n\t\t\"\"\" Just sets combobox value \"\"\"\n\t\tcb = self.builder.get_object(\"cbMode\")\n\t\tmodel = cb.get_model()\n\t\tself._recursing = True\n\t\tfor row in model:\n\t\t\tif key == row[2]:\n\t\t\t\tcb.set_active_iter(row.iter)\n\t\t\t\tself._recursing = False\n\t\t\t\treturn\n\t\tself._recursing = False\n\t\n\t\n\tdef select_yaw_roll(self, yawroll):\n\t\t\"\"\" Just sets combobox value \"\"\"\n\t\tcb = self.builder.get_object(\"cbYawRoll\")\n\t\tmodel = cb.get_model()\n\t\tself._recursing = True\n\t\tfor row in model:\n\t\t\tif yawroll == row[0]:\n\t\t\t\tcb.set_active_iter(row.iter)\n\t\t\t\tself._recursing = False\n\t\t\t\treturn\n\t\tself._recursing = False\n\t\n\t\n\tdef select_gyro_button(self, item):\n\t\t\"\"\" Just sets combobox value \"\"\"\n\t\tcb = self.builder.get_object(\"cbGyroButton\")\n\t\trvSoftLevel = self.builder.get_object(\"rvSoftLevel\")\n\t\tsclSoftLevel = self.builder.get_object(\"sclSoftLevel\")\n\t\tmodel = cb.get_model()\n\t\tself._recursing = True\n\t\tbutton = None\n\t\tif isinstance(item, RangeOP):\n\t\t\tbutton = nameof(item.what)\n\t\t\tsclSoftLevel.set_value(item.value)\n\t\t\trvSoftLevel.set_reveal_child(True)\n\t\telif item is not None:\n\t\t\tbutton = nameof(item.name)\n\t\tfor row in model:\n\t\t\tif button == row[0] and row[1] != None:\n\t\t\t\tcb.set_active_iter(row.iter)\n\t\t\t\tself._recursing = False\n\t\t\t\treturn\n\t\tself._recursing = False\n\t\n\t\n\tdef on_cbInvertGyro_toggled(self, cb, *a):\n\t\tlblGyroEnable = self.builder.get_object(\"lblGyroEnable\")\n\t\tif cb.get_active():\n\t\t\tlblGyroEnable.set_label(_(\"Gyro Disable Button\"))\n\t\telse:\n\t\t\tlblGyroEnable.set_label(_(\"Gyro Enable Button\"))\n\t\tif not self._recursing:\n\t\t\tself.send()\n\t\n\t\n\tdef on_sclSoftLevel_format_value(self, scale, value):\n\t\treturn  \"%s%%\" % (int(value * 100.0),)\n\t\n\t\n\tdef update(self, *a):\n\t\tcbMode = self.builder.get_object(\"cbMode\")\n\t\tcbYawRoll = self.builder.get_object(\"cbYawRoll\")\n\t\tlblYawRoll = self.builder.get_object(\"lblYawRoll\")\n\t\tkey = cbMode.get_model().get_value(cbMode.get_active_iter(), 2)\n\t\tcbYawRoll.set_sensitive(key != \"cemuhook\")\n\t\tlblYawRoll.set_sensitive(key != \"cemuhook\")\t\n\t\n\t\n\tdef hidden(self):\n\t\tself.editor.set_default_sensitivity(1, 1, 1)\n\t\n\t\n\tdef send(self, *a):\n\t\tif self._recursing : return\n\t\t\n\t\tcbMode = self.builder.get_object(\"cbMode\")\n\t\tcbYawRoll = self.builder.get_object(\"cbYawRoll\")\n\t\trvSoftLevel = self.builder.get_object(\"rvSoftLevel\")\n\t\tsclSoftLevel = self.builder.get_object(\"sclSoftLevel\")\n\t\tcbGyroButton = self.builder.get_object(\"cbGyroButton\")\n\t\tcbInvertGyro = self.builder.get_object(\"cbInvertGyro\")\n\t\tcbInvertGyro = self.builder.get_object(\"cbInvertGyro\")\n\t\taction = cbMode.get_model().get_value(cbMode.get_active_iter(), 0)\n\t\tkey = cbMode.get_model().get_value(cbMode.get_active_iter(), 2)\n\t\tyawroll = cbYawRoll.get_model().get_value(cbYawRoll.get_active_iter(), 0)\n\t\titem = cbGyroButton.get_model().get_value(cbGyroButton.get_active_iter(), 0)\n\t\trvSoftLevel.set_reveal_child(item in TRIGGERS)\n\t\t\n\t\tmatch = re.match(r\"([^\\[]+)\\[([^\\|]+)\\|([^\\]]+)\\](.*)\", action)\n\t\tif match:\n\t\t\tgrps = match.groups()\n\t\t\tif yawroll == YAW:\n\t\t\t\taction = \"%s%s%s\" % (grps[0], grps[1], grps[3])\n\t\t\telse:\n\t\t\t\taction = \"%s%s%s\" % (grps[0], grps[2], grps[3])\n\t\taction = self.parser.restart(action).parse()\n\t\t\n\t\tif item and action:\n\t\t\tif item in TRIGGERS:\n\t\t\t\twhat = RangeOP(getattr(SCButtons, item), \">=\", sclSoftLevel.get_value())\n\t\t\telif item == STICK:\n\t\t\t\twhat = RangeOP(item, \">=\", sclSoftLevel.get_value())\n\t\t\telse:\n\t\t\t\twhat = getattr(SCButtons, item)\n\t\t\tif cbInvertGyro.get_active():\n\t\t\t\taction = ModeModifier(what, NoAction(), action)\n\t\t\telse:\n\t\t\t\taction = ModeModifier(what, action)\n\t\tif key == \"mouse\":\n\t\t\tself.editor.set_default_sensitivity(3.5, 3.5, 3.5)\n\t\telse:\n\t\t\tself.editor.set_default_sensitivity(1, 1, 1)\n\t\t\n\t\tself.update()\n\t\tself.editor.set_action(action)\n\n\ndef is_gyro_enable(modemod):\n\t\"\"\" Returns True if ModeModifier instance is used to create \"Gyro Enable Button\" \"\"\"\n\tif isinstance(modemod, ModeModifier):\n\t\tif len(modemod.mods) != 1:\n\t\t\treturn False\n\t\taction = list(modemod.mods.values())[0]\n\t\tif modemod.default:\n\t\t\tif not action:\n\t\t\t\t# Possibly, default action is gyro and mode is NoAction.\n\t\t\t\t# That would mean that Gyro Disable button mode is used.\n\t\t\t\taction = modemod.default\n\t\t\telse:\n\t\t\t\treturn False\n\t\tif isinstance(action, SensitivityModifier):\n\t\t\taction = action.action\n\t\tif isinstance(action, ModeModifier):\n\t\t\treturn False\n\t\tif isinstance(action, MouseAction):\n\t\t\treturn True\n\t\tif isinstance(action, GyroAction):\t# Takes GyroAbsAction as well\n\t\t\treturn True\n\t\tif isinstance(action, MultiAction):\n\t\t\tfor a in action.actions:\n\t\t\t\tif not isinstance(a, GyroAction):\n\t\t\t\t\treturn False\n\t\t\treturn True\n\treturn False\n\n\ndef fill_buttons(cb):\n\tcb.set_row_separator_func( lambda model, iter : model.get_value(iter, 1) is None )\n\tmodel = cb.get_model()\n\tfor button, text in GyroActionComponent.BUTTONS:\n\t\tmodel.append(( None if button is None else nameof(button), text ))\t\n\tcb.set_active(0)\n"
  },
  {
    "path": "scc/gui/ae/menu_action.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - common part of \"DPAD or menu\" and \"Special Action\",\ntwo components with MenuAction selectable.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk\nfrom scc.special_actions import MenuAction, HorizontalMenuAction\nfrom scc.special_actions import RadialMenuAction, GridMenuAction\nfrom scc.special_actions import QuickMenuAction, PositionModifier\nfrom scc.constants import SCButtons, SAME, STICK, DEFAULT\nfrom scc.paths import get_menus_path\nfrom scc.actions import NoAction\nfrom scc.tools import nameof\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.gui.menu_editor import MenuEditor\nfrom scc.gui.parser import GuiActionParser\n\nimport os, logging\nlog = logging.getLogger(\"AE.Menu\")\n\n__all__ = [ 'MenuActionCofC' ]\n\n\nclass MenuActionCofC(UserDataManager):\n\t# CofC - Component of Component\n\tdef __init__(self):\n\t\tUserDataManager.__init__(self)\n\t\tself._current_menu = None\n\t\tself.parser = GuiActionParser()\n\t\tself.allow_globals = True\n\t\tself.allow_in_profile = True\n\t\n\t\n\tdef allow_menus(self, allow_globals, allow_in_profile):\n\t\t\"\"\"\n\t\tSets which type of menu should be selectable.\n\t\tBy default, both are enabled.\n\t\t\n\t\tReturns self.\n\t\t\"\"\"\n\t\tself.allow_globals = allow_globals\n\t\tself.allow_in_profile = allow_in_profile\n\t\treturn self\n\t\n\t\n\tdef set_selected_menu(self, menu):\n\t\t\"\"\"\n\t\tSets menu selected in combobox.\n\t\tReturns self.\n\t\t\"\"\"\n\t\tself._current_menu = menu\n\t\t# TODO: This currently works only if menu list is not yet loaded\n\t\n\t\n\t@staticmethod\n\tdef menu_class_to_key(action):\n\t\t\"\"\"\n\t\tFor subclass of MenuAction, returns correct key to be used in ListStore.\n\t\t\"\"\"\n\t\tif isinstance(action, GridMenuAction):\n\t\t\treturn \"gridmenu\"\n\t\telif isinstance(action, QuickMenuAction):\n\t\t\treturn \"quickmenu\"\n\t\telif isinstance(action, HorizontalMenuAction):\n\t\t\treturn \"hmenu\"\n\t\telif isinstance(action, RadialMenuAction):\n\t\t\treturn \"radialmenu\"\n\t\telse:\n\t\t\treturn \"menu\"\n\t\n\t\n\tdef load_menu_data(self, action):\n\t\tif isinstance(action, PositionModifier):\n\t\t\t# Load menu position modifier, if used\n\t\t\tx, y = action.position\n\t\t\tself.builder.get_object(\"cbMenuPosX\").set_active(0 if x >= 0 else 1)\n\t\t\tself.builder.get_object(\"cbMenuPosY\").set_active(0 if y >= 0 else 1)\n\t\t\tself.builder.get_object(\"spMenuPosX\").set_value(abs(x))\n\t\t\tself.builder.get_object(\"spMenuPosY\").set_value(abs(y))\n\t\t\taction = action.action\n\t\t\n\t\tself._current_menu = action.menu_id\n\t\tcbm = self.builder.get_object(\"cbMenuType\")\n\t\tself.set_cb(cbm, self.menu_class_to_key(action), 1)\n\t\t\n\t\tif self.builder.get_object(\"rvMenuSize\"):\n\t\t\tspMenuSize = self.builder.get_object(\"spMenuSize\")\n\t\t\tif self.update_size_display(action):\n\t\t\t\tsize = spMenuSize.get_adjustment().set_value(action.size)\n\t\t\n\t\tcbControlWith = self.builder.get_object(\"cbControlWith\")\n\t\tcbConfirmWith = self.builder.get_object(\"cbConfirmWith\")\n\t\tcbCancelWith = self.builder.get_object(\"cbCancelWith\")\n\t\tcbMenuAutoConfirm = self.builder.get_object(\"cbMenuAutoConfirm\")\n\t\tcbMenuConfirmWithClick = self.builder.get_object(\"cbMenuConfirmWithClick\")\n\t\tcbMenuAutoCancel = self.builder.get_object(\"cbMenuAutoCancel\")\n\t\tif cbControlWith:\n\t\t\tself.set_cb(cbControlWith, nameof(action.control_with), 1)\n\t\t\n\t\tcow = action.confirm_with\n\t\tcaw = action.cancel_with\n\t\t\n\t\tif cbConfirmWith:\n\t\t\tif cow == SAME and cbMenuAutoConfirm:\n\t\t\t\tcbMenuAutoConfirm.set_active(True)\n\t\t\t\tcbConfirmWith.set_sensitive(False)\n\t\t\telif cbMenuConfirmWithClick and cow == self.get_default_confirm():\n\t\t\t\tcbMenuConfirmWithClick.set_active(True)\n\t\t\t\tcbConfirmWith.set_sensitive(False)\n\t\t\telse:\n\t\t\t\tif cbMenuAutoConfirm:\n\t\t\t\t\tcbMenuAutoConfirm.set_active(False)\n\t\t\t\tif cbMenuConfirmWithClick:\n\t\t\t\t\tcbMenuAutoConfirm.set_active(False)\n\t\t\t\tcbConfirmWith.set_sensitive(True)\n\t\t\t\tself.set_cb(cbConfirmWith, nameof(cow), 1)\n\t\t\n\t\tif cbCancelWith:\n\t\t\tif caw == SAME and cbMenuAutoCancel:\n\t\t\t\tcbMenuAutoCancel.set_active(True)\n\t\t\t\tcbCancelWith.set_sensitive(False)\n\t\t\telse:\n\t\t\t\tif cbMenuAutoCancel:\n\t\t\t\t\tcbMenuAutoCancel.set_active(False)\n\t\t\t\tself.set_cb(cbCancelWith, nameof(caw), 1)\n\t\t\n\t\tself.on_cbMenus_changed()\n\t\n\t\n\tdef get_default_confirm(self):\n\t\t\"\"\"\n\t\tReturns DEFAULT, but may be overriden when default\n\t\tconfirm button is different - specifically when used with pads.\n\t\t\"\"\"\n\t\treturn DEFAULT\n\t\n\t\n\tdef get_default_cancel(self):\n\t\t\"\"\"\n\t\tReturns DEFAULT, but may be overriden when default\n\t\tconfirm button is different - specifically when used with pads.\n\t\t\"\"\"\n\t\treturn DEFAULT\n\t\n\t\n\tdef on_menu_changed(self, new_id):\n\t\tself._current_menu = new_id\n\t\tself.editor.set_action(MenuAction(new_id))\n\t\tself.load_menu_list()\n\t\n\t\n\tdef on_btEditMenu_clicked(self, *a):\n\t\tname = self.get_selected_menu()\n\t\tif name:\n\t\t\tlog.debug(\"Editing %s\", name)\n\t\t\tme = MenuEditor(self.app, self.on_menu_changed)\n\t\t\tid = self.get_selected_menu()\n\t\t\tlog.debug(\"Opening editor for menu ID '%s'\", id)\n\t\t\tme.set_menu(id)\n\t\t\tme.allow_menus(self.allow_globals, self.allow_in_profile)\n\t\t\tme.show(self.editor.window)\n\t\n\t\n\tdef on_cbMenus_button_press_event(self, trash, event):\n\t\tif event.button == 3:\n\t\t\tmnuMenu = self.builder.get_object(\"mnuMenu\")\n\t\t\tmnuMenu.popup(None, None, None, None,\n\t\t\t\t3, Gtk.get_current_event_time())\n\t\n\t\n\tdef on_mnuMenuNew_activate(self, *a):\n\t\tself.on_new_menu_selected()\n\t\n\t\n\tdef on_mnuMenuCopy_activate(self, *a):\n\t\tself.on_new_menu_selected(make_copy=True)\n\t\n\t\n\tdef on_mnuMenuRename_activate(self, *a):\n\t\tself.on_btEditMenu_clicked()\n\t\n\t\n\tdef on_mnuMenuDelete_activate(self, *a):\n\t\tid = self.get_selected_menu()\n\t\tif MenuEditor.menu_is_global(id):\n\t\t\ttext = _(\"Really delete selected global menu?\")\n\t\telse:\n\t\t\ttext = _(\"Really delete selected menu?\")\n\t\t\n\t\td = Gtk.MessageDialog(parent=self.editor.window,\n\t\t\tflags = Gtk.DialogFlags.MODAL,\n\t\t\ttype = Gtk.MessageType.WARNING,\n\t\t\tbuttons = Gtk.ButtonsType.OK_CANCEL,\n\t\t\tmessage_format = text,\n\t\t)\n\t\t\n\t\tif MenuEditor.menu_is_global(id):\n\t\t\td.format_secondary_text(_(\"This action is not undoable!\"))\n\t\t\n\t\tif d.run() == -5: # OK button, no idea where is this defined...\n\t\t\tif MenuEditor.menu_is_global(id):\n\t\t\t\tfname = os.path.join(get_menus_path(), id)\n\t\t\t\ttry:\n\t\t\t\t\tos.unlink(fname)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.error(\"Failed to remove %s: %s\", fname, e)\n\t\t\telse:\n\t\t\t\tdel self.app.current.menus[id]\n\t\t\t\tself.app.on_profile_modified()\n\t\t\tself.load_menu_list()\n\t\td.destroy()\n\t\n\t\n\tdef on_menus_loaded(self, menus):\n\t\tcb = self.builder.get_object(\"cbMenus\")\n\t\tcb.set_row_separator_func( lambda model, iter : model.get_value(iter, 1) is None )\n\t\tmodel = cb.get_model()\n\t\tmodel.clear()\n\t\ti, current_index = 0, 0\n\t\tif self.allow_in_profile:\n\t\t\t# Add menus from profile\n\t\t\tfor key in sorted(self.app.current.menus):\n\t\t\t\tmodel.append((key, key))\n\t\t\t\tif self._current_menu == key:\n\t\t\t\t\tcurrent_index = i\n\t\t\t\ti += 1\n\t\t\tif i > 0:\n\t\t\t\tmodel.append((None, None))\t# Separator\n\t\t\t\ti += 1\n\t\tif self.allow_globals:\n\t\t\tfor f in menus:\n\t\t\t\tkey = f.get_basename()\n\t\t\t\tname = key\n\t\t\t\tif name.startswith(\".\"): continue\n\t\t\t\tif \".\" in name:\n\t\t\t\t\tname = _(\"%s (global)\" % (name.split(\".\")[0]))\n\t\t\t\tmodel.append((name, key))\n\t\t\t\tif self._current_menu == key:\n\t\t\t\t\tcurrent_index = i\n\t\t\t\ti += 1\n\t\tif i > 0:\n\t\t\tmodel.append((None, None))\t# Separator\n\t\tmodel.append(( _(\"New Menu...\"), \"\" ))\n\t\t\n\t\tself._recursing = True\n\t\tcb.set_active(current_index)\n\t\tself._recursing = False\n\t\tname = self.get_selected_menu()\n\t\tif name:\n\t\t\tself.builder.get_object(\"btEditMenu\").set_sensitive(name not in MenuEditor.OPEN)\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif isinstance(action, PositionModifier):\n\t\t\treturn isinstance(action.action, MenuAction)\n\t\treturn isinstance(action, MenuAction)\n\t\n\t\n\tdef get_selected_menu(self):\n\t\tcb = self.builder.get_object(\"cbMenus\")\n\t\tmodel = cb.get_model()\n\t\titer = cb.get_active_iter()\n\t\tif iter is None:\n\t\t\t# Empty list\n\t\t\treturn None\n\t\treturn model.get_value(iter, 1)\n\t\n\t\n\tdef prevent_confirm_cancel_nonsense(self, widget, *a):\n\t\t\"\"\"\n\t\tIf 'confirm with click', 'confirm with release' and\n\t\t'cbMenuAutoCancel' are all present, this method prevents them from\n\t\tbeing checked in nonsensical way.\n\t\t\"\"\"\n\t\tcbMenuConfirmWithClick = self.builder.get_object(\"cbMenuConfirmWithClick\")\n\t\tcbMenuAutoConfirm = self.builder.get_object(\"cbMenuAutoConfirm\")\n\t\tcbMenuAutoCancel = self.builder.get_object(\"cbMenuAutoCancel\")\n\t\tif widget.get_active():\n\t\t\tif widget == cbMenuConfirmWithClick:\n\t\t\t\tif cbMenuAutoConfirm:\n\t\t\t\t\tcbMenuAutoConfirm.set_active(False)\n\t\t\telif widget == cbMenuAutoConfirm:\n\t\t\t\tif cbMenuConfirmWithClick:\n\t\t\t\t\tcbMenuConfirmWithClick.set_active(False)\n\t\t\t\tif cbMenuAutoCancel:\n\t\t\t\t\tcbMenuAutoCancel.set_active(False)\n\t\t\telif widget == cbMenuAutoCancel:\n\t\t\t\tif cbMenuAutoConfirm:\n\t\t\t\t\tcbMenuAutoConfirm.set_active(False)\n\t\n\t\n\tdef on_cbMenus_changed(self, *a):\n\t\t\"\"\" Called when user changes any menu settings \"\"\"\n\t\tif self._recursing : return\n\t\tcbMenuConfirmWithClick = self.builder.get_object(\"cbMenuConfirmWithClick\")\n\t\tcbMenuAutoConfirm = self.builder.get_object(\"cbMenuAutoConfirm\")\n\t\tcbMenuAutoCancel = self.builder.get_object(\"cbMenuAutoCancel\")\n\t\tlblControlWith = self.builder.get_object(\"lblControlWith\")\n\t\tcbControlWith = self.builder.get_object(\"cbControlWith\")\n\t\tlblConfirmWith = self.builder.get_object(\"lblConfirmWith\")\n\t\tcbConfirmWith = self.builder.get_object(\"cbConfirmWith\")\n\t\tlblCancelWith = self.builder.get_object(\"lblCancelWith\")\n\t\tcbCancelWith = self.builder.get_object(\"cbCancelWith\")\n\t\t\n\t\tcbm = self.builder.get_object(\"cbMenuType\")\n\t\tmenu_type = cbm.get_model().get_value(cbm.get_active_iter(), 1)\n\t\t\n\t\tif cbControlWith:\n\t\t\tsensitive = True\n\t\t\tif menu_type == \"quickmenu\":\n\t\t\t\tsensitive = False\n\t\t\tlblControlWith.set_sensitive(sensitive)\n\t\t\tcbControlWith.set_sensitive(sensitive)\n\t\t\n\t\tif cbConfirmWith:\n\t\t\tsensitive = True\n\t\t\tif cbMenuAutoConfirm and cbMenuAutoConfirm.get_active():\n\t\t\t\tsensitive = False\n\t\t\tif cbMenuConfirmWithClick and cbMenuConfirmWithClick.get_active():\n\t\t\t\tsensitive = False\n\t\t\tif menu_type == \"quickmenu\":\n\t\t\t\tsensitive = False\n\t\t\tlblConfirmWith.set_sensitive(sensitive)\n\t\t\tcbConfirmWith.set_sensitive(sensitive)\n\t\t\n\t\tif cbCancelWith:\n\t\t\tsensitive = True\n\t\t\tif cbMenuAutoCancel and cbMenuAutoCancel.get_active():\n\t\t\t\tsensitive = False\n\t\t\tif menu_type == \"quickmenu\":\n\t\t\t\tsensitive = False\n\t\t\tlblCancelWith.set_sensitive(sensitive)\n\t\t\tcbCancelWith.set_sensitive(sensitive)\n\t\t\n\t\tif cbMenuAutoConfirm:\n\t\t\tsensitive = True\n\t\t\tif menu_type == \"quickmenu\":\n\t\t\t\tsensitive = False\n\t\t\tcbMenuAutoConfirm.set_sensitive(sensitive)\n\t\t\n\t\tname = self.get_selected_menu()\n\t\tif name == \"\":\n\t\t\treturn self.on_new_menu_selected()\n\t\tif name:\n\t\t\t# There is some menu choosen\n\t\t\tself.builder.get_object(\"btEditMenu\").set_sensitive(name not in MenuEditor.OPEN)\n\t\t\tparams = [ name ]\n\t\t\t\n\t\t\tcow = SAME\n\t\t\tif cbMenuAutoConfirm and cbMenuAutoConfirm.get_active():\n\t\t\t\tcow = SAME\n\t\t\telif cbMenuConfirmWithClick and cbMenuConfirmWithClick.get_active():\n\t\t\t\tcow = DEFAULT\n\t\t\telif cbConfirmWith:\n\t\t\t\tcow = cbConfirmWith.get_model().get_value(cbConfirmWith.get_active_iter(), 1)\n\t\t\t\tif cow != DEFAULT:\n\t\t\t\t\tcow = getattr(SCButtons, cow)\n\t\t\t\n\t\t\tcaw = DEFAULT\n\t\t\tif cbMenuAutoCancel and cbMenuAutoCancel.get_active():\n\t\t\t\tcaw = DEFAULT\n\t\t\telif cbCancelWith:\n\t\t\t\tcaw = cbCancelWith.get_model().get_value(cbCancelWith.get_active_iter(), 1)\n\t\t\t\tif caw != DEFAULT:\n\t\t\t\t\tcaw = getattr(SCButtons, caw)\n\t\t\t\n\t\t\tparams += [ self.get_control_with(), cow, caw ]\n\t\t\t\n\t\t\t\n\t\t\t# Hide / apply and display 'Items per row' selector if it exists in UI\n\t\t\tif self.builder.get_object(\"rvMenuSize\"):\n\t\t\t\tspMenuSize = self.builder.get_object(\"spMenuSize\")\n\t\t\t\tmenu_type = cbm.get_model().get_value(cbm.get_active_iter(), 1)\n\t\t\t\tif menu_type == \"gridmenu\":\n\t\t\t\t\tself.update_size_display(GridMenuAction(\"dummy\"))\n\t\t\t\t\tsize = int(spMenuSize.get_adjustment().get_value())\n\t\t\t\t\tif size > 0:\n\t\t\t\t\t\t# size is 2nd parameter\n\t\t\t\t\t\tparams += [ False, size ]\n\t\t\t\telif menu_type == \"radialmenu\":\n\t\t\t\t\tself.update_size_display(RadialMenuAction(\"dummy\"))\n\t\t\t\t\tsize = int(spMenuSize.get_adjustment().get_value())\n\t\t\t\t\tif size > 0 and size < 100:\n\t\t\t\t\t\t# Both 0 and 100 means default here\n\t\t\t\t\t\t# size is 2nd parameter\n\t\t\t\t\t\tparams += [ False, size ]\n\t\t\t\telif menu_type == \"hmenu\":\n\t\t\t\t\tself.update_size_display(HorizontalMenuAction(\"dummy\"))\n\t\t\t\t\tsize = int(spMenuSize.get_adjustment().get_value())\n\t\t\t\t\tif size > 1:\n\t\t\t\t\t\t# Size 0 and 1 means default here\n\t\t\t\t\t\t# size is 2nd parameter\n\t\t\t\t\t\tparams += [ False, size ]\n\t\t\t\telse:\n\t\t\t\t\t# , \"radialmenu\"):\n\t\t\t\t\tself.update_size_display(None)\n\t\t\t\n\t\t\t# Grab menu type and choose apropriate action\n\t\t\taction = NoAction()\n\t\t\tif cbm and menu_type == \"gridmenu\":\n\t\t\t\t# Grid menu\n\t\t\t\taction = GridMenuAction(*params)\n\t\t\telif cbm and menu_type == \"radialmenu\":\n\t\t\t\t# Circular menu\n\t\t\t\taction = RadialMenuAction(*params)\n\t\t\telif cbm and menu_type == \"hmenu\":\n\t\t\t\t# Horizontal menu\n\t\t\t\taction = HorizontalMenuAction(*params)\n\t\t\telif cbm and menu_type == \"quickmenu\":\n\t\t\t\t# Horizontal menu\n\t\t\t\taction = QuickMenuAction(name)\n\t\t\telse:\n\t\t\t\t# Normal menu\n\t\t\t\taction = MenuAction(*params)\n\t\t\t\n\t\t\t# Apply Menu Position options, if such block exists in UI\n\t\t\tif self.builder.get_object(\"spMenuPosX\"):\n\t\t\t\tcbMenuPosX = self.builder.get_object(\"cbMenuPosX\")\n\t\t\t\tcbMenuPosY = self.builder.get_object(\"cbMenuPosY\")\n\t\t\t\tx = int(self.builder.get_object(\"spMenuPosX\").get_value())\n\t\t\t\ty = int(self.builder.get_object(\"spMenuPosY\").get_value())\n\t\t\t\tx *= cbMenuPosX.get_model().get_value(cbMenuPosX.get_active_iter(), 0)\n\t\t\t\ty *= cbMenuPosY.get_model().get_value(cbMenuPosY.get_active_iter(), 0)\n\t\t\t\tif (x, y) != MenuAction.DEFAULT_POSITION:\n\t\t\t\t\taction = PositionModifier(x, y, action)\n\t\t\t\n\t\t\tself.editor.set_action(action)\n\t\n\t\n\tdef on_new_menu_selected(self, make_copy=False):\n\t\t# 'New menu' selected\n\t\tself.load_menu_list()\n\t\tlog.debug(\"Creating editor for new menu\")\n\t\tme = MenuEditor(self.app, self.on_menu_changed)\n\t\tif make_copy:\n\t\t\tname = self.get_selected_menu()\n\t\t\tlog.debug(\"Copying %s\", name)\n\t\t\tme = MenuEditor(self.app, self.on_menu_changed)\n\t\t\tme.set_menu(name)\n\t\tme.set_new_menu()\n\t\tme.allow_menus(self.allow_globals, self.allow_in_profile)\n\t\tme.show(self.editor.window)\n\t\n\t\n\tdef update_size_display(self, action):\n\t\t\"\"\"\n\t\tDisplays or hides menu size area and upadates text displayed in it.\n\t\tReturns True if action is menuaction where changing size makes sense\n\t\t\"\"\"\n\t\trvMenuSize = self.builder.get_object(\"rvMenuSize\")\n\t\tlblMenuSize = self.builder.get_object(\"lblMenuSize\")\n\t\tspMenuSize = self.builder.get_object(\"spMenuSize\")\n\t\tsclMenuSize = self.builder.get_object(\"sclMenuSize\")\n\t\tif isinstance(action, GridMenuAction):\n\t\t\tspMenuSize.set_visible(True)\n\t\t\tsclMenuSize.set_visible(False)\n\t\t\tlblMenuSize.set_text(_(\"Items per row\"))\n\t\t\trvMenuSize.set_reveal_child(True)\n\t\t\treturn True\n\t\telif isinstance(action, (RadialMenuAction, HorizontalMenuAction)):\n\t\t\tspMenuSize.set_visible(False)\n\t\t\tsclMenuSize.set_visible(True)\n\t\t\tlblMenuSize.set_text(_(\"Size\"))\n\t\t\trvMenuSize.set_reveal_child(True)\n\t\t\treturn True\n\t\telse:\n\t\t\trvMenuSize.set_reveal_child(False)\n\t\t\treturn False\t\n\t\n\t\n\tdef get_control_with(self):\n\t\t\"\"\" Returns value of \"Control With\" combo or STICK if there is none \"\"\"\n\t\tcbControlWith = self.builder.get_object(\"cbControlWith\")\n\t\tif cbControlWith:\n\t\t\treturn cbControlWith.get_model().get_value(cbControlWith.get_active_iter(), 1)\n\t\treturn STICK\n\t\n\t\n\tdef on_spMenuSize_format_value(self, spinner):\n\t\tval = int(spinner.get_adjustment().get_value())\n\t\tif val < 1:\n\t\t\tspinner.get_buffer().set_text(_(\"auto\"), -1)\n\t\telse:\n\t\t\tspinner.get_buffer().set_text(str(val), -1)\n\t\treturn True\n\t\n\t\n\tdef on_sclMenuSize_format_value(self, scale, val):\n\t\tcbm = self.builder.get_object(\"cbMenuType\")\n\t\tmenu_type = cbm.get_model().get_value(cbm.get_active_iter(), 1)\n\t\tif menu_type == \"radialmenu\":\n\t\t\tif val < 1:\n\t\t\t\treturn _(\"default\")\n\t\t\treturn  \"%s%%\" % (int(val),)\n\t\telse: # if menu_type == \"hmenu\"\n\t\t\tval = int(val)\n\t\t\tif val < 2:\n\t\t\t\treturn _(\"default\")\n\t\t\treturn str(int(val))\n"
  },
  {
    "path": "scc/gui/ae/menu_only.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Menu Only Component\n\nDisplays page that can edito only MenuAction\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.special_actions import MenuAction, PositionModifier\nfrom scc.actions import Action\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.gui.ae.menu_action import MenuActionCofC\nfrom scc.gui.ae import AEComponent\n\nimport os, logging\nlog = logging.getLogger(\"AE.SA\")\n\n__all__ = [ 'MenuOnlyComponent' ]\n\n\nclass MenuOnlyComponent(AEComponent, MenuActionCofC):\n\tGLADE = \"ae/menu_only.glade\"\n\tNAME = \"menu_only\"\n\tCTXS = Action.AC_MENU\n\tPRIORITY = 0\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tMenuActionCofC.__init__(self)\n\t\tself._userdata_load_started = False\n\t\tself._recursing = False\n\t\n\t\n\tdef shown(self):\n\t\tif not self._userdata_load_started:\n\t\t\tself._userdata_load_started = True\n\t\t\tself.load_menu_list()\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif isinstance(action, PositionModifier):\n\t\t\taction = action.action\n\t\tif isinstance(action, MenuAction):\n\t\t\tself._current_menu = action.menu_id\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Menu\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\t\"\"\" Not visible by default \"\"\"\n\t\treturn False\n"
  },
  {
    "path": "scc/gui/ae/osk_action.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - On Screen Keyboard Action Component\n\nAssigns actions from scc.osd.osk_actions\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action, NoAction, ButtonAction\nfrom scc.constants import LEFT, RIGHT\nfrom scc.uinput import Keys\nfrom scc.gui.ae import AEComponent\nfrom scc.gui.parser import GuiActionParser\nfrom scc.osd.osk_actions import OSKAction, CloseOSKAction, OSKCursorAction\nfrom scc.osd.osk_actions import MoveOSKAction, OSKPressAction\n\nimport os, logging\nlog = logging.getLogger(\"AE.SA\")\n\n__all__ = [ 'OSKActionComponent' ]\n\n\nclass OSKActionComponent(AEComponent):\n\tGLADE = \"ae/osk_action.glade\"\n\tNAME = \"osk_action\"\n\tCTXS = Action.AC_OSK\n\tPRIORITY = 2\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tself._recursing = False\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\tif isinstance(action, CloseOSKAction):\n\t\t\tself.set_cb(cb, \"OSK.close()\")\n\t\telif isinstance(action, OSKCursorAction) and action.side == LEFT:\n\t\t\tself.set_cb(cb, \"OSK.cursor(LEFT)\")\n\t\telif isinstance(action, OSKCursorAction): # and action.side == RIGHT:\n\t\t\tself.set_cb(cb, \"OSK.cursor(RIGHT)\")\n\t\telif isinstance(action, OSKPressAction) and action.side == LEFT:\n\t\t\tself.set_cb(cb, \"OSK.press(LEFT)\")\n\t\telif isinstance(action, OSKPressAction): # and action.side == RIGHT:\n\t\t\tself.set_cb(cb, \"OSK.press(RIGHT)\")\n\t\telif isinstance(action, MoveOSKAction):\n\t\t\tself.set_cb(cb, \"OSK.move()\")\n\t\tif isinstance(action, ButtonAction):\n\t\t\tif action.button == Keys.BTN_LEFT:\n\t\t\t\tself.set_cb(cb, \"button(Keys.BTN_LEFT)\")\n\t\t\telif action.button == Keys.BTN_RIGHT:\n\t\t\t\tself.set_cb(cb, \"button(Keys.BTN_RIGHT)\")\n\t\telse:\n\t\t\tself.set_cb(cb, \"None\")\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"On-Screen Keyboard\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif isinstance(action, ButtonAction):\n\t\t\treturn action.button in ( Keys.BTN_LEFT, Keys.BTN_RIGHT )\n\t\treturn isinstance(action, (NoAction, OSKAction, OSKCursorAction))\n\t\n\t\n\tdef on_cbActionType_changed(self, *a):\n\t\tcbActionType = self.builder.get_object(\"cbActionType\")\n\t\tkey = cbActionType.get_model().get_value(cbActionType.get_active_iter(), 0)\n\t\tself.editor.set_action(GuiActionParser().restart(key).parse())\n\t\t"
  },
  {
    "path": "scc/gui/ae/osk_buttons.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - OSK Button Component\n\nBinds controller buttons on on on on on... screen keyboard.\nRetuses ButtonsComponent, but hides image, so user can't select mouse or gamepad\nbutton.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action, ButtonAction, MultiAction, NoAction\nfrom scc.gui.area_to_action import action_to_area\nfrom scc.gui.ae.buttons import ButtonsComponent\nfrom scc.gui.key_grabber import KeyGrabber\nfrom scc.gui.parser import InvalidAction\nfrom scc.gui.chooser import Chooser\nfrom scc.gui.ae import AEComponent\n\nimport os, logging\nlog = logging.getLogger(\"AE.Buttons\")\n\n__all__ = [ 'OSKButtonsComponent' ]\n\n\nclass OSKButtonsComponent(ButtonsComponent):\n\tCTXS = Action.AC_OSK\n\tPRIORITY = 1\n\tIMAGES = { }\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Key\")\n\t\n\t\n\tdef load(self):\n\t\tif not self.loaded:\n\t\t\tAEComponent.load(self)\n\t\t\tself.builder.get_object(\"lblClickAnyButton\").set_visible(False)\n"
  },
  {
    "path": "scc/gui/ae/per_axis.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Per-Axis Component\n\nHandles all XYActions\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action, NoAction, XYAction\nfrom scc.gui.ae import AEComponent, describe_action\nfrom scc.gui.area_to_action import action_to_area\nfrom scc.gui.simple_chooser import SimpleChooser\nfrom scc.gui.parser import GuiActionParser\n\nimport os, logging\nlog = logging.getLogger(\"AE.PerAxis\")\n\n__all__ = [ 'PerAxisComponent' ]\n\n\nclass PerAxisComponent(AEComponent):\n\tGLADE = \"ae/per_axis.glade\"\n\tNAME = \"per_axis\"\n\tCTXS = Action.AC_STICK | Action.AC_PAD\n\tPRIORITY = 1\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tself.x = self.y = NoAction()\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif isinstance(action, XYAction):\n\t\t\tself.x = action.x\n\t\t\tself.y = action.y\n\t\t\tself.update()\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Per Axis\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\treturn isinstance(action, XYAction)\n\t\n\t\n\tdef update(self):\n\t\tself.builder.get_object(\"lblAxisX\").set_label(describe_action(Action.AC_STICK, None, self.x))\n\t\tself.builder.get_object(\"lblAxisY\").set_label(describe_action(Action.AC_STICK, None, self.y))\n\t\n\t\n\tdef send(self):\n\t\tself.editor.set_action(XYAction(self.x, self.y))\n\t\n\t\n\tdef on_btAxisX_clicked(self, *a):\n\t\t\"\"\" 'Select X Axis Action' handler \"\"\"\n\t\tdef cb(action):\n\t\t\tself.x = action\n\t\t\tself.update()\n\t\t\tself.send()\n\t\tself.grab_action(self.x, cb)\n\t\n\t\n\tdef on_btAxisY_clicked(self, *a):\n\t\t\"\"\" 'Select Y Axis Action' handler \"\"\"\n\t\tdef cb(action):\n\t\t\tself.y = action\n\t\t\tself.update()\n\t\t\tself.send()\n\t\tself.grab_action(self.y, cb)\n\t\n\t\n\tdef grab_action(self, action, cb):\n\t\tb = SimpleChooser(self.app, \"axis\", cb)\n\t\tb.set_title(_(\"Select Axis\"))\n\t\tarea = action_to_area(action)\n\t\tb.display_action(Action.AC_STICK, area)\n\t\tb.show(self.editor.window)\n\t"
  },
  {
    "path": "scc/gui/ae/recent_list.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Recent List Component\n\nDisplays page that can edit settings for RecentListMenuGenerator\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.gui.ae import AEComponent\n\nimport os, logging\nlog = logging.getLogger(\"AE.SA\")\n\n__all__ = [ 'RecentListGenComponent' ]\n\n\nclass RecentListGenComponent(AEComponent):\n\tGLADE = \"ae/recent_list.glade\"\n\tNAME = \"recent_list\"\n\tCTXS = 0\n\tPRIORITY = 0\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tpass\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Recent List\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\t\"\"\" Not visible by default \"\"\"\n\t\treturn False\n\t\n\t\n\tdef set_row_count(self, count):\n\t\tself.builder.get_object(\"sclNumOfProfiles\").set_value(count)\n\t\n\t\n\tdef get_row_count(self):\n\t\treturn int(self.builder.get_object(\"sclNumOfProfiles\").get_value())\n"
  },
  {
    "path": "scc/gui/ae/special_action.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Axis Component\n\nAssigns emulated axis to trigger\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.special_actions import ChangeProfileAction, ShellCommandAction\nfrom scc.special_actions import TurnOffAction, KeyboardAction, OSDAction\nfrom scc.special_actions import ClearOSDAction\nfrom scc.actions import Action, NoAction, ResetGyroAction\nfrom scc.gui.ae.menu_action import MenuActionCofC\nfrom scc.gui.ae import AEComponent\n\nimport logging\nlog = logging.getLogger(\"AE.SA\")\n\n__all__ = [ 'SpecialActionComponent' ]\n\n\nclass SpecialActionComponent(AEComponent, MenuActionCofC):\n\tGLADE = \"ae/special_action.glade\"\n\tNAME = \"special_action\"\n\tCTXS = Action.AC_BUTTON | Action.AC_MENU\n\tPRIORITY = 0\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tMenuActionCofC.__init__(self)\n\t\tself._userdata_load_started = False\n\t\tself._recursing = False\n\t\tself._current_profile = None\n\t\n\t\n\tdef load(self):\n\t\tif self.loaded : return\n\t\tAEComponent.load(self)\n\t\tcbConfirmWith = self.builder.get_object(\"cbConfirmWith\")\n\t\tcbCancelWith = self.builder.get_object(\"cbCancelWith\")\n\t\tcbConfirmWith.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == \"-\" )\n\t\tcbCancelWith.set_row_separator_func( lambda model, iter : model.get_value(iter, 0)  == \"-\" )\n\t\n\t\n\tdef shown(self):\n\t\tif not self._userdata_load_started:\n\t\t\tself._userdata_load_started = True\n\t\t\tself.load_profile_list()\n\t\t\tself.load_menu_list()\n\t\n\t\n\tdef confirm_with_same_active(self):\n\t\tcbMenuAutoConfirm = self.builder.get_object(\"cbMenuAutoConfirm\")\n\t\treturn cbMenuAutoConfirm.get_active()\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif self.handles(mode, action):\n\t\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\t\tif isinstance(action, TurnOffAction):\n\t\t\t\tself.set_cb(cb, \"turnoff\")\n\t\t\telif isinstance(action, ShellCommandAction):\n\t\t\t\tself.set_cb(cb, \"shell\")\n\t\t\t\tenCommand = self.builder.get_object(\"enCommand\")\n\t\t\t\tenCommand.set_text(action.command.encode(\"utf-8\"))\n\t\t\telif isinstance(action, ResetGyroAction):\n\t\t\t\tself.set_cb(cb, \"resetgyro\")\n\t\t\telif isinstance(action, ChangeProfileAction):\n\t\t\t\tself._current_profile = action.profile\n\t\t\t\tself.set_cb(cb, \"profile\")\n\t\t\telif MenuActionCofC.handles(self, None, action):\n\t\t\t\tself.set_cb(cb, \"menu\")\n\t\t\t\tself.load_menu_data(action)\n\t\t\telif isinstance(action, KeyboardAction):\n\t\t\t\tself.set_cb(cb, \"keyboard\")\n\t\t\telif isinstance(action, OSDAction):\n\t\t\t\tself.set_cb(cb, \"osd\")\n\t\t\t\tsclOSDTimeout = self.builder.get_object(\"sclOSDTimeout\")\n\t\t\t\tenOSDText = self.builder.get_object(\"enOSDText\")\n\t\t\t\tcbOSDSize = self.builder.get_object(\"cbOSDSize\")\n\t\t\t\tself._recursing = True\n\t\t\t\tsclOSDTimeout.set_value(action.timeout or 60.1)\n\t\t\t\tenOSDText.set_text(action.text)\n\t\t\t\tself.set_cb(cbOSDSize, action.size)\n\t\t\t\tself._recursing = False\n\t\t\telif isinstance(action, ClearOSDAction):\n\t\t\t\tself.set_cb(cb, \"clearosd\")\n\t\t\telse:\n\t\t\t\tself.set_cb(cb, \"none\")\n\t\n\t\n\tdef on_profiles_loaded(self, profiles):\n\t\tcb = self.builder.get_object(\"cbProfile\")\n\t\tmodel = cb.get_model()\n\t\tmodel.clear()\n\t\ti, current_index = 0, 0\n\t\tfor f in sorted(profiles, key=lambda f: f.get_basename()):\n\t\t\tname = f.get_basename()\n\t\t\tif name.endswith(\".mod\"):\n\t\t\t\tcontinue\n\t\t\tif name.startswith(\".\"):\n\t\t\t\tcontinue\n\t\t\tif name.endswith(\".sccprofile\"):\n\t\t\t\tname = name[0:-11]\n\t\t\tif name == self._current_profile:\n\t\t\t\tcurrent_index = i\n\t\t\tmodel.append((name, f, None))\n\t\t\ti += 1\n\t\t\n\t\tself._recursing = True\n\t\tcb.set_active(current_index)\n\t\tself._recursing = False\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Special Action\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif MenuActionCofC.handles(self, mode, action):\n\t\t\treturn True\n\t\tif isinstance(action, OSDAction) and action.action is None:\n\t\t\treturn True\n\t\treturn isinstance(action, (NoAction, TurnOffAction, ShellCommandAction,\n\t\t\tChangeProfileAction, KeyboardAction, ClearOSDAction, ResetGyroAction))\n\t\n\t\n\tdef on_cbActionType_changed(self, *a):\n\t\tcbActionType = self.builder.get_object(\"cbActionType\")\n\t\tstActionData = self.builder.get_object(\"stActionData\")\n\t\tkey = cbActionType.get_model().get_value(cbActionType.get_active_iter(), 0)\n\t\tif key == \"shell\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"vbShell\"))\n\t\t\tself.on_enCommand_changed()\n\t\telif key == \"profile\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"vbProfile\"))\n\t\t\tself.on_cbProfile_changed()\n\t\telif key == \"keyboard\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"nothing\"))\n\t\t\tif not self._recursing:\n\t\t\t\tself.editor.set_action(KeyboardAction())\n\t\telif key == \"clearosd\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"nothing\"))\n\t\t\tif not self._recursing:\n\t\t\t\tself.editor.set_action(ClearOSDAction())\n\t\telif key == \"resetgyro\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"nothing\"))\n\t\t\tif not self._recursing:\n\t\t\t\tself.editor.set_action(ResetGyroAction())\n\t\telif key == \"osd\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"vbOSD\"))\n\t\t\tif not self._recursing:\n\t\t\t\tself.editor.set_action(OSDAction(\"\"))\n\t\telif key == \"menu\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"grMenu\"))\n\t\t\tself.on_cbMenus_changed()\n\t\telif key == \"turnoff\":\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"nothing\"))\n\t\t\tif not self._recursing:\n\t\t\t\tself.editor.set_action(TurnOffAction())\n\t\telse: # none\n\t\t\tstActionData.set_visible_child(self.builder.get_object(\"nothing\"))\n\t\t\tif not self._recursing:\n\t\t\t\tself.editor.set_action(NoAction())\n\t\n\t\n\tdef on_cbProfile_changed(self, *a):\n\t\t\"\"\" Called when user chooses profile in selection combo \"\"\"\n\t\tif self._recursing : return\n\t\tcb = self.builder.get_object(\"cbProfile\")\n\t\tmodel = cb.get_model()\n\t\titer = cb.get_active_iter()\n\t\tif iter is None:\n\t\t\t# Empty list\n\t\t\treturn\n\t\tf = model.get_value(iter, 1)\n\t\tname = f.get_basename()\n\t\tif name.endswith(\".sccprofile\"):\n\t\t\tname = name[0:-11]\n\t\tself.editor.set_action(ChangeProfileAction(name))\n\t\n\t\n\tdef on_enCommand_changed(self, *a):\n\t\tif self._recursing : return\n\t\tenCommand = self.builder.get_object(\"enCommand\")\n\t\tself.editor.set_action(ShellCommandAction(enCommand.get_text()))\n\t\n\t\n\tdef on_osd_settings_changed(self, *a):\n\t\tif self._recursing : return\n\t\tenOSDText = self.builder.get_object(\"enOSDText\")\n\t\tsclOSDTimeout = self.builder.get_object(\"sclOSDTimeout\")\n\t\tcbOSDSize = self.builder.get_object(\"cbOSDSize\")\n\t\ttimeout = sclOSDTimeout.get_value()\n\t\tsize = cbOSDSize.get_model().get_value(cbOSDSize.get_active_iter(), 0)\n\t\tself.editor.set_action(OSDAction(\n\t\t\t0 if timeout > 60.0 else timeout,\n\t\t\tsize,\n\t\t\tenOSDText.get_text()\n\t\t))\n\t\n\t\n\tdef on_exMenuControl_activate(self, ex, *a):\n\t\trvMenuControl = self.builder.get_object(\"rvMenuControl\")\n\t\trvMenuControl.set_reveal_child(not ex.get_expanded())\n\t\n\t\n\tdef on_exMenuPosition_activate(self, ex, *a):\n\t\trvMenuPosition = self.builder.get_object(\"rvMenuPosition\")\n\t\trvMenuPosition.set_reveal_child(not ex.get_expanded())\n\t\n\t\n\tdef on_sclOSDTimeout_format_value(self, scale, value):\n\t\tif value > 60.0:\n\t\t\treturn _(\"forever\")\n\t\telif value < 1:\n\t\t\treturn \"%sms\" % int(value * 1000)\n\t\telse:\n\t\t\treturn \"%ss\" % value\n"
  },
  {
    "path": "scc/gui/ae/tilt.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Tilt\n\nSetups DPAD emulation or menu display\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import Action, NoAction, TiltAction, ButtonAction\nfrom scc.special_actions import MenuAction\nfrom scc.modifiers import NameModifier\nfrom scc.uinput import Keys\nfrom scc.gui.ae import AEComponent, describe_action\nfrom scc.gui.ae.menu_action import MenuActionCofC\nfrom scc.gui.binding_editor import BindingEditor\nfrom scc.gui.action_editor import ActionEditor\n\n\nimport os, logging\nlog = logging.getLogger(\"AE.Tilt\")\n\n__all__ = [ 'TiltComponent' ]\n\n\nclass TiltComponent(AEComponent, BindingEditor):\n\tGLADE = \"ae/tilt.glade\"\n\tNAME = \"tilt\"\n\tCTXS = Action.AC_GYRO\n\tPRIORITY = 2\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tBindingEditor.__init__(self, app)\n\t\tself._recursing = False\n\t\tself.actions = [ NoAction() ] * 6\n\t\n\t\n\tdef set_action(self, mode, action):\n\t\tif isinstance(action, TiltAction):\n\t\t\tself.actions = list(action.actions)\n\t\t\twhile len(self.actions) < 6:\n\t\t\t\tself.actions.append(NoAction())\n\t\t\tself.update_button_desc(action)\n\t\n\t\n\tdef update_button_desc(self, action):\n\t\tfor i in range(0, len(action.actions)):\n\t\t\tself.actions[i] = action.actions[i]\n\t\tfor i in range(0, 6):\n\t\t\tself.set_button_desc(i)\n\t\n\t\n\tdef set_button_desc(self, i):\n\t\tdesc = describe_action(Action.AC_BUTTON, None, self.actions[i])\n\t\tl = self.builder.get_object(\"lblTilt%s\" % (i,))\n\t\tif l is None:\n\t\t\tl = self.builder.get_object(\"btTilt%s\" % (i,)).get_children()[0]\n\t\tl.set_markup(desc)\n\t\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Tilt\")\n\t\n\t\n\tdef handles(self, mode, action):\n\t\treturn isinstance(action, TiltAction)\n\t\n\t\n\tdef update(self):\n\t\tself.editor.set_action(TiltAction(*self.actions))\n\t\n\t\n\tdef on_action_chosen(self, i, action, mark_changed=True):\n\t\tself.actions[i] = action\n\t\tself.set_button_desc(i)\n\t\tself.update()\n\t\n\t\n\tdef on_btTilt_clicked(self, b):\n\t\t\"\"\" 'Select Tilt Action' handler \"\"\"\n\t\ti = int(b.get_name())\n\t\taction = self.actions[i]\n\t\tae = self.choose_editor(action, \"\")\n\t\tae.set_title(_(\"Select Tilt Action\"))\n\t\tae.set_input(i, action, mode = Action.AC_BUTTON)\n\t\tae.show(self.editor.window)\n"
  },
  {
    "path": "scc/gui/ae/trigger.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor - Trigger-as-button Component\n\nAssigns one or two emulated buttons to trigger\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.constants import TRIGGER_MIN, TRIGGER_HALF, TRIGGER_CLICK, TRIGGER_MAX\nfrom scc.constants import HIPFIRE_NORMAL, HIPFIRE_SENSIBLE, HIPFIRE_EXCLUSIVE\nfrom scc.actions import TriggerAction, ButtonAction, AxisAction, MouseAction, HipfireAction\nfrom scc.actions import Action, NoAction, MultiAction\nfrom scc.gui.ae import AEComponent, describe_action\nfrom scc.gui.area_to_action import action_to_area\nfrom scc.gui.simple_chooser import SimpleChooser\nfrom scc.gui.binding_editor import BindingEditor\nfrom scc.modifiers import FeedbackModifier\nfrom scc.gui.parser import InvalidAction\n\nimport os, logging\nlog = logging.getLogger(\"AE.TriggerAB\")\n\n__all__ = [ 'TriggerComponent' ]\n\n\nclass TriggerComponent(AEComponent, BindingEditor):\n\tGLADE = \"ae/trigger.glade\"\n\tNAME = \"trigger\"\n\tCTXS = Action.AC_TRIGGER\n\t\n\tdef __init__(self, app, editor):\n\t\tAEComponent.__init__(self, app, editor)\n\t\tBindingEditor.__init__(self, app)\n\t\tself._recursing = False\n\t\tself.half = NoAction()\n\t\tself.full = NoAction()\n\t\tself.analog = NoAction()\n\t\n\t\n\tdef handles(self, mode, action):\n\t\tif isinstance(action, NoAction):\n\t\t\treturn True\n\t\tsucess, half, full, analog = TriggerComponent._split(action)\n\t\treturn sucess\n\t\n\t\n\t@staticmethod\n\tdef _split(action):\n\t\t\"\"\"\n\t\tSplits passed action so it can be displayed in UI.\n\t\tReturns (sucess, half, full, analog), with three actions\n\t\tfor each UI element.\n\t\tNote that each returned action may be TriggerAction.\n\t\t\n\t\tIf passed action cannot be decoded,\n\t\t'sucess' element of tuple is set to False\n\t\t\"\"\"\n\t\thalf, full, analog = NoAction(), NoAction(), NoAction()\n\t\tactions = action.actions if isinstance(action, MultiAction) else [ action ]\n\t\tfor a in actions:\n\t\t\teffective = TriggerComponent._strip_trigger(a).strip()\n\t\t\tif isinstance(effective, AxisAction):\n\t\t\t\tif analog:\n\t\t\t\t\t# UI can do only one analog action per trigger\n\t\t\t\t\treturn False, half, full, analog\n\t\t\t\tanalog = a\n\t\t\telif isinstance(effective, MouseAction):\n\t\t\t\tif analog:\n\t\t\t\t\t# UI can do only one analog action per trigger\n\t\t\t\t\treturn False, half, full, analog\n\t\t\t\tanalog = a\n\t\t\telif isinstance(a, TriggerAction):\n\t\t\t\tif full and half:\n\t\t\t\t\t# UI can handle only one full and\n\t\t\t\t\t# one half-press action\n\t\t\t\t\treturn False, half, full, analog\n\t\t\t\tif a.release_level == TRIGGER_MAX:\n\t\t\t\t\tif full and a.press_level < full.press_level:\n\t\t\t\t\t\tif half:\n\t\t\t\t\t\t\t# UI can handle only one half-press action\n\t\t\t\t\t\t\treturn False, half, full, analog\n\t\t\t\t\t\thalf = a\n\t\t\t\t\telif full:\n\t\t\t\t\t\tif half:\n\t\t\t\t\t\t\t# UI can handle only one half-press action\n\t\t\t\t\t\t\treturn False, half, full, analog\n\t\t\t\t\t\thalf, full = full, a\n\t\t\t\t\telse:\n\t\t\t\t\t\tfull = a\n\t\t\t\telse:\n\t\t\t\t\tif half:\n\t\t\t\t\t\t# UI can handle only one half-press action\n\t\t\t\t\t\treturn False, half, full, analog\n\t\t\t\t\thalf = a\n\t\t\telif isinstance(a, HipfireAction):\n\t\t\t\thipfire_actions = TriggerComponent._strip_hipfire(a)\n\t\t\t\thalf, full = (x.strip() for x in hipfire_actions )\n\n\t\t\telif isinstance(a, NoAction):\n\t\t\t\t# Ignore theese\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\t# Unhandled action type\n\t\t\t\treturn False, half, full, analog\n\t\tif full and not half:\n\t\t\tfull, half = NoAction(), full\n\t\treturn True, half, full, analog\n\t\n\t\n\t@staticmethod\n\tdef _strip_trigger(action):\n\t\t\"\"\"\n\t\tIf passed action is TriggerAction, returns its child action.\n\t\tReturns passed action otherwise.\n\t\t\"\"\"\n\t\tif isinstance(action, TriggerAction):\n\t\t\treturn action.action\n\t\treturn action\n\t\n\t@staticmethod\n\tdef _strip_hipfire(action):\n\t\t\"\"\"\n\t\tIf passed action is HipfireAction, returns its childs action.\n\t\tReturns passed action otherwise.\n\t\t\"\"\"\n\t\tif isinstance(action, HipfireAction):\n\t\t\treturn action.partialpress_action, action.fullpress_action\n\t\treturn action\n\t\n\tdef get_button_title(self):\n\t\treturn _(\"Key or Button\")\n\n\n\tdef set_action(self, mode, action):\n\t\tself.half, self.full, self.analog = NoAction(), NoAction(), NoAction()\n\t\tsucess, half, full, analog = TriggerComponent._split(action)\n\t\tif sucess:\n\t\t\tself._recursing = True\n\t\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\t\tif isinstance(action, HipfireAction):\n\t\t\t\tself.half, self.full = (TriggerComponent._strip_hipfire(x) for x in (half, full))\n\t\t\t\tif half and full:\n\t\t\t\t\tself.builder.get_object(\"sclPartialLevel\").set_value(action.partialpress_level)\n\t\t\t\t\tself.builder.get_object(\"sclFullLevel\").set_value(action.fullpress_level)\n\t\t\t\t\ttrigger_style = action.mode\n\t\t\t\t\tself.set_cb(cb, \"HIPFIRE_\" + trigger_style, 1)\n\t\t\t\t\tself.builder.get_object(\"sclTimeOut\").set_value(action.timeout)\n\n\t\t\telse:\n\t\t\t\tself.half, self.full, self.analog = (TriggerComponent._strip_trigger(x) for x in (half, full, analog))\n\t\t\t\tif half:\n\t\t\t\t\tself.builder.get_object(\"sclPartialLevel\").set_value(half.press_level)\n\t\t\t\t\ttrigger_style = \"NORMAL_EXCLUSIVE\" if (half.release_level < TRIGGER_MAX) else \"NORMAL\"\n\t\t\t\t\tself.set_cb(cb, trigger_style, 1)\n\t\t\t\tif full:\n\t\t\t\t\tself.builder.get_object(\"sclFullLevel\").set_value(full.press_level)\n\n\t\t\t\tif isinstance(analog, AxisAction):\n\t\t\t\t\tself.builder.get_object(\"sclARangeStart\").set_value(analog.min)\n\t\t\t\t\tself.builder.get_object(\"sclARangeEnd\").set_value(analog.max)\n\t\t\t\n\t\t\tself._recursing = False\n\t\tself.update()\n\t\n\t\n\tdef update(self):\n\t\tself.builder.get_object(\"lblPartPressed\").set_label(describe_action(Action.AC_BUTTON, ButtonAction, self.half))\n\t\tself.builder.get_object(\"lblFullPressed\").set_label(describe_action(Action.AC_BUTTON, ButtonAction, self.full))\n\t\tself.builder.get_object(\"lblAnalog\").set_label(describe_action(Action.AC_TRIGGER, AxisAction, self.analog))\n\t\n\t\n\tdef send(self):\n\t\tactions = []\n\t\thalf_level = int(self.builder.get_object(\"sclPartialLevel\").get_value())\n\t\tfull_level = int(self.builder.get_object(\"sclFullLevel\").get_value())\n\t\tcb = self.builder.get_object(\"cbActionType\")\n\t\ttrigger_style = cb.get_model().get_value(cb.get_active_iter(), 1)\n\t\ttimeout = self.builder.get_object(\"sclTimeOut\").get_value()\n\t\t\n\t\tif (trigger_style == \"HIPFIRE_NORMAL\") and self.half and self.full:\n\t\t\t\tactions.append(HipfireAction(half_level, full_level, self.half, self.full, HIPFIRE_NORMAL,timeout))\n\t\telif (trigger_style == \"HIPFIRE_EXCLUSIVE\") and self.half and self.full:\n\t\t\t\tactions.append(HipfireAction(half_level, full_level, self.half, self.full, HIPFIRE_EXCLUSIVE,timeout))\n\t\telif (trigger_style == \"HIPFIRE_SENSIBLE\") and self.half and self.full:\n\t\t\t\tactions.append(HipfireAction(half_level, full_level, self.half, self.full, HIPFIRE_SENSIBLE,timeout))\n\t\telse:\n\t\t\tif self.half:\n\t\t\t\tif self.full and trigger_style == \"NORMAL_EXCLUSIVE\":\n\t\t\t\t\tactions.append(TriggerAction(half_level, full_level, self.half))\n\t\t\t\telse:\n\t\t\t\t\tactions.append(TriggerAction(half_level, TRIGGER_MAX, self.half))\n\t\t\tif self.full:\n\t\t\t\tactions.append(TriggerAction(full_level, TRIGGER_MAX, self.full))\n\t\t\t\n\t\t\tif self.analog:\n\t\t\t\tanalog_start = int(self.builder.get_object(\"sclARangeStart\").get_value())\n\t\t\t\tanalog_end   = int(self.builder.get_object(\"sclARangeEnd\").get_value())\n\t\t\t\tif analog_start == TRIGGER_MIN and analog_end == TRIGGER_MAX:\n\t\t\t\t\tactions.append(AxisAction(self.analog.id))\n\t\t\t\telse:\n\t\t\t\t\tactions.append(AxisAction(self.analog.id, analog_start, analog_end))\n\t\t\n\t\tself.editor.set_action(MultiAction.make(*actions))\n\t\n\t\n\tdef on_btFullPressedClear_clicked(self, *a):\n\t\tself.full = NoAction()\n\t\tself.update()\n\t\tself.send()\n\t\n\t\n\tdef on_btAnalogClear_clicked(self, *a):\n\t\tself.analog = NoAction()\n\t\tself.update()\n\t\tself.send()\n\t\n\t\n\tdef on_ui_value_changed(self, *a):\n\t\tif not self._recursing:\n\t\t\tself.send()\n\n\tdef on_cbActionType_changed(self, *a):\n\t\tif not self._recursing:\n\t\t\tself.send()\n\t\n\t\n\tdef on_btPartPressed_clicked(self, *a):\n\t\t\"\"\" 'Partialy Pressed Action' handler \"\"\"\n\t\tae = self.choose_editor(self.half, \"\")\n\t\tae.set_title(_(\"Select Partialy Pressed Action\"))\n\t\tae.hide_name()\n\t\tae.set_input(\"half\", self.half, mode = Action.AC_BUTTON)\n\t\tae.show(self.editor.window)\n\t\n\t\n\tdef on_btFullPress_clicked(self, *a):\n\t\t\"\"\" 'Fully Pressed Action' handler \"\"\"\n\t\tae = self.choose_editor(self.full, \"\")\n\t\tae.set_title(_(\"Select Fully Pressed Action\"))\n\t\tae.hide_name()\n\t\tae.set_input(\"full\", self.full, mode = Action.AC_BUTTON)\n\t\tae.show(self.editor.window)\n\t\n\t\n\tdef on_btAnalog_clicked(self, *a):\n\t\t\"\"\" 'Analog Output' handler \"\"\"\n\t\tb = SimpleChooser(self.app, \"axis\", lambda action: self.on_action_chosen(\"analog\", action) )\n\t\tb.set_title(_(\"Select Analog Axis\"))\n\t\tb.display_action(Action.AC_STICK, AxisAction(self.analog))\n\t\tb.show(self.editor.window)\n\t\n\t\n\tdef on_action_chosen(self, i, action, mark_changed=True):\n\t\tif i == \"full\":\n\t\t\tself.full = action\n\t\telif i == \"half\":\n\t\t\tself.half = action\n\t\telse:\n\t\t\tself.analog = action\n\t\tself.update()\n\t\tself.send()\n\t\n\t\n\tdef on_btFullyPresedClear_clicked(self, *a):\n\t\tself.builder.get_object(\"sclFullLevel\").set_value(TRIGGER_CLICK)\n\t\n\t\n\tdef on_btPartPresedClear_clicked(self, *a):\n\t\tself.builder.get_object(\"sclPartialLevel\").set_value(TRIGGER_HALF)\n\t\n\t\n\tdef on_btARangeStartClear_clicked(self, *a):\n\t\tself.builder.get_object(\"sclARangeStart\").set_value(TRIGGER_MIN)\n\t\n\t\n\tdef on_btARangeEndClear_clicked(self, *a):\n\t\tself.builder.get_object(\"sclARangeEnd\").set_value(TRIGGER_MAX)\n\n\tdef on_btTimeOutClear_clicked(self, *a):\n\t\tself.builder.get_object(\"sclTimeOut\").set_value(HipfireAction.DEFAULT_TIMEOUT)\n"
  },
  {
    "path": "scc/gui/app.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - App\n\nMain application window\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, Gdk, Gio, GLib\nfrom scc.gui.controller_widget import TRIGGERS, PADS, STICKS, BUTTONS, GYROS\nfrom scc.gui.daemon_manager import DaemonManager, ControllerManager\nfrom scc.gui.parser import GuiActionParser, InvalidAction\nfrom scc.gui.controller_image import ControllerImage\nfrom scc.gui.profile_switcher import ProfileSwitcher\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.gui.binding_editor import BindingEditor\nfrom scc.gui.statusicon import get_status_icon\nfrom scc.gui.dwsnc import headerbar, IS_UNITY\nfrom scc.gui.ribar import RIBar\nfrom scc.tools import check_access, find_gksudo, profile_is_override, nameof\nfrom scc.tools import get_profile_name, profile_is_default, find_profile\nfrom scc.constants import SCButtons, STICK, STICK_PAD_MAX\nfrom scc.constants import DAEMON_VERSION, LEFT, RIGHT\nfrom scc.paths import get_config_path, get_profiles_path\nfrom scc.custom import load_custom_module\nfrom scc.modifiers import NameModifier\nfrom scc.actions import NoAction\nfrom scc.profile import Profile\nfrom scc.config import Config\n\nimport scc.osd.menu_generators\nimport os, sys, platform, re, json, urllib, logging\nlog = logging.getLogger(\"App\")\n\nclass App(Gtk.Application, UserDataManager, BindingEditor):\n\t\"\"\"\n\tMain application / window.\n\t\"\"\"\n\t\n\tHILIGHT_COLOR = \"#FF00FF00\"\t\t# ARGB\n\tOBSERVE_COLOR = \"#FF60A0FF\"\t\t# ARGB\n\tCONFIG = \"scc.config.json\"\n\tRELEASE_URL = \"https://github.com/Ryochan7/sc-controller/releases/tag/v%s\"\n\tOSD_MODE_PROF_NAME = \".scc-osd.profile_editor\"\n\t\n\tdef __init__(self, gladepath=\"/usr/share/scc\",\n\t\t\t\t\t\timagepath=\"/usr/share/scc/images\"):\n\t\tGtk.Application.__init__(self,\n\t\t\t\tapplication_id=\"me.kozec.scc\",\n\t\t\t\tflags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE | Gio.ApplicationFlags.NON_UNIQUE )\n\t\tUserDataManager.__init__(self)\n\t\tBindingEditor.__init__(self, self)\n\t\t# Setup Gtk.Application\n\t\tself.convert_old_profiles()\n\t\tself.setup_commandline()\n\t\t# Setup DaemonManager\n\t\tself.dm = DaemonManager()\n\t\tself.dm.connect(\"alive\", self.on_daemon_alive)\n\t\tself.dm.connect('event', self.on_daemon_event_observer)\n\t\tself.dm.connect(\"controller-count-changed\", self.on_daemon_ccunt_changed)\n\t\tself.dm.connect(\"dead\", self.on_daemon_dead)\n\t\tself.dm.connect(\"error\", self.on_daemon_error)\n\t\tself.dm.connect('reconfigured', self.on_daemon_reconfigured),\n\t\tself.dm.connect(\"version\", self.on_daemon_version)\n\t\t# Load custom stuff\n\t\tload_custom_module(log, \"gui\")\n\t\t# Set variables\n\t\tself.config = Config()\n\t\tself.gladepath = gladepath\n\t\tself.imagepath = imagepath\n\t\tself.builder = None\n\t\tself.recursing = False\n\t\tself.statusicon = None\n\t\tself.status = \"unknown\"\n\t\tself.context_menu_for = None\n\t\tself.daemon_changed_profile = False\n\t\tself.osd_mode = False\t# In OSD mode, only active profile can be editted\n\t\tself.osd_mode_mapper = None\n\t\tself.background = None\n\t\tself.outdated_version = None\n\t\tself.profile_switchers = []\n\t\tself.test_mode_controller = None\n\t\tself.current_ui_layout = \"default\"\t\t# only \"default\" and \"deck\" are supported\n\t\tself.current_file = None\t\t\t\t# Currently edited file\n\t\tself.controller_count = 0\n\t\tself.current = Profile(GuiActionParser())\n\t\tself.just_started = True\n\t\tself.button_widgets = {}\n\t\tself.hilights = { App.HILIGHT_COLOR : set(), App.OBSERVE_COLOR : set() }\n\t\tself.undo = []\n\t\tself.redo = []\n\t\n\t\n\tdef setup_widgets(self):\n\t\t# Important stuff\n\t\tself.builder = Gtk.Builder()\n\t\tself.builder.add_from_file(os.path.join(self.gladepath, \"app.glade\"))\n\t\tself.builder.connect_signals(self)\n\t\tself.window = self.builder.get_object(\"window\")\n\t\tself.add_window(self.window)\n\t\tself.window.set_title(_(\"SC Controller\"))\n\t\tself.window.set_wmclass(\"SC Controller\", \"SC Controller\")\n\t\tself.ribar = None\n\t\tself.create_binding_buttons()\n\t\t\n\t\tps = self.add_switcher(12, 12)\n\t\tps.set_allow_new(True)\n\t\tps.set_profile(self.load_profile_selection())\n\t\tps.connect('new-clicked', self.on_new_clicked)\n\t\tps.connect('save-clicked', self.on_save_clicked)\n\t\t\n\t\t# Drag&drop target\n\t\tself.builder.get_object(\"content\").drag_dest_set(Gtk.DestDefaults.ALL, [\n\t\t\tGtk.TargetEntry.new(\"text/uri-list\", Gtk.TargetFlags.OTHER_APP, 0),\n\t\t\tGtk.TargetEntry.new(\"text/plain\", Gtk.TargetFlags.OTHER_APP, 0)\n\t\t\t], Gdk.DragAction.COPY\n\t\t)\n\t\t\n\t\t# 'C' and 'CPAD' buttons\n\t\tvbc = self.builder.get_object(\"vbC\")\n\t\tself.main_area = self.builder.get_object(\"mainArea\")\n\t\tvbc.get_parent().remove(vbc)\n\t\t\n\t\t# Background\n\t\tself.background = ControllerImage(self)\n\t\tself.background.connect('hover', self.on_background_area_hover)\n\t\tself.background.connect('leave', self.on_background_area_hover, None)\n\t\tself.background.connect('click', self.on_background_area_click)\n\t\tself.background.connect('button-press-event', self.on_background_button_press)\n\t\tself.main_area.put(self.background, 0, 0)\n\t\tself.main_area.put(vbc, 0, 0) # (self.IMAGE_SIZE[0] / 2) - 90, self.IMAGE_SIZE[1] - 100)\n\t\t\n\t\t# Test markers (those blue circles over PADs and sticks)\n\t\tself.lpad_test = Gtk.Image.new_from_file(os.path.join(self.imagepath, \"test-cursor.svg\"))\n\t\tself.rpad_test = Gtk.Image.new_from_file(os.path.join(self.imagepath, \"test-cursor.svg\"))\n\t\tself.stick_test = Gtk.Image.new_from_file(os.path.join(self.imagepath, \"test-cursor.svg\"))\n\t\tself.main_area.put(self.lpad_test, 40, 40)\n\t\tself.main_area.put(self.rpad_test, 290, 90)\n\t\tself.main_area.put(self.stick_test, 150, 40)\n\t\t\n\t\t# OSD mode (if used)\n\t\tif self.osd_mode:\n\t\t\tself.builder.get_object(\"btDaemon\").set_sensitive(False)\n\t\t\tself.window.set_title(_(\"Edit Profile\"))\n\t\t\n\t\t# Headerbar\n\t\theaderbar(self.builder.get_object(\"hbWindow\"))\n\t\n\t\n\tdef load_gui_config_for_controller(self, controller, first):\n\t\t\"\"\"\n\t\tLoads controller config, changes image and hides, shows or disables\n\t\tbuttons around it.\n\t\t\n\t\tTo make this look less jumpy, Gtk.Stack is used to make transition\n\t\tto empty page and only after that is grid repopulated, everything\n\t\tset up and Stack switched back to original page.\n\t\t\"\"\"\n\t\tstckEditor = self.builder.get_object('stckEditor')\n\t\tlblEmpty = self.builder.get_object('lblEmpty')\n\t\tif controller:\n\t\t\tconfig = controller.load_gui_config(self.imagepath or {})\n\t\telse:\n\t\t\tconfig = {}\n\t\tconfig = self.background.use_config(config, controller=controller)\n\t\t\n\t\tdef do_loading():\n\t\t\t\"\"\" Called after transition is finished \"\"\"\n\t\t\tself.background.use_config(config, controller=controller)\n\t\t\tself.apply_gui_config_buttons(config)\n\t\t\n\t\tif first:\n\t\t\tb1 = self.background.get_config()['gui']['background']\n\t\t\tb2 = config['gui']['background']\n\t\t\tif b1 == b2:\n\t\t\t\t# If application has just started and image is\n\t\t\t\t# not changing, transition would just look weird\n\t\t\t\tdo_loading()\n\t\t\t\treturn\n\t\tif not first:\n\t\t\tstckEditor.set_transition_type(Gtk.StackTransitionType.SLIDE_DOWN)\n\t\tstckEditor.set_visible_child(lblEmpty)\n\t\tGLib.timeout_add(stckEditor.get_transition_duration(), do_loading)\n\t\n\t\n\tdef apply_gui_config_buttons(self, config):\n\t\t\"\"\" Changes UI according to controller configuration \"\"\"\n\t\tstckEditor = self.builder.get_object('stckEditor')\n\t\tgrEditor = self.builder.get_object('grEditor')\n\t\tbtCPAD = self.builder.get_object('btCPAD')\n\t\tbtDPAD = self.builder.get_object('btDPAD')\n\t\tbtGYRO = self.builder.get_object('btGYRO')\n\t\tbtC = self.builder.get_object('btC')\n\t\t\n\t\tbuttons = ControllerImage.get_names(config.get('buttons', {}))\n\t\taxes = ControllerImage.get_names(config.get('axes', {}))\n\t\tgyros = config.get('gyros', False)\n\t\t# Set sensitivity to signalize available inputs\n\t\t# Buttons (as on image)\n\t\tfor b in BUTTONS:\n\t\t\tw = self.builder.get_object(\"bt\" + nameof(b))\n\t\t\tif w:\n\t\t\t\tw.set_sensitive(nameof(b) in buttons)\n\t\t# Buttons (as GTK Widgets)\n\t\tfor b in self.button_widgets:\n\t\t\ttry:\n\t\t\t\tw = self.button_widgets[b]\n\t\t\t\ticon, trash = ControllerManager.get_button_icon(config, b, True)\n\t\t\t\tw.icon.set_from_file(icon)\n\t\t\texcept Exception:\n\t\t\t\tpass\n\t\t# Triggers\n\t\tw = self.builder.get_object(\"btLT\")\n\t\tif w: w.set_sensitive(\"ltrig\" in axes)\n\t\tw = self.builder.get_object(\"btRT\")\n\t\tif w: w.set_sensitive(\"rtrig\" in axes)\n\t\t# Sticks & pads\n\t\tfor b in PADS + STICKS:\n\t\t\tw = self.builder.get_object(\"bt\" + nameof(b))\n\t\t\tif w:\n\t\t\t\tw.set_sensitive(\n\t\t\t\t\t\tb.lower() + \"_x\" in axes\n\t\t\t\t\t\tor b.lower() + \"_y\" in axes\n\t\t\t\t\t\tor nameof(b) in buttons)\n\t\t# Gyro\n\t\tfor b in GYROS:\n\t\t\tw = self.builder.get_object(\"bt\" + b)\n\t\t\tif w:\n\t\t\t\t# TODO: Maybe actual detection\n\t\t\t\tw.set_sensitive(gyros)\n\t\t\n\t\tfor w in (btC, btCPAD, btDPAD, btGYRO):\n\t\t\tw.set_visible(w.get_sensitive())\n\t\t\n\t\t# Re-layout if needed\n\t\texpected_layout = \"default\"\n\t\tif len(axes) >= 8 and btC.get_sensitive():\n\t\t\texpected_layout = \"deck\"\n\t\t\n\t\tif expected_layout != self.current_ui_layout:\n\t\t\tself.apply_ui_layout(expected_layout)\n\t\t\n\t\tstckEditor.set_visible_child(grEditor)\n\t\tGLib.idle_add(self.on_c_size_allocate)\n\t\n\t\n\tdef apply_ui_layout(self, layout):\n\t\t\"\"\"\n\t\tChanges layout of ui elements to fit additional buttons needed for Deck\n\t\t\"\"\"\n\t\tif layout == \"deck\":\n\t\t\t# Move 'C' button bellow LGRIP\n\t\t\tbtRGRIP = self.builder.get_object(\"btRGRIP\")\n\t\t\tbtC = self.builder.get_object(\"btC\")\n\t\t\tbtC.get_parent().remove(btC)\n\t\t\tbtC.set_margin_right(0)\n\t\t\tbtRGRIP.get_parent().pack_start(btC, False, True, 0)\n\t\t\tbtRGRIP.get_parent().reorder_child(btC, 5)\n\t\t\t# Move 'GYRO' button to middle of image (where C was)\n\t\t\tbtGYRO = self.builder.get_object(\"btGYRO\")\n\t\t\tbtGYRO.get_parent().remove(btGYRO)\n\t\t\tvbC = self.builder.get_object(\"vbC\")\n\t\t\tvbC.pack_start(btGYRO, False, True, 0)\n\t\t\tbtGYRO.set_margin_top(30)\n\t\t\t# Resize buttons at bottom\n\t\t\t#for w in ['btSTICK', 'btRSTICK', 'btLPAD', 'btRPAD']:\n\t\t\t#\tw.set_size_request(150, -1)\n\t\t\t# Move 'DPAD' bellow 'LGRIP'\n\t\t\tbtLGRIP = self.builder.get_object(\"btLGRIP\")\n\t\t\tbtDPAD = self.builder.get_object(\"btDPAD\")\n\t\t\tbtDPAD.get_parent().remove(btDPAD)\n\t\t\tbtLGRIP.get_parent().pack_start(btDPAD, False, True, 6)\n\t\t\tbtLGRIP.get_parent().reorder_child(btDPAD, 5)\n\t\n\t\n\tdef setup_statusicon(self):\n\t\tmenu = self.builder.get_object(\"mnuTray\")\n\t\tself.statusicon = get_status_icon(self.imagepath, menu)\n\t\tself.statusicon.connect('clicked', self.on_statusicon_clicked)\n\t\tif not self.statusicon.is_clickable():\n\t\t\tself.builder.get_object(\"mnuShowWindowTray\").set_visible(True)\n\t\tGLib.idle_add(self.statusicon.set, \"scc-%s\" % (self.status,), _(\"SC Controller\"))\n\t\n\t\n\tdef destroy_statusicon(self):\n\t\tself.statusicon.destroy()\n\t\tself.statusicon = None\n\t\n\t\n\tdef check(self):\n\t\t\"\"\" Performs various (three) checks and reports possible problems \"\"\"\n\t\t# TODO: Maybe not best place to do this\n\t\ttry:\n\t\t\t# Dynamic modules\n\t\t\trawlist = open(\"/proc/modules\", \"r\").read().split(\"\\n\")\n\t\t\tkernel_mods = [ line.split(\" \")[0] for line in rawlist ]\n\t\t\t# Built-in modules\n\t\t\trelease = platform.uname()[2]\n\t\t\trawlist = open(\"/lib/modules/%s/modules.builtin\" % release, \"r\").read().split(\"\\n\")\n\t\t\tkernel_mods += [ os.path.split(x)[-1].split(\".\")[0] for x in rawlist ]\n\t\texcept Exception:\n\t\t\t# Maybe running on BSD or Windows...\n\t\t\tkernel_mods = [ ]\n\t\t\n\t\tif len(kernel_mods) > 0 and \"uinput\" not in kernel_mods:\n\t\t\t# There is no uinput\n\t\t\tmsg = _('uinput kernel module not loaded')\n\t\t\tmsg += \"\\n\\n\" + _('Please, consult your distribution manual on how to enable uinput')\n\t\t\tmsg += \"\\n\"   + _('or click on \"Fix Temporary\" button to attempt fix that should work until next restart.')\n\t\t\tribar = self.show_error(msg)\n\t\t\tgksudo = find_gksudo()\n\t\t\tif gksudo and not hasattr(ribar, \"_fix_tmp\"):\n\t\t\t\tbutton = Gtk.Button.new_with_label(_(\"Fix Temporary\"))\n\t\t\t\tribar._fix_tmp = button\n\t\t\t\tbutton.connect('clicked', self.apply_temporary_fix,\n\t\t\t\t\tgksudo + [\"modprobe\", \"uinput\"],\n\t\t\t\t\t_(\"This will load missing uinput module.\")\n\t\t\t\t)\n\t\t\t\tribar.add_button(button, -1)\n\t\t\treturn True\n\t\telif not os.path.exists(\"/dev/uinput\"):\n\t\t\t# /dev/uinput missing\n\t\t\tmsg = _('/dev/uinput doesn\\'t exists')\n\t\t\tmsg += \"\\n\" + _('uinput kernel module is loaded, but /dev/uinput is missing.')\n\t\t\t#msg += \"\\n\\n\" + _('Please, consult your distribution manual on what in the world could cause this.')\n\t\t\tmsg += \"\\n\\n\" + _('Please, consult your distribution manual on how to enable uinput')\n\t\t\tself.show_error(msg)\n\t\t\treturn True\n\t\telif not check_access(\"/dev/uinput\"):\n\t\t\t# Cannot acces uinput\n\t\t\tmsg = _('You don\\'t have required access to /dev/uinput.')\n\t\t\tmsg += \"\\n\"   + _('This will most likely prevent emulation from working.')\n\t\t\tmsg += \"\\n\\n\" + _('Please, consult your distribution manual on how to enable uinput')\n\t\t\tmsg += \"\\n\"   + _('or click on \"Fix Temporary\" button to attempt fix that should work until next restart.')\n\t\t\tribar = self.show_error(msg)\n\t\t\tgksudo = find_gksudo()\n\t\t\tif gksudo and not hasattr(ribar, \"_fix_tmp\"):\n\t\t\t\tbutton = Gtk.Button.new_with_label(_(\"Fix Temporary\"))\n\t\t\t\tribar._fix_tmp = button\n\t\t\t\tbutton.connect('clicked', self.apply_temporary_fix,\n\t\t\t\t\tgksudo + [\"chmod\", \"666\", \"/dev/uinput\"],\n\t\t\t\t\t_(\"This will enable input emulation for <i>every application</i> and <i>all users</i> on this machine.\")\n\t\t\t\t)\n\t\t\t\tribar.add_button(button, -1)\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef apply_temporary_fix(self, trash, shell_command, message):\n\t\t\"\"\"\n\t\tDisplays MessageBox with confirmation, tries to run passed shell\n\t\tcommand and restarts daemon.\n\t\t\n\t\tDoing this allows user to teporary fix some uinput-related problems\n\t\tby his vaim belief I'll not format his harddrive.\n\t\t\"\"\"\n\t\td = Gtk.MessageDialog(parent=self.window,\n\t\t\tflags = Gtk.DialogFlags.MODAL,\n\t\t\ttype = Gtk.MessageType.WARNING,\n\t\t\tbuttons = Gtk.ButtonsType.OK_CANCEL,\n\t\t\tmessage_format = _(\"sudo fix-my-pc\")\n\t\t)\n\t\t\n\t\tdef on_response(dialog, response_id):\n\t\t\tif response_id == -5:\t# OK button, not defined anywhere\n\t\t\t\tsudo = Gio.Subprocess.new(shell_command, 0)\n\t\t\t\tsudo.communicate(None, None)\n\t\t\t\tif sudo.get_exit_status() == 0:\n\t\t\t\t\tself.dm.restart()\n\t\t\t\telse:\n\t\t\t\t\td2 = Gtk.MessageDialog(parent=d,\n\t\t\t\t\t\tflags = Gtk.DialogFlags.MODAL,\n\t\t\t\t\t\ttype = Gtk.MessageType.ERROR,\n\t\t\t\t\t\tbuttons = Gtk.ButtonsType.OK,\n\t\t\t\t\t\tmessage_format = _(\"Command Failed\")\n\t\t\t\t\t)\n\t\t\t\t\td2.run()\n\t\t\t\t\td2.destroy()\n\t\t\td.destroy()\n\t\t\n\t\td.connect(\"response\", on_response)\n\t\td.format_secondary_markup( _(\"\"\"Following command is going to be executed:\n\n<b>%s</b>\n\n%s\"\"\") % (\" \".join(shell_command), message), )\n\t\td.show()\n\t\n\t\n\tdef hilight(self, button):\n\t\t\"\"\" Hilights specified button on background image \"\"\"\n\t\tif button:\n\t\t\tself.hilights[App.HILIGHT_COLOR] = set([button])\n\t\telse:\n\t\t\tself.hilights[App.HILIGHT_COLOR] = set()\n\t\tself._update_background()\n\t\n\t\n\tdef _update_background(self):\n\t\th = {}\n\t\tfor color in self.hilights:\n\t\t\tfor i in self.hilights[color]:\n\t\t\t\th[i] = color\n\t\tself.background.hilight(h)\n\t\n\t\n\tdef hint(self, button):\n\t\t\"\"\" As hilight, but marks GTK Button as well \"\"\"\n\t\tactive = None\n\t\tfor b in self.button_widgets.values():\n\t\t\tif b.widget.get_sensitive():\n\t\t\t\tb.widget.set_state(Gtk.StateType.NORMAL)\n\t\t\t\tif b.name == button:\n\t\t\t\t\tactive = b.widget\n\t\t\n\t\tif active is not None:\n\t\t\tactive.set_state(Gtk.StateType.ACTIVE)\n\t\t\n\t\tself.hilight(button)\n\t\n\t\n\tdef show_editor(self, id):\n\t\taction = self.get_action(self.current, id)\n\t\tae = self.choose_editor(action, \"\", id)\n\t\tae.allow_first_page()\n\t\tae.set_input(id, action)\n\t\tae.show(self.window)\n\t\n\t\n\tdef show_context_menu(self, for_id):\n\t\t\"\"\" Sets sensitivity of popup menu items and displays it on screen \"\"\"\n\t\tmnuPopup = self.builder.get_object(\"mnuPopup\")\n\t\tmnuCopy = self.builder.get_object(\"mnuCopy\")\n\t\tmnuClear = self.builder.get_object(\"mnuClear\")\n\t\tmnuPaste = self.builder.get_object(\"mnuPaste\")\n\t\tmnuEPress = self.builder.get_object(\"mnuEditPress\")\n\t\tmnuEPressS = self.builder.get_object(\"mnuEditPressSeparator\")\n\t\tself.context_menu_for = for_id\n\t\tclp = Gtk.Clipboard.get_default(Gdk.Display.get_default())\n\t\tmnuCopy.set_sensitive(bool(self.get_action(self.current, for_id)))\n\t\tmnuClear.set_sensitive(bool(self.get_action(self.current, for_id)))\n\t\tmnuPaste.set_sensitive(clp.wait_is_text_available())\n\t\tmnuEPress.set_visible(for_id in STICKS + PADS)\n\t\tmnuEPressS.set_visible(mnuEPress.get_visible())\n\t\t\n\t\tmnuPopup.popup(None, None, None, None,\n\t\t\t3, Gtk.get_current_event_time())\n\t\n\t\n\tdef save_config(self):\n\t\tself.config.save()\n\t\tself.dm.reconfigure()\n\t\tself.enable_test_mode()\n\t\n\t\n\tdef on_statusicon_clicked(self, *a):\n\t\t\"\"\" Handler for user clicking on tray icon button \"\"\"\n\t\tself.window.set_visible(not self.window.get_visible())\n\t\n\t\n\tdef on_window_delete_event(self, *a):\n\t\t\"\"\" Called when user tries to close window \"\"\"\n\t\tif not IS_UNITY and self.config['gui']['enable_status_icon'] and self.config['gui']['minimize_to_status_icon']:\n\t\t\t# Override closing and hide instead\n\t\t\tself.window.set_visible(False)\n\t\telse:\n\t\t\tself.on_mnuExit_activate()\n\t\treturn True\n\t\n\t\n\tdef on_mnuClear_activate(self, *a):\n\t\t\"\"\"\n\t\tHandler for 'Clear' context menu item.\n\t\tSimply sets NoAction to input.\n\t\t\"\"\"\n\t\tself.on_action_chosen(self.context_menu_for, NoAction())\n\t\n\t\n\tdef on_mnuCopy_activate(self, *a):\n\t\t\"\"\"\n\t\tHandler for 'Copy' context menu item.\n\t\tConverts action to string and sends that string to clipboard.\n\t\t\"\"\"\n\t\ta = self.get_action(self.current, self.context_menu_for)\n\t\tif a:\n\t\t\tif a.name:\n\t\t\t\ta = NameModifier(a.name, a)\n\t\t\tclp = Gtk.Clipboard.get_default(Gdk.Display.get_default())\n\t\t\tclp.set_text(a.to_string().encode('utf-8'), -1)\n\t\t\tclp.store()\n\t\n\t\n\tdef on_mnuPaste_activate(self, *a):\n\t\t\"\"\"\n\t\tHandler for 'Paste' context menu item.\n\t\tReads string from clipboard, parses it as action and sets that action\n\t\ton selected input.\n\t\t\"\"\"\n\t\tclp = Gtk.Clipboard.get_default(Gdk.Display.get_default())\n\t\ttext = clp.wait_for_text()\n\t\tif text:\n\t\t\ta = GuiActionParser().restart(text.decode('utf-8')).parse()\n\t\t\tif not isinstance(a, InvalidAction):\n\t\t\t\tself.on_action_chosen(self.context_menu_for, a)\n\t\n\t\n\tdef on_mnuEditPress_activate(self, *a):\n\t\t\"\"\"\n\t\tHandler for 'Edit Pressed Action' context menu item.\n\t\t\"\"\"\n\t\tid = self.context_menu_for\n\t\tif id == STICK:\n\t\t\tid = nameof(SCButtons.STICKPRESS)\n\t\tself.show_editor(getattr(SCButtons, id))\n\t\n\t\n\tdef on_mnuGlobalSettings_activate(self, *a):\n\t\tfrom scc.gui.global_settings import GlobalSettings\n\t\tgs = GlobalSettings(self)\n\t\tgs.show(self.window)\n\t\n\t\n\tdef on_mnuImport_activate(self, *a):\n\t\t\"\"\"\n\t\tHandler for 'Import Steam Profile' context menu item.\n\t\tDisplays apropriate dialog.\n\t\t\"\"\"\n\t\tfrom scc.gui.importexport.dialog import Dialog\n\t\tied = Dialog(self)\n\t\tied.show(self.window)\n\t\n\t\n\tdef on_btUndo_clicked(self, *a):\n\t\tif len(self.undo) < 1: return\n\t\tundo, self.undo = self.undo[-1], self.undo[0:-1]\n\t\tself.set_action(self.current, undo.id, undo.before)\n\t\tself.redo.append(undo)\n\t\tself.builder.get_object(\"btRedo\").set_sensitive(True)\n\t\tif len(self.undo) < 1:\n\t\t\tself.builder.get_object(\"btUndo\").set_sensitive(False)\n\t\tself.on_profile_modified()\n\t\n\t\n\tdef on_btRedo_clicked(self, *a):\n\t\tif len(self.redo) < 1: return\n\t\tredo, self.redo = self.redo[-1], self.redo[0:-1]\n\t\tself.set_action(self.current, redo.id, redo.after)\n\t\tself.undo.append(redo)\n\t\tself.builder.get_object(\"btUndo\").set_sensitive(True)\n\t\tif len(self.redo) < 1:\n\t\t\tself.builder.get_object(\"btRedo\").set_sensitive(False)\n\t\tself.on_profile_modified()\n\t\n\t\n\tdef on_profiles_loaded(self, profiles):\n\t\tfor ps in self.profile_switchers:\n\t\t\tps.set_profile_list(profiles)\n\t\n\t\n\tdef undeletable_dialog(self, dlg, *a):\n\t\tdlg.hide()\n\t\treturn True\n\t\n\t\n\tdef on_btNewProfile_clicked(self, *a):\n\t\t\"\"\" Called when new profile name is set and OK is clicked \"\"\"\n\t\ttxNewProfile = self.builder.get_object(\"txNewProfile\")\n\t\trbNewProfile = self.builder.get_object(\"rbNewProfile\")\n\t\t\n\t\tdlg = self.builder.get_object(\"dlgNewProfile\")\n\t\tif rbNewProfile.get_active():\n\t\t\t# Creating blank profile is requested\n\t\t\tself.current.clear()\n\t\telse:\n\t\t\tself.current.is_template = False\n\t\tself.new_profile(self.current, txNewProfile.get_text())\n\t\tdlg.hide()\n\t\n\t\n\tdef on_rbNewProfile_group_changed(self, *a):\n\t\t\"\"\"\n\t\tCalled when user clicks 'Copy current profile' button.\n\t\tIf profile name was not changed by user before clicking it,\n\t\tit's automatically changed.\n\t\t\"\"\"\n\t\ttxNewProfile = self.builder.get_object(\"txNewProfile\")\n\t\trbNewProfile = self.builder.get_object(\"rbNewProfile\")\n\t\t\n\t\tif not txNewProfile._changed:\n\t\t\tself.recursing = True\n\t\t\tif rbNewProfile.get_active():\n\t\t\t\t# Create empty profile\n\t\t\t\ttxNewProfile.set_text(self.generate_new_name())\n\t\t\telse:\n\t\t\t\t# Copy current profile\n\t\t\t\ttxNewProfile.set_text(self.generate_copy_name(txNewProfile._name))\n\t\t\tself.recursing = False\n\t\n\t\n\tdef on_profile_modified(self, update_ui=True):\n\t\t\"\"\"\n\t\tCalled when selected profile is modified in memory.\n\t\t\"\"\"\n\t\tif update_ui:\n\t\t\tself.profile_switchers[0].set_profile_modified(True, self.current.is_template)\n\t\t\n\t\tif not self.current_file.get_path().endswith(\".mod\"):\n\t\t\tmod = self.current_file.get_path() + \".mod\"\n\t\t\tself.current_file = Gio.File.new_for_path(mod)\n\t\t\n\t\tself.save_profile(self.current_file, self.current)\n\t\n\t\n\tdef on_profile_loaded(self, profile, giofile):\n\t\tself.current = profile\n\t\tself.current_file = giofile\n\t\tself.recursing = True\n\t\tself.profile_switchers[0].set_profile_modified(False, self.current.is_template)\n\t\tself.builder.get_object(\"txProfileFilename\").set_text(giofile.get_path())\n\t\tself.builder.get_object(\"txProfileDescription\").get_buffer().set_text(self.current.description)\n\t\tself.builder.get_object(\"cbProfileIsTemplate\").set_active(self.current.is_template)\n\t\tfor b in self.button_widgets.values():\n\t\t\tb.update()\n\t\tself.recursing = False\n\t\n\t\n\tdef on_profile_selected(self, ps, name, giofile):\n\t\tif ps == self.profile_switchers[0]:\n\t\t\tself.load_profile(giofile)\n\t\tif ps.get_controller():\n\t\t\tps.get_controller().set_profile(giofile.get_path())\n\t\n\t\n\tdef on_unknown_profile(self, ps, name):\n\t\tlog.warn(\"Daemon reported unknown profile: '%s'; Overriding.\", name)\n\t\tif self.current_file is not None and ps.get_controller() is not None:\n\t\t\tps.get_controller().set_profile(self.current_file.get_path())\n\t\n\t\n\tdef on_save_clicked(self, *a):\n\t\tif self.current_file.get_path().endswith(\".mod\"):\n\t\t\torig = self.current_file.get_path()[0:-4]\n\t\t\tself.current_file = Gio.File.new_for_path(orig)\n\t\t\n\t\tif self.current.is_template:\n\t\t\t# Ask user if he is OK with overwriting template\n\t\t\td = Gtk.MessageDialog(parent=self.window,\n\t\t\t\tflags = Gtk.DialogFlags.MODAL,\n\t\t\t\ttype = Gtk.MessageType.QUESTION,\n\t\t\t\tbuttons = Gtk.ButtonsType.YES_NO,\n\t\t\t\tmessage_format = _(\"You are about to save changes over template.\\nAre you sure?\")\n\t\t\t)\n\t\t\tNEW_PROFILE_BUTTON = 7\n\t\t\td.add_button(_(\"Create New Profile\"), NEW_PROFILE_BUTTON)\n\t\t\t\n\t\t\t\n\t\t\tr = d.run()\n\t\t\td.destroy()\n\t\t\tif r == NEW_PROFILE_BUTTON:\n\t\t\t\t# New profile button clicked\n\t\t\t\tps = self.profile_switchers[0]\n\t\t\t\trbCopyProfile = self.builder.get_object(\"rbCopyProfile\")\n\t\t\t\tself.on_new_clicked(ps, ps.get_profile_name())\n\t\t\t\trbCopyProfile.set_active(True)\n\t\t\t\treturn\n\t\t\tif r != -8:\n\t\t\t\t# Bail out if user answers anything but yes\n\t\t\t\treturn\n\t\t\n\t\tself.save_profile(self.current_file, self.current)\n\t\n\t\n\tdef on_switch_to_clicked(self, ps, *a):\n\t\t\"\"\" Switches editor to another controller \"\"\"\n\t\tps0 = self.profile_switchers[0]\n\t\tif ps == ps0: return\n\t\t\n\t\tc, p = ps.get_controller(), ps.get_profile_name()\n\t\tc0, p0 = ps0.get_controller(), ps0.get_profile_name()\n\t\t\n\t\tps0.set_controller(c); ps0.set_profile(p)\n\t\tps.set_controller(c0); ps.set_profile(p0)\n\t\t\n\t\tself.load_gui_config_for_controller(c, False)\n\t\tself.enable_test_mode()\n\t\n\t\n\tdef on_profile_saved(self, giofile, send=True):\n\t\t\"\"\"\n\t\tCalled when selected profile is saved to disk\n\t\t\"\"\"\n\t\tif self.osd_mode:\n\t\t\t# Special case, profile shouldn't be changed while in osd_mode\n\t\t\tif not giofile.get_path().endswith(\".mod\"):\n\t\t\t\tself.profile_switchers[0].set_profile_modified(False, self.current.is_template)\n\t\t\treturn\n\t\t\n\t\tif giofile.get_path().endswith(\".mod\"):\n\t\t\t# Special case, this one is saved only to be sent to daemon\n\t\t\t# and user doesn't need to know about it\n\t\t\tif self.dm.is_alive():\n\t\t\t\tcontroller = self.profile_switchers[0].get_controller()\n\t\t\t\tif controller:\n\t\t\t\t\tcontroller.set_profile(giofile.get_path())\n\t\t\t\telse:\n\t\t\t\t\tself.dm.set_profile(giofile.get_path())\n\t\t\treturn\n\t\t\n\t\tself.profile_switchers[0].set_profile_modified(False, self.current.is_template)\n\t\tif send and self.dm.is_alive() and not self.daemon_changed_profile:\n\t\t\tfor ps in self.profile_switchers:\n\t\t\t\tcontroller = ps.get_controller()\n\t\t\t\tif controller:\n\t\t\t\t\tactive = controller.get_profile()\n\t\t\t\t\tif active.endswith(\".mod\"): active = active[0:-4]\n\t\t\t\t\tif active == giofile.get_path():\n\t\t\t\t\t\tcontroller.set_profile(giofile.get_path())\n\t\t\n\t\tself.current_file = giofile\t\n\t\n\t\n\tdef generate_new_name(self):\n\t\t\"\"\"\n\t\tGenerates name for new profile.\n\t\tThat is 'New Profile X', where X is number that makes name unique.\n\t\t\"\"\"\n\t\ti = 1\n\t\tnew_name = _(\"New Profile %s\") % (i,)\n\t\tfilename = os.path.join(get_profiles_path(), new_name + \".sccprofile\")\n\t\twhile os.path.exists(filename):\n\t\t\ti += 1\n\t\t\tnew_name = _(\"New Profile %s\") % (i,)\n\t\t\tfilename = os.path.join(get_profiles_path(), new_name + \".sccprofile\")\n\t\treturn new_name\n\t\n\t\n\tdef generate_copy_name(self, name):\n\t\t\"\"\"\n\t\tGenerates name for profile copy.\n\t\tThat is 'New Profile X', where X is number that makes name unique.\n\t\t\"\"\"\n\t\tnew_name = _(\"%s (copy)\") % (name,)\n\t\tfilename = os.path.join(get_profiles_path(), new_name + \".sccprofile\")\n\t\ti = 2\n\t\twhile os.path.exists(filename):\n\t\t\tnew_name = _(\"%s (copy %s)\") % (name,)\n\t\t\tfilename = os.path.join(get_profiles_path(), new_name + \".sccprofile\")\n\t\t\ti += 1\n\t\treturn new_name\n\t\n\t\n\tdef on_txNewProfile_changed(self, tx):\n\t\tif self.recursing:\n\t\t\treturn\n\t\ttx._changed = True\n\t\n\t\n\tdef on_new_clicked(self, ps, name):\n\t\tdlg = self.builder.get_object(\"dlgNewProfile\")\n\t\ttxNewProfile = self.builder.get_object(\"txNewProfile\")\n\t\trbNewProfile = self.builder.get_object(\"rbNewProfile\")\n\t\tself.recursing = True\n\t\trbNewProfile.set_active(True)\n\t\ttxNewProfile.set_text(self.generate_new_name())\n\t\ttxNewProfile._name = name\n\t\ttxNewProfile._changed = False\n\t\tself.recursing = False\n\t\tdlg.set_transient_for(self.window)\n\t\tdlg.show()\n\t\n\t\n\tdef on_action_chosen(self, id, action, mark_changed=True):\n\t\tbefore = self.set_action(self.current, id, action)\n\t\tif mark_changed:\n\t\t\tif before.to_string() != action.to_string():\n\t\t\t\t# TODO: Maybe better comparison\n\t\t\t\tself.undo.append(UndoRedo(id, before, action))\n\t\t\t\tself.builder.get_object(\"btUndo\").set_sensitive(True)\n\t\t\tself.on_profile_modified()\n\t\telse:\n\t\t\tself.on_profile_modified(update_ui=False)\n\t\treturn before\n\t\n\t\n\tdef on_background_area_hover(self, trash, area):\n\t\tself.hint(area)\n\t\n\t\n\tdef on_background_button_press(self, trash, event):\n\t\tif event.button == 3:\n\t\t\tmnuImage = self.builder.get_object(\"mnuImage\")\n\t\t\tmnuImage.popup(None, None, None, None,\n\t\t\t\t3, Gtk.get_current_event_time())\n\t\n\t\n\tdef on_mnu_change_background_image(self, mnu, *a):\n\t\tcommand, filename = mnu.get_name().split(\",\")\n\t\tif command == \"background\":\n\t\t\tself.background.override_background(filename)\n\t\telif command == \"buttons\":\n\t\t\tself.background.override_buttons(filename)\n\t\t\tself.apply_gui_config_buttons(self.background.get_config())\n\t\telif command == \"undo\":\n\t\t\tself.background.undo_override()\n\t\t\tself.apply_gui_config_buttons(self.background.get_config())\n\t\n\t\n\tdef on_background_area_click(self, trash, area):\n\t\tif area in [ x.name for x in BUTTONS ]:\n\t\t\tself.hint(None)\n\t\t\tself.show_editor(getattr(SCButtons, area))\n\t\telif area in TRIGGERS + STICKS + PADS:\n\t\t\tself.hint(None)\n\t\t\tself.show_editor(area)\n\t\n\t\n\tdef on_c_size_allocate(self, *a):\n\t\t\"\"\"\n\t\tCalled when size of 'Button C' or CPAD is changed.\n\t\tCenters buttons on background image\n\t\t\"\"\"\n\t\tmain_area = self.builder.get_object(\"mainArea\")\n\t\ty = main_area.get_allocation().height - 5\n\t\tw = self.builder.get_object(\"vbC\")\n\t\tallocation = w.get_allocation()\n\t\tx = (self.background.get_allocation().width - allocation.width) / 2\n\t\ty -= allocation.height\n\t\t\n\t\tif self.background.get_config()['gui'][\"no_buttons_in_gui\"]:\n\t\t\t# no_buttons_in_gui is used to keep image without changes\n\t\t\t# This moves \"C\" button away so it doesn't obscure it as well\n\t\t\ty = 10\n\t\t\n\t\tif w.get_parent():\n\t\t\tmain_area.move(w, x, y)\n\t\telse:\n\t\t\tmain_area.put(w, x, y)\n\t\treturn False\n\t\n\t\n\tdef on_ebImage_motion_notify_event(self, box, event):\n\t\tself.background.on_mouse_moved(event.x, event.y)\n\t\n\t\n\tdef on_exiting_n_daemon_killed(self, *a):\n\t\tself.quit()\n\t\n\t\n\tdef on_mnuExit_activate(self, *a):\n\t\tif not self.osd_mode and self.app.config['gui']['autokill_daemon']:\n\t\t\tlog.debug(\"Terminating scc-daemon\")\n\t\t\tfor x in (\"content\", \"mnuEmulationEnabled\", \"mnuEmulationEnabledTray\"):\n\t\t\t\tw = self.builder.get_object(x)\n\t\t\t\tw.set_sensitive(False)\n\t\t\tself.set_daemon_status(\"unknown\", False)\n\t\t\tself.hide_error()\n\t\t\tif self.dm.is_alive():\n\t\t\t\tself.dm.connect(\"dead\", self.on_exiting_n_daemon_killed)\n\t\t\t\tself.dm.connect(\"error\", self.on_exiting_n_daemon_killed)\n\t\t\t\tself.dm.stop()\n\t\t\telse:\n\t\t\t\t# Daemon appears to be dead, kill it just in case\n\t\t\t\tself.dm.stop()\n\t\t\t\tself.quit()\n\t\telse:\n\t\t\tself.quit()\n\t\n\t\n\tdef on_mnuAbout_activate(self, *a):\n\t\tfrom scc.gui.aboutdialog import AboutDialog\n\t\tAboutDialog(self).show(self.window)\n\t\n\t\n\tdef on_daemon_alive(self, *a):\n\t\tself.set_daemon_status(\"alive\", True)\n\t\tif not self.release_notes_visible():\n\t\t\tself.hide_error()\n\t\tself.just_started = False\n\t\tif self.osd_mode:\n\t\t\tself.enable_osd_mode()\n\t\telif self.profile_switchers[0].get_file() is not None and not self.just_started:\n\t\t\tself.dm.set_profile(self.current_file.get_path())\n\t\tGLib.timeout_add_seconds(1, self.check)\n\t\tself.enable_test_mode()\n\t\n\t\n\tdef on_daemon_ccunt_changed(self, daemon, count):\n\t\tif self.controller_count == 0:\n\t\t\t# First controller connected\n\t\t\t# \n\t\t\t# 'event' signal should be connected only on first controller,\n\t\t\t# so this block is executed only when number of connected\n\t\t\t# controllers changes from 0 to 1\n\t\t\tif len(self.dm.get_controllers()) > 0:\n\t\t\t\tc = self.dm.get_controllers()[0]\n\t\t\t\tself.load_gui_config_for_controller(c, first=True)\n\t\tif count > self.controller_count:\n\t\t\t# Controller added\n\t\t\twhile len(self.profile_switchers) < count:\n\t\t\t\ts = self.add_switcher()\n\t\telif count < self.controller_count:\n\t\t\t# Controller removed\n\t\t\twhile len(self.profile_switchers) > max(1, count):\n\t\t\t\ts = self.profile_switchers.pop()\n\t\t\t\ts.set_controller(None)\n\t\t\t\tself.remove_switcher(s)\n\t\t\n\t\t# Assign controllers to widgets\n\t\tfor i in range(0, count):\n\t\t\tc = self.dm.get_controllers()[i]\n\t\t\tself.profile_switchers[i].set_controller(c)\n\t\t\n\t\tif count < 1:\n\t\t\t# Special case, no controllers are connected, but one widget\n\t\t\t# has to stay on screen\n\t\t\tself.profile_switchers[0].set_controller(None)\n\t\t\tself.load_gui_config_for_controller(None, first=True)\n\t\t\n\t\tself.controller_count = count\n\t\n\t\n\tdef new_profile(self, profile, name):\n\t\tfilename = os.path.join(get_profiles_path(), name + \".sccprofile\")\n\t\tself.current_file = Gio.File.new_for_path(filename)\n\t\tself.save_profile(self.current_file, profile)\n\t\tcontroller = self.profile_switchers[0].get_controller()\n\t\tif controller:\n\t\t\tcontroller.set_profile(filename)\n\t\telse:\n\t\t\tself.dm.set_profile(filename)\n\t\tself.profile_switchers[0].set_profile(name, create=True)\n\t\n\t\n\tdef add_switcher(self, margin_left=24, margin_right=24):\n\t\t\"\"\"\n\t\tAdds new profile switcher widgets on top of window. Called\n\t\twhen new controller is connected to daemon.\n\t\t\n\t\tReturns generated ProfileSwitcher instance.\n\t\t\"\"\"\n\t\tvbSwitchers = self.builder.get_object(\"vbSwitchers\")\n\t\tsepSwitchers = self.builder.get_object(\"sepSwitchers\")\n\t\t\n\t\tps = ProfileSwitcher(self.imagepath, self.config)\n\t\tps.set_margin_left(margin_left)\n\t\tps.set_margin_right(margin_right)\n\t\tps.connect('right-clicked', self.on_profile_right_clicked)\n\t\tps.connect('switch-to-clicked', self.on_switch_to_clicked)\n\t\t\n\t\tvbSwitchers.pack_start(ps, False, False, 0)\n\t\tvbSwitchers.reorder_child(ps, 0)\n\t\tif len(vbSwitchers.get_children()) == 2:\n\t\t\t# 1st switcher is bellow separator, rest is stacked on top.\n\t\t\t# That means separator should be moved and shown when 2nd\n\t\t\t# switcher is created.\n\t\t\tvbSwitchers.reorder_child(sepSwitchers, 0)\n\t\t\tsepSwitchers.set_visible(True)\n\t\tvbSwitchers.show_all()\n\t\t\n\t\tif self.osd_mode:\n\t\t\tps.set_allow_switch(False)\n\t\t\n\t\tif len(self.profile_switchers) > 0:\n\t\t\tps.set_profile_list(self.profile_switchers[0].get_profile_list())\n\t\t\tps.set_switch_to_enabled(True)\n\t\t\n\t\tself.profile_switchers.append(ps)\n\t\tps.connect('changed', self.on_profile_selected)\n\t\tps.connect('unknown-profile', self.on_unknown_profile)\n\t\treturn ps\n\t\n\t\n\tdef remove_switcher(self, s):\n\t\t\"\"\"\n\t\tRemoves given profile switcher from UI.\n\t\t\"\"\"\n\t\tvbSwitchers = self.builder.get_object(\"vbSwitchers\")\n\t\tsepSwitchers = self.builder.get_object(\"sepSwitchers\")\n\t\tvbSwitchers.remove(s)\n\t\ts.destroy()\n\t\tif len(vbSwitchers.get_children()) == 2:\n\t\t\tsepSwitchers.set_visible(False)\n\t\n\t\n\tdef enable_test_mode(self):\n\t\t\"\"\"\n\t\tDisables and re-enables Input Test mode. If sniffing is disabled in\n\t\tdaemon configuration, 2nd call fails and logs error.\n\t\t\"\"\"\n\t\tif self.dm.is_alive() and not self.osd_mode:\n\t\t\tif self.test_mode_controller:\n\t\t\t\tself.test_mode_controller.unlock_all()\n\t\t\ttry:\n\t\t\t\tc = self.dm.get_controllers()[0]\n\t\t\texcept IndexError:\n\t\t\t\t# Zero controllers\n\t\t\t\treturn\n\t\t\tif c:\n\t\t\t\tc.unlock_all()\n\t\t\t\tc.observe(DaemonManager.nocallback, self.on_observe_failed,\n\t\t\t\t\t'A', 'B', 'C', 'X', 'Y', 'START', 'BACK', 'LB', 'RB',\n\t\t\t\t\t'LPAD', 'RPAD', 'LGRIP', 'RGRIP', 'LT', 'RT', 'LEFT',\n\t\t\t\t\t'RIGHT', 'STICK', 'STICKPRESS')\n\t\t\t\tself.test_mode_controller = c\n\t\n\t\n\tdef enable_osd_mode(self):\n\t\t# TODO: Support for multiple controllers here\n\t\tself.osd_mode_controller = 0\n\t\tosd_mode_profile = Profile(GuiActionParser())\n\t\tosd_mode_profile.load(find_profile(App.OSD_MODE_PROF_NAME))\n\t\ttry:\n\t\t\tc = self.dm.get_controllers()[self.osd_mode_controller]\n\t\texcept IndexError:\n\t\t\tlog.error(\"osd_mode: Controller not connected\")\n\t\t\tself.quit()\n\t\t\treturn\n\t\t\n\t\tdef on_lock_failed(*a):\n\t\t\tlog.error(\"osd_mode: Locking failed\")\n\t\t\tself.quit()\n\t\t\n\t\tdef on_lock_success(*a):\n\t\t\tlog.debug(\"osd_mode: Locked everything\")\n\t\t\tfrom scc.gui.osd_mode import OSDModeMapper, OSDModeMappings\n\t\t\tself.osd_mode_mapper = OSDModeMapper(self, osd_mode_profile)\n\t\t\tself.osd_mode_mapper.set_target_window(self.window.get_window())\n\t\t\tself.builder.get_object(\"btUndo\").set_visible(False)\n\t\t\tself.builder.get_object(\"btRedo\").set_visible(False)\n\t\t\t\n\t\t\tm = OSDModeMappings(self, self.osd_mode_mapper,\n\t\t\t\tself.builder.get_object(\"OsdmodeMappings\"))\n\t\t\tm.set_controller(self.profile_switchers[0].get_controller())\n\t\t\tm.show()\n\t\t\n\t\t# Locks everything but pads. Pads are emulating mouse and this is\n\t\t# better left in daemon - involving socket in mouse controls\n\t\t# adds too much lags.\n\t\tc.lock(on_lock_success, on_lock_failed,\n\t\t\t'A', 'B', 'X', 'Y', 'START', 'BACK', 'LB', 'RB', 'C',\n\t\t\t'STICK', 'LGRIP', 'RGRIP', 'LT', 'RT', 'STICKPRESS')\n\t\t\n\t\t# Ask daemon to temporaly reconfigure pads for mouse emulation\n\t\tc.replace(DaemonManager.nocallback, on_lock_failed,\n\t\t\tLEFT, osd_mode_profile.pads[LEFT])\n\t\tc.replace(DaemonManager.nocallback, on_lock_failed,\n\t\t\tRIGHT, osd_mode_profile.pads[RIGHT])\n\t\n\t\n\tdef on_observe_failed(self, error):\n\t\tlog.debug(\"Failed to enable test mode: %s\", error)\n\t\n\t\n\tdef on_daemon_version(self, daemon, version):\n\t\t\"\"\"\n\t\tChecks if reported version matches expected one.\n\t\tIf not, daemon is restarted.\n\t\t\"\"\"\n\t\tif version != DAEMON_VERSION and self.outdated_version != version:\n\t\t\tlog.warning(\n\t\t\t\t\"Running daemon instance is too old (version %s, expected %s). Restarting...\",\n\t\t\t\tversion, DAEMON_VERSION)\n\t\t\tself.outdated_version = version\n\t\t\tself.set_daemon_status(\"unknown\", False)\n\t\t\tself.dm.restart()\n\t\telse:\n\t\t\t# At this point, correct daemon version of daemon is running\n\t\t\t# and we can check if there is anything new to inform user about\n\t\t\tif self.app.config['gui']['news']['last_version'] != App.get_release():\n\t\t\t\tif self.app.config['gui']['news']['enabled']:\n\t\t\t\t\tif not self.osd_mode:\n\t\t\t\t\t\tself.check_release_notes()\n\t\n\t\n\tdef on_daemon_error(self, daemon, error):\n\t\tlog.debug(\"Daemon reported error '%s'\", error)\n\t\tmsg = _('There was an error with enabling emulation: <b>%s</b>') % (error,)\n\t\t# Known errors are handled with aditional message\n\t\tif \"Device not found\" in error:\n\t\t\tmsg += \"\\n\" + _(\"Please, check if you have reciever dongle connected to USB port.\")\n\t\telif \"LIBUSB_ERROR_ACCESS\" in error:\n\t\t\tmsg += \"\\n\" + _(\"You don't have access to controller device.\")\n\t\t\tmsg += \"\\n\\n\" + ( _(\"Consult your distribution manual, try installing Steam package or <a href='%s'>install required udev rules manually</a>.\") %\n\t\t\t\t\t'https://wiki.archlinux.org/index.php/Gamepad#Steam_Controller_not_pairing' )\n\t\t\t# TODO: Write howto somewhere instead of linking to ArchWiki\n\t\telif \"LIBUSB_ERROR_BUSY\" in error:\n\t\t\tmsg += \"\\n\" + _(\"Another application (most likely Steam) is using the controller.\")\n\t\telif \"CANT_SUMMON_THE_DAEMON\" in error:\n\t\t\tmsg += \"\\n\" + _(\"Background process responsible for emulation is not starting.\\n\\nTry executing \\\"scc-daemon debug\\\" in terminal window to check for any errors\"\n\t\t\t\t\"\\nor <a href='https://github.com/kozec/sc-controller/issues'>open issue on GitHub</a> and copy output there.\")\n\t\telif \"LIBUSB_ERROR_PIPE\" in error:\n\t\t\tmsg += \"\\n\" + _(\"USB dongle was removed.\")\n\t\telif \"Failed to create uinput device.\" in error:\n\t\t\t# Call check() method and try to determine what went wrong.\n\t\t\tif self.check():\n\t\t\t\t# Check() returns True if error was \"handled\".\n\t\t\t\treturn\n\t\t\t# If check() fails to find error reason, error message is displayed as it is\n\t\t\n\t\tif self.osd_mode:\n\t\t\tself.quit()\n\t\t\n\t\tself.show_error(msg)\n\t\tself.set_daemon_status(\"error\", True)\n\t\n\t\n\tdef on_daemon_event_observer(self, daemon, c, what, data):\n\t\tif self.osd_mode_mapper:\n\t\t\tself.osd_mode_mapper.handle_event(daemon, what, data)\n\t\telif what in (LEFT, RIGHT, STICK):\n\t\t\twidget, area = {\n\t\t\t\tLEFT  : (self.lpad_test,  \"LPADTEST\"),\n\t\t\t\tRIGHT : (self.rpad_test,  \"RPADTEST\"),\n\t\t\t\tSTICK : (self.stick_test, \"STICKTEST\"),\n\t\t\t}[what]\n\t\t\t# Check if stick or pad is released\n\t\t\tif data[0] == data[1] == 0:\n\t\t\t\twidget.hide()\n\t\t\t\treturn\n\t\t\tif not widget.is_visible():\n\t\t\t\twidget.show()\n\t\t\t# Grab values\n\t\t\tax, ay, aw, trash = self.background.get_area_position(area)\n\t\t\tcw = widget.get_allocation().width\n\t\t\t# Compute center\n\t\t\tx, y = ax + aw * 0.5 - cw * 0.5, ay + 1.0 - cw * 0.5\n\t\t\t# Add pad position\n\t\t\tx += data[0] * aw / STICK_PAD_MAX * 0.5\n\t\t\ty -= data[1] * aw / STICK_PAD_MAX * 0.5\n\t\t\t# Move circle\n\t\t\tself.main_area.move(widget, x, y)\n\t\telif what in (\"LT\", \"RT\", \"STICKPRESS\"):\n\t\t\tif data[0]:\n\t\t\t\tself.hilights[App.OBSERVE_COLOR].add(what)\n\t\t\telse:\n\t\t\t\tself.hilights[App.OBSERVE_COLOR].remove(what)\n\t\t\tself._update_background()\n\t\telif hasattr(SCButtons, what):\n\t\t\ttry:\n\t\t\t\tif data[0]:\n\t\t\t\t\tself.hilights[App.OBSERVE_COLOR].add(what)\n\t\t\t\telse:\n\t\t\t\t\tself.hilights[App.OBSERVE_COLOR].remove(what)\n\t\t\t\tself._update_background()\n\t\t\texcept KeyError as e:\n\t\t\t\t# Non fatal\n\t\t\t\tpass\n\t\telse:\n\t\t\tprint(\"event\", what)\n\t\n\t\n\tdef on_profile_right_clicked(self, ps):\n\t\tfor name in (\"mnuConfigureController\", \"mnuTurnoffController\"):\n\t\t\t# Disable controller-related menu items if controller is not connected\n\t\t\tobj = self.builder.get_object(name)\n\t\t\tobj.set_sensitive(ps.get_controller() is not None)\n\t\t\n\t\tfor name in (\"mnuProfileNew\", \"mnuProfileCopy\", \"mnuProfileRename\",\n\t\t\t\t\t\"mnuProfileDetails\", \"mnuProfileSeparator1\",\n\t\t\t\t\t\"mnuProfileSeparator2\"):\n\t\t\t# Hide profile-related menu items for all but 1st profile switcher\n\t\t\tobj = self.builder.get_object(name)\n\t\t\tobj.set_visible(ps == self.profile_switchers[0])\n\t\t\n\t\tif ps == self.profile_switchers[0]:\n\t\t\tname = ps.get_profile_name()\n\t\t\tis_override = profile_is_override(name)\n\t\t\tis_default = profile_is_default(name)\n\t\t\tself.builder.get_object(\"mnuProfileDelete\").set_visible(not is_default)\n\t\t\tself.builder.get_object(\"mnuProfileRevert\").set_visible(is_override)\n\t\t\tself.builder.get_object(\"mnuProfileRename\").set_visible(not is_default)\n\t\telse:\n\t\t\tself.builder.get_object(\"mnuProfileDelete\").set_visible(False)\n\t\t\tself.builder.get_object(\"mnuProfileRevert\").set_visible(False)\n\t\t\n\t\tmnuPS = self.builder.get_object(\"mnuPS\")\n\t\tmnuPS.ps = ps\n\t\tmnuPS.popup(None, None, None, None,\n\t\t\t3, Gtk.get_current_event_time())\n\t\n\t\n\tdef on_mnuConfigureController_activate(self, *a):\n\t\tfrom scc.gui.controller_settings import ControllerSettings\n\t\tmnuPS = self.builder.get_object(\"mnuPS\")\n\t\tcs = ControllerSettings(self, mnuPS.ps.get_controller(), mnuPS.ps)\n\t\tcs.show(self.window)\n\t\n\t\n\tdef on_mnuProfileNew_activate(self, *a):\n\t\tmnuPS = self.builder.get_object(\"mnuPS\")\n\t\tself.on_new_clicked(mnuPS.ps, mnuPS.ps.get_name())\n\t\n\t\n\tdef on_mnuProfileCopy_activate(self, *a):\n\t\tmnuPS = self.builder.get_object(\"mnuPS\")\n\t\trbCopyProfile = self.builder.get_object(\"rbCopyProfile\")\n\t\tself.on_new_clicked(mnuPS.ps, mnuPS.ps.get_profile_name())\n\t\trbCopyProfile.set_active(True)\n\t\n\t\n\tdef on_mnuProfileDetails_activate(self, *a):\n\t\tself.builder.get_object(\"dlgProfileDetails\").show()\n\t\n\t\n\tdef on_mnuProfileRename_activate(self, *a):\n\t\tdlg = self.builder.get_object(\"dlgRenameProfile\")\n\t\ttxRename = self.builder.get_object(\"txRename\")\n\t\tmnuPS = self.builder.get_object(\"mnuPS\")\n\t\tname = mnuPS.ps.get_profile_name()\n\t\ttxRename.set_text(name)\n\t\tdlg._name = name\n\t\tdlg.set_transient_for(self.window)\n\t\tdlg.show()\n\t\n\t\n\tdef on_txRename_changed(self, tx):\n\t\tname = tx.get_text()\n\t\tbtRenameProfile = self.builder.get_object(\"btRenameProfile\")\n\t\tbtRenameProfile.set_sensitive(find_profile(name) is None)\n\t\n\t\n\tdef on_btRenameProfile_clicked(self, *a):\n\t\tdlg = self.builder.get_object(\"dlgRenameProfile\")\n\t\ttxRename = self.builder.get_object(\"txRename\")\n\t\told_name = dlg._name\n\t\tnew_name = txRename.get_text()\n\t\told_fname = os.path.join(get_profiles_path(), old_name + \".sccprofile\")\n\t\tnew_fname = os.path.join(get_profiles_path(), new_name + \".sccprofile\")\n\t\ttry:\n\t\t\tos.rename(old_fname, new_fname)\n\t\t\tfor n in (old_fname, new_fname):\n\t\t\t\ttry:\n\t\t\t\t\tos.unlink(n + \".mod\")\n\t\t\t\texcept:\n\t\t\t\t\t# non-existing .mod file is expected\n\t\t\t\t\tpass\n\t\texcept Exception as e:\n\t\t\tlog.error(\"Failed to rename %s: %s\", old_fname, e)\n\t\t\n\t\tcontrollers = list(self.dm.get_controllers())\n\t\tfor c in controllers:\n\t\t\tif get_profile_name(c.get_profile()) == old_name:\n\t\t\t\tps = self.profile_switchers[controllers.index(c)]\n\t\t\t\tps.set_profile(new_name, True)\n\t\t\t\tc.set_profile(new_name)\n\t\tself.load_profile_list()\n\t\tdlg.hide()\n\t\n\t\n\tdef on_mnuProfileDelete_activate(self, *a):\n\t\tmnuPS = self.builder.get_object(\"mnuPS\")\n\t\tname = mnuPS.ps.get_profile_name()\n\t\tis_override = profile_is_override(name)\n\t\t\n\t\tif is_override:\n\t\t\ttext = _(\"Really revert current profile to default values?\")\n\t\telse:\n\t\t\ttext = _(\"Really delete current profile?\")\n\t\t\n\t\td = Gtk.MessageDialog(parent=self.window,\n\t\t\tflags = Gtk.DialogFlags.MODAL,\n\t\t\ttype = Gtk.MessageType.WARNING,\n\t\t\tbuttons = Gtk.ButtonsType.OK_CANCEL,\n\t\t\tmessage_format = text,\n\t\t)\n\t\td.format_secondary_text(_(\"This action is not undoable!\"))\n\t\t\n\t\tif d.run() == -5: # OK button, no idea where is this defined...\n\t\t\tfname = os.path.join(get_profiles_path(), name + \".sccprofile\")\n\t\t\ttry:\n\t\t\t\tos.unlink(fname)\n\t\t\t\ttry:\n\t\t\t\t\tos.unlink(fname + \".mod\")\n\t\t\t\texcept:\n\t\t\t\t\t# non-existing .mod file is expected\n\t\t\t\t\tpass\n\t\t\t\tfor ps in self.profile_switchers:\n\t\t\t\t\tps.refresh_profile_path(name)\n\t\t\texcept Exception as e:\n\t\t\t\tlog.error(\"Failed to remove %s: %s\", fname, e)\n\t\td.destroy()\n\t\n\t\n\tdef mnuTurnoffController_activate(self, *a):\n\t\tmnuPS = self.builder.get_object(\"mnuPS\")\n\t\tif mnuPS.ps.get_controller():\n\t\t\tmnuPS.ps.get_controller().turnoff()\n\t\n\t\n\tdef on_window_key_press_event(self, window, event):\n\t\tif (event.state & Gdk.ModifierType.CONTROL_MASK) != 0:\n\t\t\tif event.keyval == 115:\n\t\t\t\tself.on_save_clicked()\n\t\telif self.osd_mode and event.keyval == 65471:\n\t\t\tself.on_save_clicked()\n\t\n\t\n\tdef show_error(self, message, ribar=None):\n\t\tif self.ribar is None or self.ribar.get_label() is None:\n\t\t\tself.ribar = ribar or RIBar(message, Gtk.MessageType.ERROR)\n\t\t\tcontent = self.builder.get_object(\"content\")\n\t\t\tcontent.pack_start(self.ribar, False, False, 0)\n\t\t\tcontent.reorder_child(self.ribar, 0)\n\t\t\tself.ribar.connect(\"close\", self.hide_error)\n\t\t\tself.ribar.connect(\"response\", self.hide_error)\n\t\telse:\n\t\t\tself.ribar.get_label().set_markup(message)\n\t\tself.ribar.show()\n\t\tself.ribar.set_reveal_child(True)\n\t\treturn self.ribar\n\t\n\t\n\tdef hide_error(self, *a):\n\t\tif self.ribar is not None:\n\t\t\tif self.ribar.get_parent() is not None:\n\t\t\t\tself.ribar.get_parent().remove(self.ribar)\n\t\tself.ribar = None\n\t\n\t\n\tdef on_daemon_reconfigured(self, *a):\n\t\tlog.debug(\"Reloading config...\")\n\t\tself.config.reload()\n\t\tfor ps in self.profile_switchers:\n\t\t\tps.set_controller(ps.get_controller())\n\t\n\t\n\tdef on_daemon_dead(self, *a):\n\t\tif self.just_started:\n\t\t\tself.dm.restart()\n\t\t\tself.just_started = False\n\t\t\tself.set_daemon_status(\"unknown\", True)\n\t\t\treturn\n\t\t\n\t\tif self.osd_mode:\n\t\t\tself.quit()\n\t\t\n\t\tfor ps in self.profile_switchers:\n\t\t\tps.set_controller(None)\n\t\t\tps.on_daemon_dead()\n\t\tself.set_daemon_status(\"dead\", False)\n\t\n\t\n\tdef on_mnuEmulationEnabled_toggled(self, cb):\n\t\tif self.recursing : return\n\t\tif cb.get_active():\n\t\t\t# Turning daemon on\n\t\t\tself.set_daemon_status(\"unknown\", True)\n\t\t\tcb.set_sensitive(False)\n\t\t\tself.dm.start()\n\t\telse:\n\t\t\t# Turning daemon off\n\t\t\tself.set_daemon_status(\"unknown\", False)\n\t\t\tcb.set_sensitive(False)\n\t\t\tself.hide_error()\n\t\t\tself.dm.stop()\n\t\t\t\n\t\n\tdef do_startup(self, *a):\n\t\tGtk.Application.do_startup(self, *a)\n\t\tself.load_profile_list()\n\t\tself.setup_widgets()\n\t\tif self.app.config['gui']['enable_status_icon']:\n\t\t\tself.setup_statusicon()\n\t\tself.set_daemon_status(\"unknown\", True)\n\t\n\t\n\tdef do_local_options(self, trash, lo):\n\t\tset_logging_level(lo.contains(\"verbose\"), lo.contains(\"debug\") )\n\t\tself.osd_mode = lo.contains(\"osd\")\n\t\treturn -1\n\t\n\t\n\tdef do_command_line(self, cl):\n\t\tGtk.Application.do_command_line(self, cl)\n\t\tif len(cl.get_arguments()) > 1:\n\t\t\tfilename = \" \".join(cl.get_arguments()[1:]) # 'cos fuck Gtk...\n\t\t\tfrom scc.gui.importexport.dialog import Dialog\n\t\t\tif Dialog.determine_type(filename) is not None:\n\t\t\t\tied = Dialog(self)\n\t\t\t\tdef i_told_you_to_quit(*a):\n\t\t\t\t\tsys.exit(0)\n\t\t\t\tied.window.connect('destroy', i_told_you_to_quit)\n\t\t\t\tied.show(self.window)\n\t\t\t\t# Skip first screen and try to import this file\n\t\t\t\tied.import_file(filename)\n\t\t\telse:\n\t\t\t\tsys.exit(1)\n\t\telse:\n\t\t\tself.activate()\n\t\treturn 0\n\t\n\t\n\tdef do_activate(self, *a):\n\t\tself.builder.get_object(\"window\").show()\n\t\tif (self.config['gui']['minimize_on_start'] and self.statusicon\n\t\t\t\t\tand self.statusicon.get_property(\"active\")):\n\t\t\tself.builder.get_object(\"window\").hide()\n\t\telse:\n\t\t\tself.builder.get_object(\"window\").show()\n\t\n\t\n\tdef remove_dot_profile(self):\n\t\t\"\"\"\n\t\tChecks if first profile in list begins with dot and if yes, removes it.\n\t\tThis is done to undo automatic addition that is done when daemon reports\n\t\tselecting such profile.\n\t\t\"\"\"\n\t\tcb = self.builder.get_object(\"cbProfile\")\n\t\tmodel = cb.get_model()\n\t\tif len(model) == 0:\n\t\t\t# Nothing to remove\n\t\t\treturn\n\t\tif not model[0][0].startswith(\".\"):\n\t\t\t# Not dot profile\n\t\t\treturn\n\t\tactive = model.get_path(cb.get_active_iter())\n\t\tfirst = model[0].path\n\t\tif active == first:\n\t\t\t# Can't remove active item\n\t\t\treturn\n\t\tmodel.remove(model[0].iter)\n\t\n\t\n\tdef get_current_profile(self):\n\t\treturn self.profile_switchers[0].get_profile_name()\n\t\n\t\n\tdef set_daemon_status(self, status, daemon_runs):\n\t\t\"\"\" Updates image that shows daemon status and menu shown when image is clicked \"\"\"\n\t\tlog.debug(\"daemon status: %s\", status)\n\t\ticon = os.path.join(self.imagepath, \"scc-%s.svg\" % (status,))\n\t\timgDaemonStatus = self.builder.get_object(\"imgDaemonStatus\")\n\t\tbtDaemon = self.builder.get_object(\"btDaemon\")\n\t\tmnuEmulationEnabled = self.builder.get_object(\"mnuEmulationEnabled\")\n\t\tmnuEmulationEnabledTray = self.builder.get_object(\"mnuEmulationEnabledTray\")\n\t\timgDaemonStatus.set_from_file(icon)\n\t\tmnuEmulationEnabled.set_sensitive(True)\n\t\tmnuEmulationEnabledTray.set_sensitive(True)\n\t\tself.window.set_icon_from_file(icon)\n\t\tself.status = status\n\t\tif self.statusicon:\n\t\t\tGLib.idle_add(self.statusicon.set, \"scc-%s\" % (self.status,), _(\"SC Controller\"))\n\t\tself.recursing = True\n\t\tif status == \"alive\":\n\t\t\tbtDaemon.set_tooltip_text(_(\"Emulation is active\"))\n\t\telif status == \"error\":\n\t\t\tbtDaemon.set_tooltip_text(_(\"Error enabling emulation\"))\n\t\telif status == \"dead\":\n\t\t\tbtDaemon.set_tooltip_text(_(\"Emulation is inactive\"))\n\t\telse:\n\t\t\tbtDaemon.set_tooltip_text(_(\"Checking emulation status...\"))\n\t\tmnuEmulationEnabled.set_active(daemon_runs)\n\t\tmnuEmulationEnabledTray.set_active(daemon_runs)\n\t\tself.recursing = False\n\t\n\t\n\tdef on_btCloseDetails_clicked(self, *a):\n\t\tself.builder.get_object(\"dlgProfileDetails\").hide()\n\t\n\t\n\tdef on_buffProfileDescription_changed(self, buffer, *a):\n\t\tif self.recursing: return\n\t\tself.current.description = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True)\n\t\tself.on_profile_modified()\n\t\n\t\n\tdef on_cbProfileIsTemplate_toggled(self, widget, *a):\n\t\tif self.recursing: return\n\t\tself.current.is_template = widget.get_active()\n\t\tself.on_profile_modified()\n\t\n\t\n\tdef setup_commandline(self):\n\t\tdef aso(long_name, short_name, description,\n\t\t\t\targ=GLib.OptionArg.NONE,\n\t\t\t\tflags=GLib.OptionFlags.IN_MAIN):\n\t\t\t\"\"\" add_simple_option, adds program argument in simple way \"\"\"\n\t\t\to = GLib.OptionEntry()\n\t\t\tif short_name:\n\t\t\t\to.long_name = long_name\n\t\t\t\to.short_name = short_name\n\t\t\to.description = description\n\t\t\to.flags = flags\n\t\t\to.arg = arg\n\t\t\tself.add_main_option_entries([o])\n\t\t\n\t\tself.connect('handle-local-options', self.do_local_options)\n\t\t\n\t\taso(\"verbose\",\tb\"v\", \"Be verbose\")\n\t\taso(\"debug\",\tb\"d\", \"Be more verbose (debug mode)\")\n\t\taso(\"osd\",\t\tb\"o\", \"OSD mode (OSD-controllable editor for current profile)\")\n\t\n\t\n\tdef save_profile_selection(self, path):\n\t\t\"\"\" Saves name of profile into config file \"\"\"\n\t\tname = os.path.split(path)[-1]\n\t\tif name.endswith(\".sccprofile\"):\n\t\t\tname = name[0:-11]\n\t\t\n\t\tdata = dict(current_profile=name)\n\t\tjstr = json.dumps(data, sort_keys=True, indent=4)\n\t\t\n\t\topen(os.path.join(get_config_path(), self.CONFIG), \"w\").write(jstr)\n\t\n\t\n\tdef load_profile_selection(self):\n\t\t\"\"\" Returns name profile from config file or None if there is none saved \"\"\"\n\t\ttry:\n\t\t\treturn self.config['recent_profiles'][0]\n\t\texcept:\n\t\t\treturn None\n\t\n\t\n\t@staticmethod\n\tdef get_release(n=3):\n\t\t\"\"\"\n\t\tReturns current version rounded to max. 'n' numbers.\n\t\t( v0.14.1.3 ; n=3 -> v0.14.1 )\n\t\t\"\"\"\n\t\tsplit = DAEMON_VERSION.split(\".\")[0:n]\n\t\twhile split[-1] == \"0\": split = split[0:len(split) - 1]\n\t\treturn \".\".join(split)\n\t\n\t\n\tdef release_notes_visible(self):\n\t\t\"\"\" Returns True if release notes infobox is visible \"\"\"\n\t\tif not self.ribar: return False\n\t\triNewRelease = self.builder.get_object('riNewRelease')\n\t\treturn self.ribar._infobar == riNewRelease\n\t\n\t\n\tdef check_release_notes(self):\n\t\t\"\"\"\n\t\tSilently downloads release notes from github and displays infobar\n\t\tinforming user that they are ready to be displayed.\n\t\t\"\"\"\n\t\turl = App.RELEASE_URL % (App.get_release(),)\n\t\tlog.debug(\"Loading release notes from '%s'\", url)\n\t\tf = Gio.File.new_for_uri(url)\n\t\tbuffer = b\"\"\n\t\t\n\t\tdef stream_ready(stream, task, buffer):\n\t\t\ttry:\n\t\t\t\tbytes = stream.read_bytes_finish(task)\n\t\t\t\tif bytes.get_size() > 0:\n\t\t\t\t\tbuffer += bytes.get_data()\n\t\t\t\t\tstream.read_bytes_async(102400, 0, None, stream_ready, buffer)\n\t\t\t\telse:\n\t\t\t\t\tself.on_got_release_notes(buffer.decode(\"utf-8\"))\n\t\t\texcept Exception as e:\n\t\t\t\tlog.warning(\"Failed to read release notes\")\n\t\t\t\tlog.exception(e)\n\t\t\t\treturn\n\t\t\n\t\tdef http_ready(f, task, buffer):\n\t\t\ttry:\n\t\t\t\tstream = f.read_finish(task)\n\t\t\t\tassert stream\n\t\t\t\tstream.read_bytes_async(102400, 0, None, stream_ready, buffer)\n\t\t\texcept Exception as e:\n\t\t\t\tlog.warning(\"Failed to read release notes\")\n\t\t\t\tlog.exception(e)\n\t\t\t\tlog.warning(\"(above error is not fatal and can be ignored)\")\n\t\t\t\treturn\n\t\t\n\t\tf.read_async(0, None, http_ready, buffer)\n\t\n\t\n\tdef on_got_release_notes(self, data):\n\t\t\"\"\"\" Called after entire HTML page of release notes is downloaded \"\"\"\n\t\t# There is actually only one thing parsed here;\n\t\t# Sequence of words \"see ... for more\", in bold, containing <A> tag.\n\t\t# If such sequence is found, it's displayed with message about extended\n\t\t# release notes. Otherwise, shorter text and link to github is used.\n\t\tRE_EXTENDED = r'<strong>see.*href=\\\"([^\\\"]+).*for more.*</strong>'\n\t\t\n\t\tif self.ribar is not None:\n\t\t\t# There is already some error displayed, don't bother now...\n\t\t\treturn\n\t\t\n\t\tmsg = \"\"\n\t\textended = re.search(RE_EXTENDED, data, re.IGNORECASE)\n\t\tif extended:\n\t\t\tmsg += _(\"<a href='%s'>Click here</a> to check what's new!\")\n\t\t\tmsg = msg % (extended.group(1), )\n\t\telse:\n\t\t\turl = App.RELEASE_URL % (App.get_release(), )\n\t\t\tmsg += _(\"Welcome to the version <b>%s</b>.\")\n\t\t\tmsg += \" \" + _(\"<a href='%s'>Click here</a> to read release notes.\")\n\t\t\tmsg = msg % (App.get_release(), url)\n\t\t\n\t\tinfobar = self.builder.get_object('riNewRelease')\n\t\tlblNewRelease = self.builder.get_object('lblNewRelease')\n\t\tlblNewRelease.set_markup(msg)\n\t\tribar = RIBar(None, infobar=infobar)\n\t\tribar = self.show_error(None, ribar=ribar)\n\t\tself.ribar.connect(\"close\", self.on_new_release_dismissed)\n\t\tself.ribar.connect(\"response\", self.on_new_release_dismissed)\n\t\t\n\t\t\n\tdef on_new_release_dismissed(self, *a):\n\t\tself.config['gui']['news']['last_version'] = App.get_release()\n\t\tself.config.save() \n\t\n\t\n\tdef on_cbNewRelease_toggled(self, cb):\n\t\tself.app.config['gui']['news']['enabled'] = cb.get_active()\n\t\tself.config.save()\n\t\n\t\n\tdef on_drag_data_received(self, widget, context, x, y, data, info, time):\n\t\t\"\"\" Drag-n-drop handler \"\"\"\n\t\turi = None\n\t\tif str(data.get_data_type()) == \"text/uri-list\":\n\t\t\t# Only file can be dropped here\n\t\t\tif len(data.get_uris()):\n\t\t\t\turi = data.get_uris()[0]\n\t\telif str(data.get_data_type()) == \"text/plain\":\n\t\t\t# This can be anything, so try to extract uri from it\n\t\t\tlines = str(data.get_data()).split(\"\\n\")\n\t\t\tif len(lines) > 0:\n\t\t\t\tfirst = lines[0]\n\t\t\t\tif first.startswith(\"http://\") or first.startswith(\"https://\") or first.startswith(\"ftp://\"):\n\t\t\t\t\t# I don't like other protocols\n\t\t\t\t\turi = first\n\t\tif uri:\n\t\t\tfrom scc.gui.importexport.dialog import Dialog\n\t\t\tgiofile = None\n\t\t\tif uri.startswith(\"file://\"):\n\t\t\t\tgiofile = Gio.File.new_for_uri(uri)\n\t\t\telse:\n\t\t\t\t# Local file can be used directly, remote has to\n\t\t\t\t# be downloaded first\n\t\t\t\tif uri.startswith(\"https://github.com/\"):\n\t\t\t\t\t# Convert link to repository display to link to raw file\n\t\t\t\t\turi = (uri\n\t\t\t\t\t\t.replace(\"https://github.com/\", \"https://raw.githubusercontent.com/\")\n\t\t\t\t\t\t.replace(\"/blob/\", \"/\")\n\t\t\t\t\t)\n\t\t\t\tname = urllib.unquote(\".\".join(uri.split(\"/\")[-1].split(\".\")[0:-1]))\n\t\t\t\tremote = Gio.File.new_for_uri(uri)\n\t\t\t\ttmp, stream = Gio.File.new_tmp(\"%s.XXXXXX\" % (name,))\n\t\t\t\tstream.close()\n\t\t\t\tif remote.copy(tmp, Gio.FileCopyFlags.OVERWRITE, None, None):\n\t\t\t\t\t# Sucessfully downloaded\n\t\t\t\t\tlog.info(\"Downloaded '%s'\" % (uri,))\n\t\t\t\t\tgiofile = tmp\n\t\t\t\telse:\n\t\t\t\t\t# Failed. Just do nothing\n\t\t\t\t\treturn\n\t\t\tif giofile.get_path():\n\t\t\t\tpath = giofile.get_path()\n\t\t\t\tfiletype = Dialog.determine_type(path)\n\t\t\t\tif filetype:\n\t\t\t\t\tlog.info(\"Importing '%s'...\" % (filetype))\n\t\t\t\t\tlog.debug(\"(type %s)\" % (filetype,))\n\t\t\t\t\tied = Dialog(self)\n\t\t\t\t\tied.show(self.window)\n\t\t\t\t\t# Skip first screen and try to import this file\n\t\t\t\t\tied.import_file(path, filetype = filetype)\n\t\t\t\telse:\n\t\t\t\t\tlog.error(\"Unknown file type: '%s'...\" % (path,))\n\t\n\t\n\tdef convert_old_profiles(self):\n\t\t\"\"\"\n\t\tChecks all available profiles and automatically converts anything with\n\t\tversion 1.3 or lower.\n\t\t\"\"\"\n\t\tfrom scc.parser import ActionParser\n\t\tto_convert = {}\n\t\tfor name in os.listdir(get_profiles_path()):\n\t\t\tif name.endswith(\"~\"):\n\t\t\t\t# Ignore backups - https://github.com/kozec/sc-controller/issues/440\n\t\t\t\tcontinue\n\t\t\ttry:\n\t\t\t\tp = Profile(ActionParser())\n\t\t\t\tp.load(os.path.join(get_profiles_path(), name))\n\t\t\texcept:\n\t\t\t\t# Just ignore invalid profiles here\n\t\t\t\tcontinue\n\t\t\tif p.original_version < 1.4:\n\t\t\t\tto_convert[name] = p\n\t\t\n\t\tif to_convert:\n\t\t\tlog.warning(\"Auto-converting old profile files to version 1.4. This should take only moment.\")\n\t\t\tlog.warning(\"All files are modified in-place, but backup files are created. Feel free to remove them later.\")\n\t\t\tfor name in to_convert:\n\t\t\t\ttry:\n\t\t\t\t\tto_convert[name].save(\"%s/%s.convert\" % (get_profiles_path(), name))\n\t\t\t\t\tos.rename(\"%s/%s\" % (get_profiles_path(), name),\n\t\t\t\t\t\t\t\"%s/%s~\" % (get_profiles_path(), name))\n\t\t\t\t\tos.rename(\"%s/%s.convert\" % (get_profiles_path(), name),\n\t\t\t\t\t\t\t\"%s/%s\" % (get_profiles_path(), name))\n\t\t\t\t\tlog.warning(\"Converted %s (from v%s)\", name, to_convert[name].original_version)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.warning(\"Failed to convert %s: %s\", name, e)\n\n\nclass UndoRedo(object):\n\t\"\"\" Just dummy container \"\"\"\n\tdef __init__(self, id, before, after):\n\t\tself.id = id\n\t\tself.before = before\n\t\tself.after = after\n"
  },
  {
    "path": "scc/gui/area_to_action.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - AREA_TO_ACTION\n\nMaps areas on SVG images into actions.\nUsed by ActionEditor.\n\"\"\"\n\nfrom scc.actions import AxisAction, RAxisAction, MouseAction, ButtonAction\nfrom scc.actions import HatLeftAction, HatRightAction\nfrom scc.actions import HatUpAction, HatDownAction\nfrom scc.uinput import Keys, Axes, Rels\n\nAREA_TO_ACTION = {\n\t# Values in tuples: ActionClass, param1, param2...\n\t\n\t# Buttons\n\t'TL'\t\t\t\t: (ButtonAction, Keys.BTN_TL),\n\t'TR'\t\t\t\t: (ButtonAction, Keys.BTN_TR),\n\t'THUMBL'\t\t\t: (ButtonAction, Keys.BTN_THUMBL),\n\t'THUMBR'\t\t\t: (ButtonAction, Keys.BTN_THUMBR),\n\t'SELECT'\t\t\t: (ButtonAction, Keys.BTN_SELECT),\n\t'MODE'\t\t\t\t: (ButtonAction, Keys.BTN_MODE),\n\t'START'\t\t\t\t: (ButtonAction, Keys.BTN_START),\n\t'A'\t\t\t\t\t: (ButtonAction, Keys.BTN_A),\n\t'B'\t\t\t\t\t: (ButtonAction, Keys.BTN_B),\n\t'X'\t\t\t\t\t: (ButtonAction, Keys.BTN_X),\n\t'Y'\t\t\t\t\t: (ButtonAction, Keys.BTN_Y),\n\t\n\t# Media keys\n\t'KEY_PREVIOUSSONG'\t: (ButtonAction, Keys.KEY_PREVIOUSSONG),\n\t'KEY_STOP'\t\t\t: (ButtonAction, Keys.KEY_STOP),\n\t'KEY_PLAYPAUSE'\t\t: (ButtonAction, Keys.KEY_PLAYPAUSE),\n\t'KEY_NEXTSONG'\t\t: (ButtonAction, Keys.KEY_NEXTSONG),\n\t'KEY_VOLUMEDOWN'\t: (ButtonAction, Keys.KEY_VOLUMEDOWN),\n\t'KEY_VOLUMEUP'\t\t: (ButtonAction, Keys.KEY_VOLUMEUP),\n\t\n\t# Dpad\n\t'DPAD_LEFT'\t\t\t: (HatLeftAction, Axes.ABS_HAT0X),\n\t'DPAD_RIGHT'\t\t: (HatRightAction, Axes.ABS_HAT0X),\n\t'DPAD_UP'\t\t\t: (HatUpAction, Axes.ABS_HAT0Y),\n\t'DPAD_DOWN'\t\t\t: (HatDownAction, Axes.ABS_HAT0Y),\n\t'ABS_HAT0X'\t\t\t: (AxisAction, Axes.ABS_HAT0X),\n\t'ABS_HAT0Y'\t\t\t: (AxisAction, Axes.ABS_HAT0Y),\n\n\t# Left stick\n\t'LSTICK_LEFT'\t\t: (AxisAction, Axes.ABS_X, 0, -32767),\n\t'LSTICK_RIGHT'\t\t: (AxisAction, Axes.ABS_X, 0, 32767),\n\t'LSTICK_UP'\t\t\t: (AxisAction, Axes.ABS_Y, 0, -32767),\n\t'LSTICK_DOWN'\t\t: (AxisAction, Axes.ABS_Y, 0, 32767),\n\t'ABS_X'\t\t\t\t: (AxisAction, Axes.ABS_X),\n\t'ABS_Y'\t\t\t\t: (AxisAction, Axes.ABS_Y),\n\n\t# Right stick\n\t'RSTICK_LEFT'\t\t: (AxisAction, Axes.ABS_RX, 0, -32767),\n\t'RSTICK_RIGHT'\t\t: (AxisAction, Axes.ABS_RX, 0, 32767),\n\t'RSTICK_UP'\t\t\t: (AxisAction, Axes.ABS_RY, 0, 32767),\n\t'RSTICK_DOWN'\t\t: (AxisAction, Axes.ABS_RY, 0, -32767),\n\t'ABS_RX'\t\t\t: (AxisAction, Axes.ABS_RX),\n\t'ABS_RY'\t\t\t: (AxisAction, Axes.ABS_RY),\n\t\n\t# Halves\n\t'MINUSHALF_X'\t\t: (AxisAction, Axes.ABS_X, 0, -32767),\n\t'PLUSHALF_X'\t\t: (AxisAction, Axes.ABS_X, 0, 32767),\n\t'MINUSHALF_Y'\t\t: (AxisAction, Axes.ABS_Y, 0, -32767),\n\t'PLUSHALF_Y'\t\t: (AxisAction, Axes.ABS_Y, 0, 32767),\n\t'MINUSHALF_RX'\t\t: (AxisAction, Axes.ABS_RX, 0, -32767),\n\t'PLUSHALF_RX'\t\t: (AxisAction, Axes.ABS_RX, 0, 32767),\n\t'MINUSHALF_RY'\t\t: (AxisAction, Axes.ABS_RY, 0, -32767),\n\t'PLUSHALF_RY'\t\t: (AxisAction, Axes.ABS_RY, 0, 32767),\n\t\n\t# Triggers\n\t'ABS_Z'\t\t\t\t: (AxisAction, Axes.ABS_Z),\n\t'ABS_RZ'\t\t\t: (AxisAction, Axes.ABS_RZ),\n\t\n\t# Mouse\n\t'MOUSE_LEFT'\t\t: (MouseAction, Rels.REL_X, -1),\n\t'MOUSE_RIGHT'\t\t: (MouseAction, Rels.REL_X, 1),\n\t'MOUSE_UP'\t\t\t: (MouseAction, Rels.REL_Y, -1),\n\t'MOUSE_DOWN'\t\t: (MouseAction, Rels.REL_Y, 1,),\n\t'MOUSE_X'\t\t\t: (MouseAction, Rels.REL_X, 1),\n\t'MOUSE_Y'\t\t\t: (MouseAction, Rels.REL_Y, 1),\n\t'MOUSE_WHEEL'\t\t: (MouseAction, Rels.REL_WHEEL, 1),\n\t'MOUSE_HWHEEL'\t\t: (MouseAction, Rels.REL_HWHEEL, 1),\n\t\n\t# Mouse buttons\n\t'MOUSE1'\t\t\t: (ButtonAction, Keys.BTN_LEFT),\n\t'MOUSE2'\t\t\t: (ButtonAction, Keys.BTN_MIDDLE),\n\t'MOUSE3'\t\t\t: (ButtonAction, Keys.BTN_RIGHT),\n\t'MOUSE4'\t\t\t: (MouseAction, Rels.REL_WHEEL, 1),\n\t'MOUSE5'\t\t\t: (MouseAction, Rels.REL_WHEEL, -1),\n\t'MOUSE8'\t\t\t: (ButtonAction, Keys.BTN_SIDE),\n\t'MOUSE9'\t\t\t: (ButtonAction, Keys.BTN_EXTRA),\n}\n\n_CLS_TO_AREA = {}\nfor x in AREA_TO_ACTION:\n\tcls, params = AREA_TO_ACTION[x][0], AREA_TO_ACTION[x][1:]\n\tif not cls in _CLS_TO_AREA:\n\t\t_CLS_TO_AREA[cls] = []\n\t_CLS_TO_AREA[cls].append((x, params))\n\n\ndef action_to_area(action):\n\t\"\"\"\n\tReturns area that matches provided action (both class and parameters)\n\tor None if there is no such area.\n\t\"\"\"\n\tcls = action.__class__\n\tif cls == RAxisAction : cls = AxisAction\n\tif not cls in _CLS_TO_AREA:\n\t\treturn None\n\tfor area, pars in _CLS_TO_AREA[cls]:\n\t\tif len(pars) > len(action.parameters):\n\t\t\tcontinue\n\t\tdiffers = False\n\t\tfor i in range(0, len(pars)):\n\t\t\tif pars[i] != action.parameters[i]:\n\t\t\t\tdiffers = True\n\t\t\t\tbreak\n\t\tif differs : continue\n\t\treturn area\n\treturn None\n"
  },
  {
    "path": "scc/gui/binding_editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - BindingEditor\n\nBase class for main application window and OSD Keyboard bindings editor.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.modifiers import ModeModifier, SensitivityModifier, FeedbackModifier\nfrom scc.modifiers import DoubleclickModifier, HoldModifier\nfrom scc.actions import NoAction, RingAction, MultiAction\nfrom scc.macros import Macro, Type, Repeat, Cycle\nfrom scc.constants import SCButtons, LEFT, RIGHT\nfrom scc.profile import Profile\nfrom scc.gui.controller_widget import TRIGGERS, PADS, STICKS, GYROS, BUTTONS, PRESSABLE\nfrom scc.gui.controller_widget import ControllerPad, ControllerStick, ControllerGyro\nfrom scc.gui.controller_widget import ControllerButton, ControllerTrigger\nfrom scc.gui.modeshift_editor import ModeshiftEditor\nfrom scc.gui.ae.buttons import is_button_togle, is_button_repeat\nfrom scc.gui.ae.gyro_action import is_gyro_enable\nfrom scc.gui.action_editor import ActionEditor\nfrom scc.gui.macro_editor import MacroEditor\nfrom scc.gui.ring_editor import RingEditor\n\n\nclass BindingEditor(object):\n\t\n\tdef __init__(self, app):\n\t\tself.button_widgets = {}\n\t\tself.app = app\n\t\n\t\n\tdef create_binding_buttons(self, use_icons=True, enable_press=True):\n\t\t\"\"\"\n\t\tCreates ControllerWidget instances for available Gtk.Buttons defined\n\t\tin glade file.\n\t\t\"\"\"\n\t\tfor b in BUTTONS:\n\t\t\tw = self.builder.get_object(\"bt\" + b.name)\n\t\t\tif w:\n\t\t\t\tself.button_widgets[b] = ControllerButton(self, b, use_icons, w)\n\t\tfor b in TRIGGERS:\n\t\t\tw = self.builder.get_object(\"bt\" + b)\n\t\t\tif w:\n\t\t\t\tself.button_widgets[b] = ControllerTrigger(self, b, use_icons, w)\n\t\tfor b in PADS:\n\t\t\tw = self.builder.get_object(\"bt\" + b)\n\t\t\tif w:\n\t\t\t\tself.button_widgets[b] = ControllerPad(self, b, use_icons, enable_press, w)\n\t\tfor b in STICKS:\n\t\t\tw = self.builder.get_object(\"bt\" + b)\n\t\t\tif w:\n\t\t\t\te = False if b == Profile.DPAD else enable_press\n\t\t\t\tself.button_widgets[b] = ControllerStick(self, b, use_icons, e, w)\n\t\tw = self.builder.get_object(\"btSTICKPRESS\")\n\t\tif w:\n\t\t\tself.button_widgets[SCButtons.STICKPRESS] = ControllerButton(self, SCButtons.STICKPRESS, use_icons, w)\n\t\tfor b in GYROS:\n\t\t\tw = self.builder.get_object(\"bt\" + b)\n\t\t\tif w:\n\t\t\t\tself.button_widgets[b] = ControllerGyro(self, b, use_icons, w)\n\t\n\t\n\tdef on_action_chosen(self, id, action, mark_changed=True):\n\t\t\"\"\"\n\t\tCallback called when action editting is finished in editor.\n\t\tShould return None or action being replaced.\n\t\t\"\"\"\n\t\traise TypeError(\"Non-overriden on_action_chosen\")\n\t\n\t\n\tdef set_action(self, profile, id, action):\n\t\t\"\"\"\n\t\tStores action in profile.\n\t\tReturns formely stored action.\n\t\t\"\"\"\n\t\tbefore = NoAction()\n\t\tif id == SCButtons.STICKPRESS and Profile.STICK in self.button_widgets:\n\t\t\tbefore, profile.buttons[id] = profile.buttons[id], action\n\t\t\tself.button_widgets[Profile.STICK].update()\n\t\telif id == SCButtons.CPADPRESS and Profile.CPAD in self.button_widgets:\n\t\t\tbefore, profile.buttons[id] = profile.buttons[id], action\n\t\t\tself.button_widgets[Profile.CPAD].update()\n\t\telif id in PRESSABLE:\n\t\t\tbefore, profile.buttons[id] = profile.buttons[id], action\n\t\t\tself.button_widgets[id.name].update()\n\t\telif id in BUTTONS:\n\t\t\tbefore, profile.buttons[id] = profile.buttons[id], action\n\t\t\tself.button_widgets[id].update()\n\t\telif id in TRIGGERS:\n\t\t\t# TODO: Use LT and RT in profile as well\n\t\t\tside = LEFT if id == \"LT\" else RIGHT\n\t\t\tbefore, profile.triggers[side] = profile.triggers[side], action\n\t\t\tself.button_widgets[id].update()\n\t\telif id in GYROS:\n\t\t\tbefore, profile.gyro = profile.gyro, action\n\t\t\tself.button_widgets[id].update()\n\t\telif id in STICKS + PADS:\n\t\t\tif id == Profile.STICK:\n\t\t\t\tbefore, profile.stick = profile.stick, action\n\t\t\telif id == Profile.RSTICK:\n\t\t\t\tbefore, profile.rstick = profile.rstick, action\n\t\t\telif id == Profile.DPAD:\n\t\t\t\tbefore, profile.pads[Profile.DPAD] = profile.pads[Profile.DPAD], action\n\t\t\telif id == Profile.LPAD:\n\t\t\t\tbefore, profile.pads[Profile.LEFT] = profile.pads[Profile.LEFT], action\n\t\t\telif id == Profile.RPAD:\n\t\t\t\tbefore, profile.pads[Profile.RIGHT] = profile.pads[Profile.RIGHT], action\n\t\t\telif id == Profile.CPAD:\n\t\t\t\tbefore, profile.pads[Profile.CPAD] = profile.pads[Profile.CPAD], action\n\t\t\telse:\n\t\t\t\traise ValueError(\"unknown id %s\" % (id,))\n\t\t\tself.button_widgets[id].update()\n\t\treturn before\n\t\n\t\n\tdef get_action(self, profile, id):\n\t\t\"\"\"\n\t\tReturns action for specified id.\n\t\tReturns None if id is not known.\n\t\t\"\"\"\n\t\tbefore = NoAction()\n\t\tif id in BUTTONS:\n\t\t\treturn profile.buttons[id]\n\t\telif id in PRESSABLE:\n\t\t\treturn profile.buttons[id]\n\t\telif id in TRIGGERS:\n\t\t\t# TODO: Use LT and RT in profile as well\n\t\t\tside = LEFT if id == \"LT\" else RIGHT\n\t\t\treturn profile.triggers[side]\n\t\telif id in GYROS:\n\t\t\treturn profile.gyro\n\t\telif id in STICKS + PADS:\n\t\t\tif id == Profile.STICK:\n\t\t\t\treturn profile.stick\n\t\t\telif id == Profile.RSTICK:\n\t\t\t\treturn profile.rstick\n\t\t\telif id in Profile.DPAD:\n\t\t\t\treturn profile.pads[Profile.DPAD]\n\t\t\telif id == Profile.LPAD:\n\t\t\t\treturn profile.pads[Profile.LEFT]\n\t\t\telif id == Profile.RPAD:\n\t\t\t\treturn profile.pads[Profile.RIGHT]\n\t\t\telif id == Profile.CPAD:\n\t\t\t\treturn profile.pads[Profile.CPAD]\n\t\t\telse:\n\t\t\t\traise ValueError(\"unknown id %s\" % (id,))\n\t\treturn None\n\t\n\t\n\tdef choose_editor(self, action, title, id=None):\n\t\t\"\"\" Chooses apropripate Editor instance for edited action \"\"\"\n\t\tif isinstance(action, SensitivityModifier):\n\t\t\taction = action.action\n\t\tif isinstance(action, FeedbackModifier):\n\t\t\taction = action.action\n\t\tif id in GYROS:\n\t\t\te = ActionEditor(self.app, self.on_action_chosen)\n\t\t\te.set_title(title)\n\t\telif isinstance(action, (ModeModifier, DoubleclickModifier, HoldModifier)) and not is_gyro_enable(action):\n\t\t\te = ModeshiftEditor(self.app, self.on_action_chosen)\n\t\t\te.set_title(_(\"Mode Shift for %s\") % (title,))\n\t\telif RingEditor.is_ring_action(action):\n\t\t\te = RingEditor(self.app, self.on_action_chosen)\n\t\t\te.set_title(title)\n\t\telif isinstance(action, Type):\n\t\t\t# Type is subclass of Macro\n\t\t\te = ActionEditor(self.app, self.on_action_chosen)\n\t\t\te.set_title(title)\n\t\telif isinstance(action, Macro) and not (is_button_togle(action) or is_button_repeat(action)):\n\t\t\te = MacroEditor(self.app, self.on_action_chosen)\n\t\t\te.set_title(_(\"Macro for %s\") % (title,))\n\t\telse:\n\t\t\te = ActionEditor(self.app, self.on_action_chosen)\n\t\t\te.set_title(title)\n\t\treturn e\n\t\n\t\n\tdef hilight(self, button):\n\t\t\"\"\" Hilights button on image. Overriden by app. \"\"\"\n\t\tpass\n\t\n\t\n\tdef show_editor(self, id):\n\t\traise TypeError(\"show_editor not overriden\")\n"
  },
  {
    "path": "scc/gui/chooser.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor\n\nAllows to edit button or trigger action.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.actions import ButtonAction, AxisAction, MouseAction, MultiAction\nfrom scc.actions import HatLeftAction, HatRightAction\nfrom scc.actions import HatUpAction, HatDownAction\nfrom scc.gui.area_to_action import AREA_TO_ACTION\nfrom scc.gui.svg_widget import SVGWidget\nfrom scc.gui.dwsnc import headerbar\nfrom scc.gui.editor import Editor\nimport os, logging\nlog = logging.getLogger(\"Chooser\")\n\nAXIS_ACTION_CLASSES = (AxisAction, MouseAction, HatLeftAction, HatRightAction, HatUpAction, HatDownAction)\n\nclass Chooser(Editor):\n\tIMAGES = {}\n\t\n\tACTIVE_COLOR = \"#FF00FF00\"\t# ARGB\n\tHILIGHT_COLOR = \"#FFFF0000\"\t# ARGB\n\t\n\tdef __init__(self, app):\n\t\tself.app = app\n\t\tself.active_area = None\t\t# Area that is permanently hilighted on the image\n\t\tself.images = []\n\t\tself.axes_allowed = True\n\t\tself.mouse_allowed = True\n\t\n\t\n\tdef setup_image(self, grid_columns=0):\n\t\tfor id in self.IMAGES:\n\t\t\tparent = self.builder.get_object(id)\n\t\t\tif parent is not None:\n\t\t\t\timage = SVGWidget(os.path.join(self.app.imagepath, self.IMAGES[id]))\n\t\t\t\timage.connect('hover', self.on_background_area_hover)\n\t\t\t\timage.connect('leave', self.on_background_area_hover, None)\n\t\t\t\timage.connect('click', self.on_background_area_click)\n\t\t\t\tself.images.append(image)\n\t\t\t\tif grid_columns:\n\t\t\t\t\t# Grid\n\t\t\t\t\tparent.attach(image, 0, 0, grid_columns, 1)\n\t\t\t\telse:\n\t\t\t\t\t# Box\n\t\t\t\t\tparent.pack_start(image, True, True, 0)\n\t\t\t\tparent.show_all()\n\t\n\t\n\tdef set_active_area(self, a):\n\t\t\"\"\"\n\t\tSets area that is permanently hilighted on image.\n\t\t\"\"\"\n\t\tself.active_area = a\n\t\tfor i in self.images:\n\t\t\ti.hilight({ self.active_area : Chooser.ACTIVE_COLOR })\n\t\n\t\n\tdef on_background_area_hover(self, background, area):\n\t\tif area in AREA_TO_ACTION:\n\t\t\tif AREA_TO_ACTION[area][0] in AXIS_ACTION_CLASSES:\n\t\t\t\tif not self.axes_allowed:\n\t\t\t\t\treturn\n\t\t\tif not self.mouse_allowed and \"MOUSE\" in area :\n\t\t\t\treturn\n\t\tbackground.hilight({\n\t\t\tself.active_area : Chooser.ACTIVE_COLOR,\n\t\t\tarea : Chooser.HILIGHT_COLOR\n\t\t})\n\t\n\t\n\tdef on_background_area_click(self, trash, area):\n\t\t\"\"\"\n\t\tCalled when user clicks on defined area on gamepad image.\n\t\t\"\"\"\n\t\tif area in AREA_TO_ACTION:\n\t\t\tcls, params = AREA_TO_ACTION[area][0], AREA_TO_ACTION[area][1:]\n\t\t\tif not self.axes_allowed and cls in AXIS_ACTION_CLASSES:\n\t\t\t\treturn\n\t\t\tif not self.mouse_allowed and \"MOUSE\" in area :\n\t\t\t\treturn\n\t\t\tself.area_action_selected(area, cls(*params))\n\t\telse:\n\t\t\tlog.warning(\"Click on unknown area: %s\" % (area,))\n\t\n\t\n\tdef area_action_selected(self, area, action):\n\t\traise Exception(\"Override me!\")\n\t\n\t\n\tdef hide_axes(self):\n\t\t\"\"\" Prevents user from selecting axes \"\"\"\n\t\tself.axes_allowed = False\n\t\n\t\n\tdef hide_mouse(self):\n\t\t\"\"\" Prevents user from selecting mouse-related stuff \"\"\"\n\t\tself.mouse_allowed = False\n"
  },
  {
    "path": "scc/gui/controller_image.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Controller Image\n\nBig, SVGWidget based widget with interchangeable controller and button images.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.gui.svg_widget import SVGWidget, SVGEditor\nfrom scc.paths import get_share_path\nfrom scc.constants import SCButtons\nfrom scc.tools import nameof\n\nimport os, sys, copy, json, logging\nlog = logging.getLogger(\"ContImage\")\n\n\nclass ControllerImage(SVGWidget):\n\tDEFAULT  = \"sc\"\n\tBUTTONS_WITH_IMAGES = (\n\t\tSCButtons.A, SCButtons.B, SCButtons.X, SCButtons.Y,\n\t\tSCButtons.BACK, SCButtons.C, SCButtons.START,\n\t\tSCButtons.DOTS,\n\t)\n\t\n\tDEFAULT_AXES = (\n\t\t# Shared between DS4 and Steam Controller\n\t\t\"stick_x\", \"stick_y\", \"lpad_x\", \"lpad_x\",\n\t\t\"rpad_y\", \"rpad_y\", \"ltrig\", \"rtrig\",\n\t)\n\t\n\tDEFAULT_BUTTONS = [ nameof(x) for x in BUTTONS_WITH_IMAGES ] + [\n\t\t# Used only by Steam Controller\n\t\tnameof(SCButtons.LB), nameof(SCButtons.RB),\n\t\tnameof(SCButtons.LT), nameof(SCButtons.RT),\n\t\tnameof(SCButtons.STICKPRESS),\n\t\tnameof(SCButtons.RPAD), nameof(SCButtons.LPAD),\n\t\tnameof(SCButtons.LGRIP), nameof(SCButtons.RGRIP),\n\t]\n\t\n\t\n\tdef __init__(self, app, config=None):\n\t\tself.app = app\n\t\tself.backup = None\n\t\tself.current = self._ensure_config({}, None)\n\t\tfilename = self._make_controller_image_path(ControllerImage.DEFAULT)\n\t\tSVGWidget.__init__(self, filename)\n\t\tif config:\n\t\t\tself._controller_image.use_config(config)\n\t\n\t\n\tdef _make_controller_image_path(self, img):\n\t\treturn os.path.join(self.app.imagepath,\n\t\t\t\"controller-images/%s.svg\" % (img, ))\n\t\n\t\n\tdef get_config(self):\n\t\t\"\"\"\n\t\tReturns last used config\n\t\t\"\"\"\n\t\treturn self.current\n\t\n\t\n\tdef _ensure_config(self, data, controller):\n\t\t\"\"\" Ensure that required keys are present in config data \"\"\"\n\t\tdata['gui'] = data.get('gui', {})\n\t\tdata['gui']['background'] = data['gui'].get(\"background\", \"sc\")\n\t\tdata['gui']['buttons'] = data['gui'].get(\"buttons\") or self._get_default_images()\n\t\tdata[\"gui\"][\"no_buttons_in_gui\"] = data[\"gui\"].get(\"no_buttons_in_gui\") or False\n\t\tdata['buttons'] = data.get(\"buttons\") or ControllerImage.DEFAULT_BUTTONS\n\t\tdata['axes'] = data.get(\"axes\") or ControllerImage.DEFAULT_AXES\n\t\tdata['gyros'] = data.get(\"gyros\", data['gui'][\"background\"] == \"sc\")\n\t\treturn data\n\t\n\t\n\t@staticmethod\n\tdef get_names(dict_or_tuple):\n\t\t\"\"\"\n\t\tThere are three different ways how button and axis names are stored\n\t\tin config. This wrapper provides unified way to get list of them.\n\t\t\"\"\"\n\t\tif type(dict_or_tuple) in (list, tuple):\n\t\t\treturn dict_or_tuple\n\t\treturn [\n\t\t\t(x[\"axis\"] if type(x) == dict else x)\n\t\t\tfor x in dict_or_tuple.values()\n\t\t]\n\t\n\t\n\tdef use_config(self, config, backup=None, controller=None):\n\t\t\"\"\"\n\t\tLoads controller settings from provided config, adding default values\n\t\twhen needed. Returns same config.\n\t\t\"\"\"\n\t\tself.backup = backup\n\t\tself.current = self._ensure_config(config or {}, controller)\n\t\tself.set_image(os.path.join(self.app.imagepath,\n\t\t\t\"controller-images/%s.svg\" % (self.current[\"gui\"][\"background\"], )))\n\t\tif not self.current[\"gui\"][\"no_buttons_in_gui\"]:\n\t\t\tself._fill_button_images(self.current[\"gui\"][\"buttons\"])\n\t\tself.hilight({})\n\t\treturn self.current\n\t\n\t\n\tdef override_background(self, filename):\n\t\t\"\"\"\n\t\tOverrides background image setting. This changes config in place,\n\t\tso next time get_config is called, changed background is part of it.\n\t\t\"\"\"\n\t\tif self.backup is None:\n\t\t\tself.backup = copy.deepcopy(self.current)\n\t\tdata = json.loads(open(os.path.join(self.app.imagepath,\n\t\t\t\"%s.json\" % (filename,)), \"r\").read())\n\t\tself.current[\"gui\"][\"background\"] = data[\"gui\"][\"background\"]\n\t\tself.use_config(self.current, self.backup)\n\t\n\t\n\tdef override_buttons(self, filename):\n\t\t\"\"\"\n\t\tOverrides button settings. This changes config in place,\n\t\tso next time get_config is called, changed background is part of it.\n\t\t\"\"\"\n\t\tif self.backup is None:\n\t\t\tself.backup = copy.deepcopy(self.current)\n\t\tdata = json.loads(open(os.path.join(self.app.imagepath,\n\t\t\t\"%s.json\" % (filename,)), \"r\").read())\n\t\tself.current[\"gui\"][\"buttons\"] = data[\"gui\"][\"buttons\"]\n\t\tself.current[\"buttons\"] = data[\"buttons\"]\n\t\tself.use_config(self.current, self.backup)\n\t\n\t\n\tdef undo_override(self):\n\t\t\"\"\" Undoes override_* changes \"\"\"\n\t\tif self.backup is not None:\n\t\t\tself.use_config(self.backup, None)\n\t\n\t\n\tdef get_button_groups(self):\n\t\tgroups = json.loads(open(os.path.join(self.app.imagepath,\n\t\t\t\"button-images\", \"groups.json\"), \"r\").read())\n\t\treturn {\n\t\t\tx['key'] : x['buttons'] for x in groups\n\t\t\tif x['type'] == \"buttons\"\n\t\t}\n\t\n\t\n\tdef _get_default_images(self):\n\t\treturn self.get_button_groups()[ControllerImage.DEFAULT]\n\t\n\t\n\tdef _fill_button_images(self, buttons):\n\t\te = self.edit()\n\t\t#SVGEditor.update_parents(e)\n\t\ttarget = SVGEditor.get_element(e, \"controller\")\n\t\ttarget_x, target_y = SVGEditor.get_translation(target)\n\t\tfor i in range(len(ControllerImage.BUTTONS_WITH_IMAGES)):\n\t\t\tb = nameof(ControllerImage.BUTTONS_WITH_IMAGES[i])\n\t\t\tif b == \"DOTS\":\n\t\t\t\t# How did I managed to create this kind of special case? -_-\n\t\t\t\ti = 16\n\t\t\tpath = None\n\t\t\ttry:\n\t\t\t\telm = SVGEditor.get_element(e, \"AREA_%s\" % (b,))\n\t\t\t\tif elm is None:\n\t\t\t\t\tlog.warning(\"Area for button %s not found\", b)\n\t\t\t\t\tcontinue\n\t\t\t\tx, y = SVGEditor.get_translation(elm)\n\t\t\t\tscale = 1.0\n\t\t\t\tif \"scc-button-scale\" in elm.attrib:\n\t\t\t\t\tw, h = SVGEditor.get_size(elm)\n\t\t\t\t\tscale = float(elm.attrib['scc-button-scale'])\n\t\t\t\t\ttw, th = w * scale, h * scale\n\t\t\t\t\tif scale < 1.0:\n\t\t\t\t\t\tx += (w - tw) * 0.5\n\t\t\t\t\t\ty += (h - th) * 0.5\n\t\t\t\t\telse:\n\t\t\t\t\t\tx -= (tw - w) * 0.25\n\t\t\t\t\t\ty -= (th - h) * 0.25\n\t\t\t\tpath = os.path.join(self.app.imagepath, \"button-images\",\n\t\t\t\t\t\"%s.svg\" % (buttons[i], ))\n\t\t\t\timg = SVGEditor.get_element(SVGEditor.load_from_file(path), \"button\")\n\t\t\t\timg.attrib[\"transform\"] = \"translate(%s, %s) scale(%s)\" % (\n\t\t\t\t\tx - target_x, y - target_y, scale)\n\t\t\t\timg.attrib[\"id\"] = b\n\t\t\t\tSVGEditor.add_element(target, img)\n\n\t\t\texcept Exception as err:\n\t\t\t\tlog.warning(\"Failed to add image for button %s (from %s)\", b, path)\n\t\t\t\tlog.exception(err)\n\t\te.commit()\n\n"
  },
  {
    "path": "scc/gui/controller_settings.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Global Settings\n\nCurrently setups only one thing...\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import GLib, GdkPixbuf\nfrom scc.paths import get_controller_icons_path, get_default_controller_icons_path\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.gui.editor import Editor, ComboSetter\n\nimport os, logging\nlog = logging.getLogger(\"GS\")\n\nclass ControllerSettings(Editor, UserDataManager, ComboSetter):\n\tGLADE = \"controller_settings.glade\"\n\t\n\tdef __init__(self, app, controller, profile_switcher=None):\n\t\tUserDataManager.__init__(self)\n\t\tself.app = app\n\t\tself.controller = controller\n\t\tself.profile_switcher = profile_switcher\n\t\tself.setup_widgets()\n\t\tself.load_icons()\n\t\tself._timer = None\n\t\tself.app.config.reload()\n\t\tself.load_settings()\n\t\tself._eh_ids = ()\n\t\n\t\n\tdef load_icons(self):\n\t\tpaths = [ get_default_controller_icons_path(), get_controller_icons_path() ]\n\t\tself.load_user_data(paths, \"*.svg\", None, self.on_icons_loaded)\n\t\n\t\n\tdef on_icons_loaded(self, icons):\n\t\tlstIcons = self.builder.get_object(\"lstIcons\")\n\t\tcbIcon = self.builder.get_object(\"cbIcon\")\n\t\tpaths = [ f.get_path() for f in icons ]\n\t\tfor path in sorted(paths):\n\t\t\tfilename = os.path.split(path)[-1]\n\t\t\tname = \".\".join(filename.split(\".\")[0:-1])\n\t\t\tif self.controller.get_type() not in name:\n\t\t\t\t# Ignore images for other types\n\t\t\t\tcontinue\n\t\t\ttry:\n\t\t\t\tpb = GdkPixbuf.Pixbuf.new_from_file(path)\n\t\t\texcept:\n\t\t\t\t# Failed to load image\n\t\t\t\tcontinue\n\t\t\tlstIcons.append(( path, filename, name, pb ))\n\t\t\n\t\tcfg = self.app.config.get_controller_config(self.controller.get_id())\n\t\t\n\t\tif \"icon\" in cfg:\n\t\t\t# Should be always\n\t\t\tself.set_cb(cbIcon, cfg[\"icon\"], 1)\n\t\n\t\n\tdef on_Dialog_destroy(self, *a):\n\t\tfor x in self._eh_ids:\n\t\t\tself.app.dm.disconnect(x)\n\t\tself._eh_ids = ()\n\t\n\t\n\tdef on_btClearControlWith_clicked(self, *a):\n\t\tself.builder.get_object(\"cbControlWith\").set_active(0)\n\t\n\t\n\tdef on_btClearConfirmWith_clicked(self, *a):\n\t\tself.builder.get_object(\"cbConfirmWith\").set_active(0)\n\t\n\t\n\tdef on_btClearCancelWith_clicked(self, *a):\n\t\tself.builder.get_object(\"cbCancelWith\").set_active(1)\n\t\n\t\n\tdef on_exTouchpadRotation_activate(self, ex, *a):\n\t\trvTouchpadRotation = self.builder.get_object(\"rvTouchpadRotation\")\n\t\trvTouchpadRotation.set_reveal_child(not ex.get_expanded())\n\t\n\t\n\tdef on_exMenuButtons_activate(self, ex, *a):\n\t\trvMenuButtons = self.builder.get_object(\"rvMenuButtons\")\n\t\trvMenuButtons.set_reveal_child(not ex.get_expanded())\n\t\n\t\n\tdef on_btClearLeftRotation_clicked(self, *a):\n\t\tsclLeftRotation = self.builder.get_object(\"sclLeftRotation\")\n\t\tsclLeftRotation.set_value(20)\n\t\n\t\n\tdef on_btClearRightRotation_clicked(self, *a):\n\t\tsclRightRotation = self.builder.get_object(\"sclRightRotation\")\n\t\tsclRightRotation.set_value(-20)\n\t\n\t\n\tdef on_rotation_value_changed(self, *a):\n\t\tif self._recursing: return\n\t\tself.save_config()\n\t\n\t\n\tdef load_settings(self):\n\t\ttxName = self.builder.get_object(\"txName\")\n\t\tsclLED = self.builder.get_object(\"sclLED\")\n\t\tcbAlignOSD = self.builder.get_object(\"cbAlignOSD\")\n\t\tsclIdleTimeout = self.builder.get_object(\"sclIdleTimeout\")\n\t\tsclLeftRotation = self.builder.get_object(\"sclLeftRotation\")\n\t\tsclRightRotation = self.builder.get_object(\"sclRightRotation\")\n\t\tcbControlWith = self.builder.get_object(\"cbControlWith\")\n\t\tcbConfirmWith = self.builder.get_object(\"cbConfirmWith\")\n\t\tcbCancelWith = self.builder.get_object(\"cbCancelWith\")\n\t\t\n\t\tcfg = self.app.config.get_controller_config(self.controller.get_id())\n\t\t\n\t\tself._recursing = True\n\t\ttxName.set_text(cfg[\"name\"] or \"\")\n\t\tsclLED.set_value(float(cfg[\"led_level\"]))\n\t\tsclIdleTimeout.set_value(float(cfg[\"idle_timeout\"]))\n\t\tsclLeftRotation.set_value(float(cfg[\"input_rotation_l\"]))\n\t\tsclRightRotation.set_value(float(cfg[\"input_rotation_r\"]))\n\t\tcbAlignOSD.set_active(cfg[\"osd_alignment\"] != 0)\n\t\tself.set_cb(cbControlWith, cfg[\"menu_control\"], keyindex=1)\n\t\tself.set_cb(cbConfirmWith, cfg[\"menu_confirm\"], keyindex=1)\n\t\tself.set_cb(cbCancelWith, cfg[\"menu_cancel\"], keyindex=1)\n\t\tcbConfirmWith.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) == \"-\" )\n\t\tcbCancelWith.set_row_separator_func( lambda model, iter : model.get_value(iter, 0)  == \"-\" )\n\t\tself._recursing = False\n\t\n\t\n\tdef save_config(self, *a):\n\t\t\"\"\" Transfers settings from UI back to config \"\"\"\n\t\tif self._recursing:\n\t\t\treturn\n\t\t# Get widgets\n\t\ttxName = self.builder.get_object(\"txName\")\n\t\tsclLED = self.builder.get_object(\"sclLED\")\n\t\tcbIcon = self.builder.get_object(\"cbIcon\")\n\t\tcbAlignOSD = self.builder.get_object(\"cbAlignOSD\")\n\t\tsclIdleTimeout = self.builder.get_object(\"sclIdleTimeout\")\n\t\tsclLeftRotation = self.builder.get_object(\"sclLeftRotation\")\n\t\tsclRightRotation = self.builder.get_object(\"sclRightRotation\")\n\t\tcbControlWith = self.builder.get_object(\"cbControlWith\")\n\t\tcbConfirmWith = self.builder.get_object(\"cbConfirmWith\")\n\t\tcbCancelWith = self.builder.get_object(\"cbCancelWith\")\n\t\t\n\t\t# Store data\n\t\tcfg = self.app.config.get_controller_config(self.controller.get_id())\n\t\tcfg[\"name\"] = txName.get_text()\n\t\tcfg[\"led_level\"] = sclLED.get_value()\n\t\tcfg[\"osd_alignment\"] = 1 if cbAlignOSD.get_active() else 0\n\t\tcfg[\"idle_timeout\"] = sclIdleTimeout.get_value()\n\t\tcfg[\"input_rotation_l\"] = sclLeftRotation.get_value()\n\t\tcfg[\"input_rotation_r\"] = sclRightRotation.get_value()\n\t\tcfg[\"menu_control\"] = cbControlWith.get_model().get_value(cbControlWith.get_active_iter(), 1)\n\t\tcfg[\"menu_confirm\"] = cbConfirmWith.get_model().get_value(cbConfirmWith.get_active_iter(), 1)\n\t\tcfg[\"menu_cancel\"] = cbCancelWith.get_model().get_value(cbCancelWith.get_active_iter(), 1)\n\t\t\n\t\ttry:\n\t\t\tcfg[\"icon\"] = cbIcon.get_model().get_value(cbIcon.get_active_iter(), 1)\n\t\t\tif self.profile_switcher:\n\t\t\t\tself.profile_switcher.update_icon()\n\t\texcept:\n\t\t\t# Just in case there are no icons at all\n\t\t\tpass\n\t\t\n\t\t# Save (almost)\n\t\tself.schedule_save_config()\n\t\n\t\n\tdef schedule_save_config(self, *a):\n\t\t\"\"\"\n\t\tSchedules config saving in 1s.\n\t\tDone to prevent literal madness when user moves slider.\n\t\t\"\"\"\n\t\tdef cb(*a):\n\t\t\tself._timer = None\n\t\t\tself.app.save_config()\n\t\t\t\n\t\tif self._timer is not None:\n\t\t\tGLib.source_remove(self._timer)\n\t\tself._timer = GLib.timeout_add_seconds(1, cb)\t\n\t\n\t\n\tdef on_sclIdleTimeout_format_value(self, scale, value):\n\t\tif value <= 180:\t# 2 minutes\n\t\t\treturn _(\"%s seconds\") % int(value)\n\t\tif value % 60 == 0:\n\t\t\treturn _(\"%s minutes\") % int(value / 60)\n\t\treturn _(\"%sm %ss\") % (int(value / 60), int(value % 60))\n\t\n\t\n\tdef on_sclLED_value_changed(self, scale, *a):\n\t\tif self._recursing: return\n\t\tcfg = self.app.config.get_controller_config(self.controller.get_id())\n\t\tcfg[\"led_level\"] = scale.get_value()\n\t\ttry:\n\t\t\tself.controller.set_led_level(scale.get_value())\n\t\texcept IndexError:\n\t\t\t# Happens when there is no controller connected to daemon\n\t\t\tpass\n\t\tself.schedule_save_config()\n\t\n\t\n\tdef on_sclIdleTimeout_value_changed(self, scale, *a):\n\t\tif self._recursing: return\n\t\tcfg = self.app.config.get_controller_config(self.controller.get_id())\n\t\tcfg[\"idle_timeout\"] = scale.get_value()\n\t\tself.schedule_save_config()\n"
  },
  {
    "path": "scc/gui/controller_widget.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Controller Widget\n\nButton that user can click to choose emulated action for physical button, axis\nor pad.\n\nWraps around actual button defined in glade file.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, Pango\nfrom scc.constants import SCButtons, STICK, RSTICK, GYRO, LEFT, RIGHT\nfrom scc.actions import Action, XYAction, MultiAction\nfrom scc.gui.ae.gyro_action import is_gyro_enable\nfrom scc.modifiers import DoubleclickModifier\nfrom scc.profile import Profile\nfrom scc.tools import nameof\nimport os, sys, logging\nimport itertools\n\nlog = logging.getLogger(\"ControllerWidget\")\n\nTRIGGERS = [ \"LT\", \"RT\" ]\nPADS\t= [ Profile.LPAD, Profile.RPAD, Profile.CPAD ]\nSTICKS\t= [ STICK, Profile.RSTICK, Profile.DPAD ]\nGYROS\t= [ GYRO ]\nPRESSABLE = [ SCButtons.LPAD, SCButtons.RPAD,\n\t\t\t\tSCButtons.STICKPRESS, SCButtons.CPADPRESS ]\n_NOT_BUTTONS = PADS + STICKS + GYROS + TRIGGERS\n_NOT_BUTTONS += [ x + \"TOUCH\" for x in PADS ]\nBUTTONS = [ b for b in SCButtons if b.name not in _NOT_BUTTONS ]\nLONG_TEXT = 16\n\nclass ControllerWidget:\n\tACTION_CONTEXT = None\n\t\n\tdef __init__(self, app, id, use_icon, widget):\n\t\tself.app = app\n\t\tself.id = id\n\t\tself.name = id if type(id) in (str,) else id.name\n\t\tself.widget = widget\n\t\t\n\t\tself.label = Gtk.Label()\n\t\tself.label.set_ellipsize(Pango.EllipsizeMode.END)\n\t\tself.icon = Gtk.Image.new_from_file(self.get_image()) if use_icon else None\n\t\tself.update()\n\t\t\n\t\tself.widget.connect('enter', self.on_cursor_enter)\n\t\tself.widget.connect('leave', self.on_cursor_leave)\n\t\tself.widget.connect('clicked', self.on_click)\n\t\tself.widget.connect('button-release-event', self.on_button_release)\n\t\n\t\n\tdef get_image(self):\n\t\treturn os.path.join(self.app.imagepath, self.name + \".svg\")\n\t\n\t\n\tdef update(self):\n\t\tself.label.set_label(_(\"(no action)\"))\n\t\n\t\n\tdef on_click(self, *a):\n\t\tself.app.show_editor(self.id)\n\t\n\tdef on_button_release(self, bt, event):\n\t\tif event.button == 3:\n\t\t\t# Rightclick\n\t\t\tself.app.show_context_menu(self.id)\n\t\n\t\n\tdef on_cursor_enter(self, *a):\n\t\tself.app.hilight(self.name)\n\t\n\t\n\tdef on_cursor_leave(self, *a):\n\t\tself.app.hilight(None)\n\n\nclass ControllerButton(ControllerWidget):\n\tACTION_CONTEXT = Action.AC_BUTTON\n\t\n\tdef __init__(self, app, name, use_icon, widget):\n\t\tControllerWidget.__init__(self, app, name, use_icon, widget)\n\t\t\n\t\tif use_icon:\n\t\t\tvbox = Gtk.Box(Gtk.Orientation.HORIZONTAL)\n\t\t\tvbox.set_spacing(6)\n\t\t\tseparator = Gtk.Separator(orientation = Gtk.Orientation.VERTICAL)\n\t\t\tvbox.pack_start(self.icon, False, False, 1)\n\t\t\tvbox.pack_start(separator, False, False, 1)\n\t\t\tvbox.pack_start(self.label, False, True, 1)\n\t\t\tself.widget.add(vbox)\n\t\telse:\n\t\t\tself.widget.add(self.label)\n\t\tself.widget.show_all()\n\t\tself.label.set_max_width_chars(LONG_TEXT)\n\t\tif name == \"C\":\n\t\t\tself.label.set_max_width_chars(10)\n\t\n\t\n\tdef update(self):\n\t\tif self.id in SCButtons.__members__.values() and self.id in self.app.current.buttons:\n\t\t\ttxt = self.app.current.buttons[self.id].describe(self.ACTION_CONTEXT)\n\t\t\tif len(txt) > LONG_TEXT or \"\\n\" in txt:\n\t\t\t\ttxt = \"\\n\".join(txt.split(\"\\n\")[0:2])\n\t\t\t\ttxt = txt.replace(\"<\", \"&lt;\").replace(\">\", \"&gt;\")\n\t\t\t\tself.label.set_markup(\"<small>%s</small>\" % (txt,))\n\t\t\telse:\n\t\t\t\ttxt = txt.replace(\"<\", \"&lt;\").replace(\">\", \"&gt;\")\n\t\t\t\tself.label.set_markup(txt)\n\t\telse:\n\t\t\tself.label.set_label(_(\"(no action)\"))\n\n\nclass ControllerStick(ControllerWidget):\n\tACTION_CONTEXT = Action.AC_STICK\n\t\n\tdef __init__(self, app, id, use_icon, enable_press, widget):\n\t\tself.pressed = Gtk.Label() if enable_press else None\n\t\tself.click_button = SCButtons.STICKPRESS if id == STICK else SCButtons.RSTICKPRESS\n\t\tControllerWidget.__init__(self, app, id, use_icon, widget)\n\t\t\n\t\tgrid = Gtk.Grid()\n\t\tgrid.set_column_spacing(6)\n\t\tself.widget.set_events(Gdk.EventMask.POINTER_MOTION_MASK)\n\t\tself.widget.connect('motion-notify-event', self.on_cursor_motion)\n\t\tself.label.set_max_width_chars(LONG_TEXT)\n\t\tif self.pressed:\n\t\t\tself.label.set_halign(Gtk.Align.START)\n\t\t\tself.pressed.set_halign(Gtk.Align.START)\n\t\t\tself.pressed.set_valign(Gtk.Align.END)\n\t\t\tself.pressed.set_ellipsize(Pango.EllipsizeMode.END)\n\t\t\tself.pressed.set_max_width_chars(LONG_TEXT)\n\t\t\tgrid.attach(self.pressed, 2, 2, 1, 1)\n\t\telse:\n\t\t\tself.label.set_halign(Gtk.Align.CENTER)\n\t\t\tself.label.set_valign(Gtk.Align.CENTER)\n\t\t\tself.pressed = None\n\t\tif self.icon:\n\t\t\tgrid.attach(self.icon, 1, 1, 1, 2)\n\t\tgrid.attach(self.label, 2, 1, 1, 1)\n\t\tself.over_icon = False\n\t\tself.enable_press = enable_press\n\t\tself.widget.add(grid)\n\t\tself.widget.show_all()\n\t\n\t\n\tdef on_cursor_enter(self, *a):\n\t\treturn\n\t\n\t\n\tdef on_click(self, *a):\n\t\tif self.over_icon and self.enable_press:\n\t\t\tself.app.show_editor(self.click_button)\n\t\telse:\n\t\t\tself.app.show_editor(self.id)\n\t\n\t\n\tdef on_cursor_motion(self, trash, event):\n\t\t# self.icon.get_allocation().x + self.icon.get_allocation().width\t# yields nonsense\n\t\tix2 = 74\n\t\t# Check if cursor is placed on icon\n\t\twhat = None\n\t\tif event.x < ix2:\n\t\t\twhat = {\n\t\t\t\tProfile.LPAD : LEFT,\n\t\t\t\tProfile.RPAD : RIGHT,\n\t\t\t\tProfile.CPAD : nameof(SCButtons.CPADPRESS),\n\t\t\t\tProfile.STICK : nameof(SCButtons.STICKPRESS),\n\t\t\t\tProfile.RSTICK : nameof(SCButtons.RSTICKPRESS),\n\t\t\t\tProfile.DPAD: None,\n\t\t\t}.get(self.name)\n\t\tif what:\n\t\t\tself.app.hilight(what)\n\t\t\tself.over_icon = True\n\t\telse:\n\t\t\tself.app.hilight(self.name)\n\t\t\tself.over_icon = False\n\t\n\t\n\tdef _set_label(self, action):\n\t\tself.label.set_label(action.describe(self.ACTION_CONTEXT))\n\t\n\t\n\tdef update(self):\n\t\tif self.id == Profile.STICK:\n\t\t\tself._set_label(self.app.current.stick)\n\t\telif self.id == Profile.RSTICK:\n\t\t\tself._set_label(self.app.current.rstick)\n\t\telif self.id == Profile.DPAD:\n\t\t\tself._set_label(self.app.current.pads[Profile.DPAD])\n\t\tif self.click_button and self.pressed:\n\t\t\taction = self.app.current.buttons[self.click_button]\n\t\t\tself._update_pressed(action)\n\t\n\t\n\tdef _update_pressed(self, action):\n\t\tescape = lambda t : t.replace(\"<\", \"&lt;\").replace(\">\", \"&gt;\")\n\t\tif isinstance(action, DoubleclickModifier):\n\t\t\tlines = []\n\t\t\tif action.normalaction:\n\t\t\t\ttxt = action.normalaction.describe(self.ACTION_CONTEXT)\n\t\t\t\tlines.append(\"Pressed: %s\" % (escape(txt),))\n\t\t\tif action.holdaction:\n\t\t\t\ttxt = action.holdaction.describe(self.ACTION_CONTEXT)\n\t\t\t\tlines.append(\"Hold: %s\" % (escape(txt),))\n\t\t\tself.pressed.set_markup(\"<small>%s</small>\" % (\"\\n\".join(lines), ))\n\t\telse:\n\t\t\ttxt = escape(action.describe(self.ACTION_CONTEXT))\n\t\t\tself.pressed.set_markup(\"<small>Pressed: %s</small>\" % (txt,))\n\n\nclass ControllerTrigger(ControllerButton):\n\tACTION_CONTEXT = Action.AC_TRIGGER\n\t\n\tdef update(self):\n\t\t# TODO: Use LT and RT in profile as well\n\t\tside = LEFT if self.id == \"LT\" else RIGHT\n\t\tif self.id in TRIGGERS and side in self.app.current.triggers:\n\t\t\tself.label.set_label(self.app.current.triggers[side].describe(self.ACTION_CONTEXT))\n\t\telse:\n\t\t\tself.label.set_label(_(\"(no action)\"))\n\n\nclass ControllerPad(ControllerStick):\n\tACTION_CONTEXT = Action.AC_PAD\n\t\n\t\n\tdef __init__(self, app, name, use_icon, enable_press, widget):\n\t\tControllerStick.__init__(self, app, name, use_icon, enable_press, widget)\n\t\tif name in (Profile.LPAD, Profile.RPAD):\n\t\t\tself.click_button = getattr(SCButtons, name)\n\t\telif name == Profile.CPAD:\n\t\t\tself.click_button = SCButtons.CPADPRESS\n\t\n\t\n\tdef update(self):\n\t\tif self.id == Profile.LPAD:\n\t\t\taction = self.app.current.pads[Profile.LEFT]\n\t\t\tpressed = self.app.current.buttons[SCButtons.LPAD]\n\t\telif self.id == Profile.RPAD:\n\t\t\taction = self.app.current.pads[Profile.RIGHT]\n\t\t\tpressed = self.app.current.buttons[SCButtons.RPAD]\n\t\telse:\n\t\t\taction = self.app.current.pads[Profile.CPAD]\n\t\t\tpressed = self.app.current.buttons[SCButtons.CPADPRESS]\n\t\t\n\t\tself._set_label(action)\n\t\tif self.pressed:\n\t\t\tself._update_pressed(pressed)\n\n\nclass ControllerGyro(ControllerWidget):\n\tACTION_CONTEXT = Action.AC_GYRO\n\t\n\tdef __init__(self, app, name, use_icon, widget):\n\t\tself.pressed = Gtk.Label()\n\t\tControllerWidget.__init__(self, app, name, use_icon, widget)\n\t\t\n\t\tgrid = Gtk.Grid()\n\t\tgrid.set_column_spacing(6)\n\t\tself.label.set_max_width_chars(LONG_TEXT)\n\t\tself.label.set_halign(Gtk.Align.START)\n\t\tself.pressed.set_max_width_chars(LONG_TEXT)\n\t\tself.pressed.set_halign(Gtk.Align.START)\n\t\tself.pressed.set_valign(Gtk.Align.END)\n\t\tgrid.attach(self.icon, 1, 1, 1, 2)\n\t\tgrid.attach(self.label, 2, 1, 1, 1)\n\t\tgrid.attach(self.pressed, 2, 2, 1, 1)\n\t\tself.over_icon = False\n\t\tself.widget.add(grid)\n\t\tself.widget.show_all()\n\t\n\t\n\tdef on_click(self, *a):\n\t\tself.app.show_editor(self.id)\n\t\n\t\n\tdef _set_label(self, action):\n\t\tif is_gyro_enable(action):\n\t\t\taction = next(itertools.islice(action.mods.values(), 0, 1)) or action.default\n\t\tif isinstance(action, MultiAction):\n\t\t\trv = []\n\t\t\tfor a in action.actions:\n\t\t\t\td = a.describe(self.ACTION_CONTEXT)\n\t\t\t\tif not d in rv : rv.append(d)\n\t\t\tself.label.set_label(\"\\n\".join(rv))\n\t\t\treturn\n\t\tself.label.set_label(action.describe(self.ACTION_CONTEXT))\n\t\n\t\n\tdef update(self):\n\t\tself._set_label(self.app.current.gyro)\n"
  },
  {
    "path": "scc/gui/creg/__init__.py",
    "content": "#!/usr/bin/env python2\n"
  },
  {
    "path": "scc/gui/creg/constants.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Controller Registration Constants\n\nJust huge chunk of constants put aside to make impotant code more readable\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.constants import SCButtons, STICK, LEFT, RIGHT\nfrom scc.gui import BUTTON_ORDER\n\nX = 0\nY = 1\n\nAXIS_ORDER = (\n\t(\"stick_x\", X), (\"stick_y\", Y),\n\t(\"rpad_x\", X),  (\"rpad_y\", Y),\n\t(\"lpad_x\", X),  (\"lpad_y\", Y),\n\t(\"ltrig\", X),\t# index 6\n\t(\"rtrig\", X),\n)\n\nSTICK_PAD_AREAS = {\n\t# Numbers here are indexes to AXIS_ORDER tuple\n\t\"STICK\":\t(STICK, (0, 1)),\n\t\"RPAD\":\t\t(RIGHT, (2, 3)),\n\t\"LPAD\":\t\t(LEFT, (4, 5)),\n}\n\nTRIGGER_AREAS = {\n\t# Numbers here are indexes to AXIS_ORDER tuple\n\t\"LT\": 6,\n\t\"RT\": 7\n}\n\nAXIS_TO_BUTTON = {\n\t# Maps stick and dpad axes to their respective \"pressed\" button\n\t\"stick_x\":\tSCButtons.STICKPRESS,\n\t\"stick_y\":\tSCButtons.STICKPRESS,\n\t\"rpad_x\":\tSCButtons.RPAD,\n\t\"rpad_y\":\tSCButtons.RPAD,\n\t\"lpad_x\":\tSCButtons.LPAD,\n\t\"lpad_y\":\tSCButtons.LPAD,\n}\n\nSDL_TO_SCC_NAMES = {\n\t'guide':\t\t\t'C',\n\t'leftstick':\t\t'STICKPRESS',\n\t'rightstick':\t\t'RPAD',\n\t'leftshoulder':\t\t'LB',\n\t'rightshoulder':\t'RB',\n}\n\nSDL_AXES = (\n\t# This tuple has to use same order as AXIS_ORDER\n\t'leftx', 'lefty',\n\t'rightx', 'righty',\n\t\"dpadx\", \"dpady\",\n\t'lefttrigger',\n\t'righttrigger'\n)\n\n\nSDL_DPAD = {\n\t# Numbers here are indexes to AXIS_ORDER tuple\n\t# Booleans here are True for positive movements (down/right) and\n\t# False for negative (up/left)\n\t'dpdown':\t(5, True),\n\t'dpleft':\t(4, False),\n\t'dpright':\t(4, True),\n\t'dpup':\t\t(5, False),\n}\n"
  },
  {
    "path": "scc/gui/creg/data.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Controller Registration data\n\nDummy container classes\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.constants import STICK_PAD_MAX, STICK_PAD_MIN\nfrom scc.gui.creg.constants import AXIS_TO_BUTTON\n\nimport logging\nlog = logging.getLogger(\"CReg.data\")\n\n\nclass AxisData(object):\n\t\"\"\"\n\t(Almost) dumb container.\n\tStores position, center and limits for single axis.\n\t\"\"\"\n\t\n\tdef __init__(self, name, xy, min=STICK_PAD_MAX, max=STICK_PAD_MIN):\n\t\tself.name = name\n\t\tself.area = name.split(\"_\")[0].upper()\n\t\tif self.area.endswith(\"TRIG\"): self.area = self.area[0:-3]\n\t\tself.xy = xy\n\t\tself.pos = 0\n\t\tself.center = 0\n\t\tself.min = min\n\t\tself.max = max\n\t\tself.invert = False\n\t\tself.cursor = None\n\t\n\t\n\tdef reset(self):\n\t\t\"\"\"\n\t\tResets min and max value so axis can (has to be) recalibrated again\n\t\t\"\"\"\n\t\tself.min = STICK_PAD_MAX\n\t\tself.max = STICK_PAD_MIN\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<Axis data '%s'>\" % (self.name, )\n\t\n\t\n\tdef set_position(self, value):\n\t\t\"\"\"\n\t\tReturns (changed, x), value determining if axis limits were changed and\n\t\tcurrent position position.\n\t\ttranslated to range of (STICK_PAD_MIN, STICK_PAD_MAX)\n\t\t\"\"\"\n\t\tchanged = False\n\t\tif value < self.min:\n\t\t\tself.min = value\n\t\t\tchanged = True\n\t\tif value > self.max:\n\t\t\tself.max = value\n\t\t\tchanged = True\n\t\tself.pos = value\n\t\ttry:\n\t\t\tr = (STICK_PAD_MAX - STICK_PAD_MIN) / (self.max - self.min)\n\t\t\tv = (self.pos - self.min) * r\n\t\t\tif self.invert:\n\t\t\t\treturn changed, STICK_PAD_MAX - v\n\t\t\telse:\n\t\t\t\treturn changed, v + STICK_PAD_MIN\n\t\texcept ZeroDivisionError:\n\t\t\treturn changed, 0\n\n\nclass DPadEmuData(object):\n\t\"\"\"\n\tDumb container that stores dpad emulation data.\n\tDPAd emulation is used, for example, on PS3 controller, where dpad does not\n\tinputs as 2 axes, but as 4 buttons.\n\t\n\tThis class stores mapping of one button to one half of axis.\n\t\"\"\"\n\t\n\tdef __init__(self, axis_data, positive):\n\t\tself.axis_data = axis_data\n\t\tself.positive  = positive\n\t\tself.button = AXIS_TO_BUTTON[axis_data.name]\n"
  },
  {
    "path": "scc/gui/creg/dialog.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Controller Registration\n\nDialog that asks a lot of question to create configuration node in config file.\nMost \"interesting\" thing here may be that this works 100% independently from\ndaemon.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, GLib, GdkPixbuf\nfrom scc.gui.creg.constants import SDL_TO_SCC_NAMES, STICK_PAD_AREAS\nfrom scc.gui.creg.constants import AXIS_ORDER, SDL_AXES, SDL_DPAD\nfrom scc.gui.creg.constants import BUTTON_ORDER, TRIGGER_AREAS\nfrom scc.gui.creg.grabs import InputGrabber, TriggerGrabber, StickGrabber\nfrom scc.gui.creg.data import AxisData, DPadEmuData\nfrom scc.gui.creg.tester import Tester\nfrom scc.gui.controller_image import ControllerImage\nfrom scc.gui.editor import Editor\nfrom scc.gui.app import App\nfrom scc.constants import SCButtons, STICK_PAD_MAX, STICK_PAD_MIN\nfrom scc.paths import get_config_path, get_share_path\nfrom scc.tools import nameof, clamp\nfrom scc.config import Config\n\nimport evdev\nimport os, logging, json, re\nlog = logging.getLogger(\"CRegistration\")\n\n\nclass ControllerRegistration(Editor):\n\tGLADE = \"creg.glade\"\n\tUNASSIGNED_COLOR = \"#FFFF0000\"\t\t# ARGB\n\tOBSERVE_COLORS = (\n\t\tApp.OBSERVE_COLOR,\n\t\t# Following just replaces 'full alpha' in ARGB with various alpha values\n\t\tApp.OBSERVE_COLOR.replace(\"#FF\", \"#DF\"),\n\t\tApp.OBSERVE_COLOR.replace(\"#FF\", \"#BF\"),\n\t\tApp.OBSERVE_COLOR.replace(\"#FF\", \"#9F\"),\n\t\tApp.OBSERVE_COLOR.replace(\"#FF\", \"#7F\"),\n\t)\n\n\tdef __init__(self, app):\n\t\tEditor.__init__(self)\n\t\tself.app = app\n\t\tself._gamepad_icon = GdkPixbuf.Pixbuf.new_from_file(\n\t\t\t\tos.path.join(self.app.imagepath, \"controller-icons\", \"evdev-0.svg\"))\n\t\tself._other_icon = GdkPixbuf.Pixbuf.new_from_file(\n\t\t\t\tos.path.join(self.app.imagepath, \"controller-icons\", \"unknown.svg\"))\n\t\tself._axis_data = [ AxisData(name, xy) for (name, xy) in AXIS_ORDER ]\n\t\tself.setup_widgets()\n\t\tself._controller_image = None\n\t\tself._evdevice = None\n\t\tself._tester = None\n\t\tself._grabber = None\n\t\tself._input_axes = {}\n\t\tself._mappings = {}\n\t\tself._hilights = {}\n\t\tself._unassigned = set()\n\t\tself._hilighted_area = None\n\t\tself.refresh_devices()\n\n\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\tcursors = {}\n\t\tfor axis in self._axis_data:\n\t\t\tif \"trig\" in axis.name:\n\t\t\t\tcontinue\n\t\t\taxis.cursor = cursors[axis.area] = ( cursors.get(axis.area) or\n\t\t\t\tGtk.Image.new_from_file(os.path.join(\n\t\t\t\tself.app.imagepath, \"test-cursor.svg\")) )\n\t\t\taxis.cursor.position = [ 0, 0 ]\n\t\tself.builder.get_object(\"cbInvert_1\").set_active(True)\n\t\tself.builder.get_object(\"cbInvert_3\").set_active(True)\n\t\tself.builder.get_object(\"cbInvert_5\").set_active(True)\n\n\n\t@staticmethod\n\tdef does_he_looks_like_a_gamepad(dev):\n\t\t\"\"\"\n\t\tExamines device capabilities and decides if it passes for gamepad.\n\t\tDevice is considered gamepad-like if has at least one button with\n\t\tkeycode in gamepad range and at least two axes.\n\t\t\"\"\"\n\n\t\t# Strip special symbols\n\t\tname = re.sub( r\"[^a-zA-Z0-9.]+\", ' ', dev.name.lower() )\n\n\t\t# ... but some cheating first\n\t\tif \"keyboard\" in name:\n\t\t\treturn False\n\t\tif \"touchpad\" in name:\n\t\t\treturn False\n\t\tif \"mouse\" in name:\n\t\t\treturn False\n\t\tif \"gamepad\" in name:\n\t\t\treturn True\n\t\tcaps = dev.capabilities(verbose=False)\n\t\tif evdev.ecodes.EV_ABS in caps: # Has axes\n\t\t\tif evdev.ecodes.EV_KEY in caps: # Has buttons\n\t\t\t\tfor button in caps[evdev.ecodes.EV_KEY]:\n\t\t\t\t\tif button >= evdev.ecodes.BTN_0 and button <= evdev.ecodes.BTN_GEAR_UP:\n\t\t\t\t\t\treturn True\n\t\treturn False\n\n\n\tdef load_sdl_mappings(self):\n\t\t\"\"\"\n\t\tAttempts to load mappings from gamecontrollerdb.txt.\n\n\t\tReturn True on success.\n\t\t\"\"\"\n\t\t# Build list of button and axes\n\t\tbuttons = self._tester.buttons\n\t\taxes = self._tester.axes\n\n\t\t# Generate database ID\n\t\twordswap = lambda i: ((i & 0xFF) << 8) | ((i & 0xFF00) >> 8)\n\t\t# TODO: version?\n\t\tweird_id = \"%.4x%.8x%.8x%.8x0000\" % (\n\t\t\t\twordswap(self._evdevice.info.bustype),\n\t\t\t\twordswap(self._evdevice.info.vendor),\n\t\t\t\twordswap(self._evdevice.info.product),\n\t\t\t\twordswap(self._evdevice.info.version)\n\t\t)\n\n\t\t# Search in database\n\t\ttry:\n\t\t\tdb = open(os.path.join(get_share_path(), \"gamecontrollerdb.txt\"), \"r\")\n\t\texcept Exception as e:\n\t\t\tlog.error('Failed to load gamecontrollerdb')\n\t\t\tlog.exception(e)\n\t\t\treturn False\n\n\t\tfor line in db.readlines():\n\t\t\tif line.startswith(weird_id):\n\t\t\t\tlog.info(\"Loading mappings for '%s' from gamecontrollerdb\", weird_id)\n\t\t\t\tlog.debug(\"Buttons: %s\", buttons)\n\t\t\t\tlog.debug(\"Axes: %s\", axes)\n\t\t\t\tfor token in line.strip().split(\",\"):\n\t\t\t\t\tif \":\" in token:\n\t\t\t\t\t\tk, v = token.split(\":\", 1)\n\t\t\t\t\t\tk = SDL_TO_SCC_NAMES.get(k, k)\n\t\t\t\t\t\tif v.startswith(\"b\") and hasattr(SCButtons, k.upper()):\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tkeycode = buttons[int(v.strip(\"b\"))]\n\t\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\t\tlog.warning(\"Skipping unknown gamecontrollerdb button->button mapping: '%s'\", v)\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\tbutton  = getattr(SCButtons, k.upper())\n\t\t\t\t\t\t\tself._mappings[keycode] = button\n\t\t\t\t\t\telif v.startswith(\"b\") and k in SDL_AXES:\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tkeycode = buttons[int(v.strip(\"b\"))]\n\t\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\t\tlog.warning(\"Skipping unknown gamecontrollerdb button->axis mapping: '%s'\", v)\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\tlog.info(\"Adding button -> axis mapping for %s\", k)\n\t\t\t\t\t\t\tself._mappings[keycode] = self._axis_data[SDL_AXES.index(k)]\n\t\t\t\t\t\t\tself._mappings[keycode].min = STICK_PAD_MIN\n\t\t\t\t\t\t\tself._mappings[keycode].max = STICK_PAD_MAX\n\t\t\t\t\t\telif v.startswith(\"h\") and 16 in axes and 17 in axes:\n\t\t\t\t\t\t\t# Special case for evdev hatswitch\n\t\t\t\t\t\t\tif v == \"h0.1\" and k == \"dpup\":\n\t\t\t\t\t\t\t\tself._mappings[16] = self._axis_data[SDL_AXES.index(\"dpadx\")]\n\t\t\t\t\t\t\t\tself._mappings[17] = self._axis_data[SDL_AXES.index(\"dpady\")]\n\t\t\t\t\t\telif k in SDL_AXES:\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tcode = axes[int(v.strip(\"a\"))]\n\t\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\t\tlog.warning(\"Skipping unknown gamecontrollerdb axis: '%s'\", v)\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\tself._mappings[code] = self._axis_data[SDL_AXES.index(k)]\n\t\t\t\t\t\telif k in SDL_DPAD and v.startswith(\"b\"):\n\t\t\t\t\t\t\ttry:\n\t\t\t\t\t\t\t\tkeycode = buttons[int(v.strip(\"b\"))]\n\t\t\t\t\t\t\texcept IndexError:\n\t\t\t\t\t\t\t\tlog.warning(\"Skipping unknown gamecontrollerdb button->dpad mapping: %s\", v)\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\tindex, positive = SDL_DPAD[k]\n\t\t\t\t\t\t\tdata = DPadEmuData(self._axis_data[index], positive)\n\t\t\t\t\t\t\tself._mappings[keycode] = data\n\t\t\t\t\t\telif k == \"platform\":\n\t\t\t\t\t\t\t# Not interesting\n\t\t\t\t\t\t\tpass\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tlog.warning(\"Skipping unknown gamecontrollerdb mapping %s:%s\", k, v)\n\t\t\t\treturn True\n\t\telse:\n\t\t\tlog.debug(\"Mappings for '%s' not found in gamecontrollerdb\", weird_id)\n\n\t\treturn False\n\n\n\tdef generate_mappings(self):\n\t\t\"\"\"\n\t\tGenerates initial mappings, just to have some preset to show.\n\t\t\"\"\"\n\t\tbuttons = list(BUTTON_ORDER)\n\t\taxes = list(self._axis_data)\n\t\tlog.info(\"Generating default mappings\")\n\t\tlog.debug(\"Buttons: %s\", self._tester.buttons)\n\t\tlog.debug(\"Axes: %s\", self._tester.axes)\n\t\tfor axis in self._tester.axes:\n\t\t\tself._mappings[axis], axes = axes[0], axes[1:]\n\t\t\tif len(axes) == 0: break\n\t\tfor button in self._tester.buttons:\n\t\t\tself._mappings[button], buttons = buttons[0], buttons[1:]\n\t\t\tif len(buttons) == 0: break\n\n\n\tdef generate_unassigned(self):\n\t\tunassigned = set()\n\t\tunassigned.clear()\n\t\tassigned_axes = set([ x for x in self._mappings.values()\n\t\t\t\t\t\t\tif isinstance(x, AxisData) ])\n\t\tassigned_axes.update([ x.axis_data for x in self._mappings.values()\n\t\t\t\t\t\t\tif isinstance(x, DPadEmuData) ])\n\t\tassigned_buttons = set([ x for x in self._mappings.values()\n\t\t\t\t\t\t\tif x in SCButtons.__members__.values() ])\n\t\tassigned_buttons.update([ x.button for x in self._mappings.values()\n\t\t\t\t\t\t\tif isinstance(x, DPadEmuData) ])\n\t\tfor a in BUTTON_ORDER:\n\t\t\tif a not in assigned_buttons:\n\t\t\t\tif a not in (SCButtons.RGRIP, SCButtons.LGRIP):\n\t\t\t\t\t# Grips are not colored red as most of controllers doesn't\n\t\t\t\t\t# have them anyway\n\t\t\t\t\tunassigned.add(nameof(a))\n\t\tfor a in TRIGGER_AREAS:\n\t\t\taxis = self._axis_data[TRIGGER_AREAS[a]]\n\t\t\tif axis in assigned_axes and a in unassigned:\n\t\t\t\tunassigned.remove(a)\n\t\t\telif axis not in assigned_axes:\n\t\t\t\tunassigned.add(a)\n\t\tfor a in STICK_PAD_AREAS:\n\t\t\tarea_name, axes = STICK_PAD_AREAS[a]\n\t\t\thas_mapping = bool(sum([\n\t\t\t\tself._axis_data[index] in assigned_axes for index in axes ]))\n\t\t\tif not has_mapping:\n\t\t\t\tunassigned.add(area_name)\n\n\t\thilight = unassigned - self._unassigned\n\t\tunhilight = self._unassigned - unassigned\n\t\tself._unassigned = unassigned\n\t\tfor a in hilight:   self.hilight(a, self.UNASSIGNED_COLOR)\n\t\tfor a in unhilight: self.unhilight(a)\n\n\n\tdef generate_raw_data(self):\n\t\tcbControllerButtons = self.builder.get_object(\"cbControllerButtons\")\n\t\tcbControllerType = self.builder.get_object(\"cbControllerType\")\n\t\tbuffRawData = self.builder.get_object(\"buffRawData\")\n\t\tconfig = dict(\n\t\t\tbuttons = {},\n\t\t\taxes = {},\n\t\t\tdpads = {},\n\t\t)\n\n\n\t\tdef axis_to_json(axisdata):\n\t\t\tindex = self._axis_data.index(axisdata)\n\t\t\ttarget_axis, xy = AXIS_ORDER[index]\n\t\t\tmin, max = axisdata.min, axisdata.max\n\t\t\tif axisdata.invert:\n\t\t\t\tmin, max = max, min\n\n\t\t\trv = dict(\n\t\t\t\taxis = target_axis,\n\t\t\t\tmin = min,\n\t\t\t\tmax = max\n\t\t\t)\n\t\t\tif target_axis not in (\"ltrig\", \"rtrig\"):\n\t\t\t\t# Deadzone is generated with assumption that all sticks are left\n\t\t\t\t# in center position before 'Save' is pressed.\n\t\t\t\tcenter = axisdata.min + (axisdata.max - axisdata.min) / 2\n\t\t\t\tdeadzone = abs(axisdata.pos - center) * 2 + 2\n\t\t\t\tif abs(axisdata.max) < 2:\n\t\t\t\t\t# DPADs\n\t\t\t\t\tdeadzone = 0\n\t\t\t\trv[\"deadzone\"] = deadzone\n\n\t\t\treturn rv\n\n\t\tfor code, target in self._mappings.items():\n\t\t\tif target in SCButtons.__members__.values():\n\t\t\t\tconfig['buttons'][code] = nameof(target)\n\t\t\telif isinstance(target, DPadEmuData):\n\t\t\t\tconfig['dpads'][code] = axis_to_json(target.axis_data)\n\t\t\t\tconfig['dpads'][code][\"positive\"] = target.positive\n\t\t\t\tconfig['dpads'][code][\"button\"] = nameof(target.button)\n\t\t\telif isinstance(target, AxisData):\n\t\t\t\tconfig['axes'][code] = axis_to_json(target)\n\n\t\tgroup = cbControllerButtons.get_model()[cbControllerButtons.get_active()][0]\n\t\tcontroller = cbControllerType.get_model()[cbControllerType.get_active()][0]\n\t\tconfig['gui'] = {\n\t\t\t'background' : controller,\n\t\t\t'buttons': self._groups[group]\n\t\t}\n\n\t\tbuffRawData.set_text(json.dumps(config, sort_keys=True,\n\t\t\t\t\t\tindent=4, separators=(',', ': ')))\n\n\n\tdef load_buttons(self):\n\t\tcbControllerButtons = self.builder.get_object(\"cbControllerButtons\")\n\t\tself._groups = {}\n\t\tmodel = cbControllerButtons.get_model()\n\t\tgroups = json.loads(open(os.path.join(self.app.imagepath,\n\t\t\t\"button-images\", \"groups.json\"), \"r\").read())\n\t\tfor group in groups:\n\t\t\timages = [ GdkPixbuf.Pixbuf.new_from_file(os.path.join(\n\t\t\t\tself.app.imagepath, \"button-images\", \"%s.svg\" % (b, )))\n\t\t\t\tfor b in group['buttons'][0:4] ]\n\t\t\tmodel.append( [group['key']] + images )\n\t\t\tself._groups[group['key']] = group['buttons']\n\t\tcbControllerButtons.set_active(0)\n\n\n\tdef save_registration(self):\n\t\tself.generate_raw_data()\n\t\tbuffRawData = self.builder.get_object(\"buffRawData\")\n\t\tjsondata = buffRawData.get_text(buffRawData.get_start_iter(),\n\t\t\tbuffRawData.get_end_iter(), True)\n\t\ttry:\n\t\t\tos.makedirs(os.path.join(get_config_path(), \"devices\"))\n\t\texcept: pass\n\n\t\tfilename = self._evdevice.name.strip().replace(\"/\",\"\")\n\t\tif self._tester.driver == \"hid\":\n\t\t\tfilename = \"%.4x:%.4x-%s\" % (self._evdevice.info.vendor,\n\t\t\t\tself._evdevice.info.product, filename)\n\n\t\tconfig_file = os.path.join(get_config_path(), \"devices\",\n\t\t\t\t\"%s-%s.json\" % (self._tester.driver, filename,))\n\n\t\topen(config_file, \"w\").write(jsondata)\n\t\tlog.debug(\"Controller configuration '%s' written\", config_file)\n\n\t\tself.kill_tester()\n\t\tself.window.destroy()\n\t\tGLib.timeout_add_seconds(1, self.app.dm.rescan)\n\n\n\tdef on_buffRawData_changed(self, buffRawData, *a):\n\t\tbtNext = self.builder.get_object(\"btNext\")\n\t\tjsondata = buffRawData.get_text(buffRawData.get_start_iter(),\n\t\t\tbuffRawData.get_end_iter(), True)\n\t\ttry:\n\t\t\tjson.loads(jsondata)\n\t\t\tbtNext.set_sensitive(True)\n\t\texcept Exception as e:\n\t\t\t# User can modify generated json code before hitting save,\n\t\t\t# but if he writes something unparsable, save button is disabled\n\t\t\tbtNext.set_sensitive(False)\n\n\n\tdef on_ibHIDWarning_response(self, *a):\n\t\trvHIDWarning = self.builder.get_object(\"rvHIDWarning\")\n\t\trvHIDWarning.set_reveal_child(False)\n\n\n\tdef on_btNext_clicked(self, *a):\n\t\trvController = self.builder.get_object(\"rvController\")\n\t\ttvDevices = self.builder.get_object(\"tvDevices\")\n\t\tstDialog = self.builder.get_object(\"stDialog\")\n\t\tbtBack = self.builder.get_object(\"btBack\")\n\t\tbtNext = self.builder.get_object(\"btNext\")\n\t\tpages = stDialog.get_children()\n\t\tindex = pages.index(stDialog.get_visible_child())\n\t\tif index == 0:\n\t\t\tmodel, iter = tvDevices.get_selection().get_selected()\n\t\t\tdev = evdev.InputDevice(model[iter][0])\n\t\t\tif (dev.info.vendor, dev.info.product) == (0x054c, 0x09cc):\n\t\t\t\t# Special case for PS4 controller\n\t\t\t\tcbDS4 = self.builder.get_object(\"cbDS4\")\n\t\t\t\timgDS4 = self.builder.get_object(\"imgDS4\")\n\t\t\t\timgDS4.set_from_file(os.path.join(\n\t\t\t\t\t\tself.app.imagepath, \"ds4-small.svg\"))\n\t\t\t\tcbDS4.set_active(Config()['drivers']['ds4drv'])\n\t\t\t\tstDialog.set_visible_child(pages[3])\n\t\t\t\tbtBack.set_sensitive(True)\n\t\t\t\tbtNext.set_label(\"_Restart Emulation\")\n\t\t\t\treturn\n\t\t\tstDialog.set_visible_child(pages[1])\n\t\t\tself.load_buttons()\n\t\t\tself.refresh_controller_image()\n\t\t\trvController.set_reveal_child(True)\n\t\t\tself.load_buttons()\n\t\t\tself.refresh_controller_image()\n\t\t\tbtBack.set_sensitive(True)\n\t\telif index == 1:\n\t\t\t# Disable Next button and determine which driver should be used\n\t\t\tbtNext.set_sensitive(False)\n\t\t\tmodel, iter = tvDevices.get_selection().get_selected()\n\t\t\tdev = evdev.InputDevice(model[iter][0])\n\t\t\tself.prepare_registration(dev)\n\t\telif index == 2:\n\t\t\tself.save_registration()\n\t\telif index == 3:\n\t\t\t# Next pressed on DS4 info page\n\t\t\t# where it means 'Restart Emulation and close'\n\t\t\tself.app.dm.stop()\n\t\t\tGLib.timeout_add_seconds(1, self.app.dm.start)\n\t\t\tself.kill_tester()\n\t\t\tself.window.destroy()\n\n\n\tdef on_btBack_clicked(self, *a):\n\t\tstDialog = self.builder.get_object(\"stDialog\")\n\t\tbtBack = self.builder.get_object(\"btBack\")\n\t\tbtNext = self.builder.get_object(\"btNext\")\n\t\tpages = stDialog.get_children()\n\t\tindex = pages.index(stDialog.get_visible_child())\n\t\tif index == 1:\n\t\t\tstDialog.set_visible_child(pages[0])\n\t\t\tbtBack.set_sensitive(False)\n\t\t\tbtNext.set_sensitive(True)\n\t\telif index == 2:\n\t\t\tstDialog.set_visible_child(pages[1])\n\t\t\trvController = self.builder.get_object(\"rvController\")\n\t\t\tself._controller_image.get_parent().remove(self._controller_image)\n\t\t\trvController.add(self._controller_image)\n\t\t\tbtNext.set_label(\"_Next\")\n\t\telif index == 3:\n\t\t\tstDialog.set_visible_child(pages[0])\n\t\t\tbtNext.set_label(\"_Next\")\n\t\t\tbtBack.set_sensitive(False)\n\t\t\tbtNext.set_sensitive(True)\n\n\n\tdef on_cbDS4_toggled(self, button):\n\t\tconfig = Config()\n\t\tconfig['drivers']['ds4drv'] = button.get_active()\n\t\tconfig.save()\n\n\n\tdef prepare_registration(self, dev):\n\t\tself._evdevice = dev\n\t\tself.set_hid_enabled(True)\n\n\t\tdef retry_with_evdev(tester, code):\n\t\t\tif tester:\n\t\t\t\tfor s in tester.__signals: tester.disconnect(s)\n\t\t\t\ttester.stop()\n\t\t\tself.set_hid_enabled(False)\n\t\t\tif code != 0:\n\t\t\t\tlog.error(\"HID driver test failed (code %s)\", code)\n\t\t\tif code == 2:\n\t\t\t\t# Special case, device may be usable, but there is no access\n\t\t\t\trvHIDWarning = self.builder.get_object(\"rvHIDWarning\")\n\t\t\t\trvHIDWarning.set_reveal_child(False)\n\t\t\tlog.debug(\"Trying to use '%s' with evdev driver...\", dev.fn)\n\t\t\tself._tester = Tester(\"evdev\", dev.fn)\n\t\t\tself._tester.__signals = [\n\t\t\t\tself._tester.connect('ready', self.on_registration_ready),\n\t\t\t\tself._tester.connect('error', self.on_device_open_failed)\n\t\t\t]\n\t\t\tself._tester.start()\n\n\t\t# Check VID+PID info and whether HID driver support is enabled\n\t\tif (dev.info.vendor == 0 and dev.info.product == 0 or\n\t\t\tnot self.app.config[\"drivers\"].get(\"hiddrv\")):\n\t\t\t# Not an USB device, skip HID test altogether\n\t\t\tretry_with_evdev(None, 0)\n\t\telse:\n\t\t\tlog.debug(\"Trying to use %.4x:%.4x with HID driver...\",\n\t\t\t\t\tdev.info.vendor, dev.info.product)\n\t\t\tself._tester = Tester(\"hid\", \"%.4x:%.4x\" % (dev.info.vendor, dev.info.product))\n\t\t\tself._tester.__signals = [\n\t\t\t\tself._tester.connect('ready', self.on_registration_ready),\n\t\t\t\tself._tester.connect('error', retry_with_evdev),\n\t\t\t]\n\t\t\tself._tester.start()\n\n\n\tdef on_registration_ready(self, tester):\n\t\tcbAccessMode = self.builder.get_object(\"cbAccessMode\")\n\t\tfxController = self.builder.get_object(\"fxController\")\n\t\tcbEmulateC = self.builder.get_object(\"cbEmulateC\")\n\t\tstDialog = self.builder.get_object(\"stDialog\")\n\t\tbtNext = self.builder.get_object(\"btNext\")\n\n\t\tself.set_cb(cbAccessMode, tester.driver)\n\t\tcbAccessMode.set_sensitive(True)\n\n\t\tif not self._mappings:\n\t\t\tself._mappings = {}\n\t\t\tif not self.load_sdl_mappings():\n\t\t\t\tself.generate_mappings()\n\t\t\tself.generate_unassigned()\n\t\t\tself.generate_raw_data()\n\n\t\tfor s in tester.__signals: tester.disconnect(s)\n\t\ttester.__signals = [\n\t\t\ttester.connect('axis', self.on_tester_axis),\n\t\t\ttester.connect('button', self.on_tester_button),\n\t\t]\n\n\t\tself._controller_image.get_parent().remove(self._controller_image)\n\t\tfxController.add(self._controller_image)\n\t\tpages = stDialog.get_children()\n\t\tstDialog.set_visible_child(pages[2])\n\t\tcbEmulateC.grab_focus()\n\t\tbtNext.set_label(\"_Save\")\n\t\tbtNext.set_sensitive(True)\n\n\n\tdef on_device_open_failed(self, *a):\n\t\t\"\"\"\n\t\tCalled when all (or user-selected) driver fails\n\t\tto communicate with controller.\n\n\t\tShoudln't be really possible, but something\n\t\t_has_ to happen in such case.\n\t\t\"\"\"\n\t\td = Gtk.MessageDialog(parent=self.window,\n\t\t\tflags = Gtk.DialogFlags.MODAL,\n\t\t\ttype = Gtk.MessageType.ERROR,\n\t\t\tbuttons = Gtk.ButtonsType.OK,\n\t\t\tmessage_format = _(\"Failed to open device\")\n\t\t)\n\t\td.run()\n\t\td.destroy()\n\t\tself.window.destroy()\n\n\n\tdef kill_tester(self, *a):\n\t\t\"\"\" Called when window is closed \"\"\"\n\t\tif self._tester:\n\t\t\ttester, self._tester = self._tester, None\n\t\t\tfor s in tester.__signals: tester.disconnect(s)\n\t\t\ttester.stop()\n\n\n\tdef set_hid_enabled(self, enabled):\n\t\t\"\"\" Enables or disables option to use HID driver \"\"\"\n\t\tcbAccessMode = self.builder.get_object(\"cbAccessMode\")\n\t\tfor x in cbAccessMode.get_model():\n\t\t\tif x[0] == \"hid\":\n\t\t\t\tx[1] = _(\"USB HID (recommended)\") if enabled else _(\"USB HID\")\n\t\t\t\tx[2] = enabled\n\n\n\tdef on_cbAccessMode_changed(self, cb):\n\t\tif self._tester:\n\t\t\tbtNext = self.builder.get_object(\"btNext\")\n\t\t\ttarget = cb.get_model().get_value(cb.get_active_iter(), 0)\n\t\t\tif self._tester.driver != target:\n\t\t\t\t# User changed driver that should be used, a lot of stuff has\n\t\t\t\t# to be restarted\n\t\t\t\tlog.debug(\"User-requested driver change\")\n\t\t\t\tself.kill_tester()\n\t\t\t\tcb.set_sensitive(False)\n\t\t\t\tbtNext.set_sensitive(False)\n\t\t\t\tif target == \"hid\":\n\t\t\t\t\tself._tester = Tester(\"hid\", \"%.4x:%.4x\" % (\n\t\t\t\t\t\tself._evdevice.info.vendor, self._evdevice.info.product))\n\t\t\t\telse:\n\t\t\t\t\tself._tester = Tester(\"evdev\", self._evdevice.fn)\n\t\t\t\tself._tester.__signals = [\n\t\t\t\t\tself._tester.connect('ready', self.on_registration_ready),\n\t\t\t\t\tself._tester.connect('error', self.on_device_open_failed),\n\t\t\t\t]\n\t\t\t\tGLib.timeout_add_seconds(1, self._tester.start)\n\n\n\tdef cbInvert_toggled_cb(self, cb, *a):\n\t\tindex = int(cb.get_name().split(\"_\")[-1])\n\t\tself._axis_data[index].invert = cb.get_active()\n\n\n\tdef on_tester_button(self, tester, keycode, pressed):\n\t\tif self._grabber:\n\t\t\treturn self._grabber.on_button(keycode, pressed)\n\n\t\twhat = self._mappings.get(keycode)\n\t\tif isinstance(what, AxisData):\n\t\t\tif pressed:\n\t\t\t\tself.hilight_axis(what, STICK_PAD_MAX)\n\t\t\telse:\n\t\t\t\tself.hilight_axis(what, STICK_PAD_MIN)\n\t\tif isinstance(what, DPadEmuData):\n\t\t\tif pressed:\n\t\t\t\tself.hilight(nameof(what.button))\n\t\t\t\tif what.positive:\n\t\t\t\t\tself.hilight_axis(what.axis_data, STICK_PAD_MAX)\n\t\t\t\telse:\n\t\t\t\t\tself.hilight_axis(what.axis_data, STICK_PAD_MIN)\n\t\t\telse:\n\t\t\t\tself.unhilight(nameof(what.button))\n\t\t\t\tself.hilight_axis(what.axis_data, 0)\n\t\telif what is not None:\n\t\t\tif pressed:\n\t\t\t\tself.hilight(nameof(what))\n\t\t\telse:\n\t\t\t\tself.unhilight(nameof(what))\n\n\n\tdef on_tester_axis(self, tester, number, value):\n\t\tself._input_axes[number] = value\n\t\tif self._grabber:\n\t\t\treturn self._grabber.on_axis(number, value)\n\n\t\taxis = self._mappings.get(number)\n\t\tif axis:\n\t\t\tself.hilight_axis(axis, value)\n\n\n\tdef hilight_axis(self, axis, value):\n\t\tcursor = axis.cursor\n\t\tif cursor is None:\n\t\t\tchanged, value = axis.set_position(value)\n\t\t\tvalue = clamp(STICK_PAD_MIN, value, STICK_PAD_MAX)\n\t\t\t# In this very specific case, trigger uses same min/max as stick\n\t\t\tif value > STICK_PAD_MAX * 2 / 3:\n\t\t\t\tself.hilight(axis.area, self.OBSERVE_COLORS[0])\n\t\t\telif value > STICK_PAD_MAX * 1 / 3:\n\t\t\t\tself.hilight(axis.area, self.OBSERVE_COLORS[1])\n\t\t\telif value > 0:\n\t\t\t\tself.hilight(axis.area, self.OBSERVE_COLORS[2])\n\t\t\telif value > STICK_PAD_MIN * 1 / 3:\n\t\t\t\tself.hilight(axis.area, self.OBSERVE_COLORS[3])\n\t\t\telif value > STICK_PAD_MIN * 2 / 3:\n\t\t\t\tself.hilight(axis.area, self.OBSERVE_COLORS[4])\n\t\t\telse:\n\t\t\t\tself.unhilight(axis.area)\n\t\t\t# Update raw data if needed\n\t\t\tif changed:\n\t\t\t\tself.generate_raw_data()\n\t\telse:\n\t\t\tparent = cursor.get_parent()\n\t\t\tif parent is None:\n\t\t\t\tparent = self._controller_image.get_parent()\n\t\t\t\tparent.add(cursor)\n\t\t\t\tcursor.show()\n\t\t\t# Make position\n\t\t\tchanged, value = axis.set_position(value)\n\t\t\tcursor.position[axis.xy] = clamp(STICK_PAD_MIN, value, STICK_PAD_MAX)\n\t\t\tpx, py = cursor.position\n\t\t\t# Grab values\n\t\t\ttry:\n\t\t\t\ttrash, ay, trash, ah = self._controller_image.get_area_position(axis.area)\n\t\t\t\tax, trash, aw, trash = self._controller_image.get_area_position(axis.area + \"TEST\")\n\t\t\texcept ValueError:\n\t\t\t\t# Area not found\n\t\t\t\tcursor.set_visible(False)\n\t\t\t\treturn\n\t\t\tcw = cursor.get_allocation().width\n\t\t\t# Compute center\n\t\t\tx, y = ax + aw * 0.5 - cw * 0.5, ay + aw * 0.5 - cw * 0.5\n\t\t\t# Add pad position\n\t\t\tx += px * aw / STICK_PAD_MAX * 0.5\n\t\t\ty -= py * aw / STICK_PAD_MAX * 0.5\n\t\t\t# Move circle\n\t\t\tparent.move(cursor, x, y)\n\t\t\tcursor.set_visible(True)\n\t\t\t# Update raw data if needed\n\t\t\tif changed:\n\t\t\t\tself.generate_raw_data()\n\n\n\n\tdef hilight(self, what, color=None):\n\t\tself._hilights[what] = color or self.OBSERVE_COLORS[0]\n\t\tself._controller_image.hilight(self._hilights)\n\n\n\tdef unhilight(self, what):\n\t\tif what in self._hilights:\n\t\t\tdel self._hilights[what]\n\t\tif what in self._unassigned:\n\t\t\tself._hilights[what] = self.UNASSIGNED_COLOR\n\t\tself._controller_image.hilight(self._hilights)\n\n\n\tdef on_exAdditionalOptions_activate(self, ex):\n\t\trv = self.builder.get_object(\"rvAdditionalOptions\")\n\t\trv.set_reveal_child(not ex.get_expanded())\n\n\n\tdef on_exRawData_activate(self, ex):\n\t\trv = self.builder.get_object(\"rvRawData\")\n\t\tdialog = self.builder.get_object(\"Dialog\")\n\t\trv.set_reveal_child(not ex.get_expanded())\n\t\tif not ex.get_expanded():\n\t\t\tdialog.set_resizable(True)\n\n\n\tdef on_area_hover(self, trash, what):\n\t\tself.on_area_leave()\n\t\tself._hilighted_area = what\n\t\tself.hilight(what, self.app.HILIGHT_COLOR)\n\n\n\tdef on_area_leave(self, *a):\n\t\tif self._hilighted_area:\n\t\t\tself.unhilight(self._hilighted_area)\n\t\t\tself._hilighted_area = None\n\n\n\tdef on_area_click(self, trash, what):\n\t\tstDialog = self.builder.get_object(\"stDialog\")\n\t\tpages = stDialog.get_children()\n\t\tindex = pages.index(stDialog.get_visible_child())\n\t\tif index == 2:\n\t\t\tif what in STICK_PAD_AREAS:\n\t\t\t\tarea_name, axes = STICK_PAD_AREAS[what]\n\t\t\t\tmnuStick = self.builder.get_object(\"mnuStick\")\n\t\t\t\tmnuStick._what = \"STICKPRESS\" if what == \"STICK\" else what\n\t\t\t\tmnuStick._axes = [ self._axis_data[index] for index in axes ]\n\t\t\t\tmnuStick.popup(None, None, None, None, 1, Gtk.get_current_event_time())\n\t\t\telif what in TRIGGER_AREAS:\n\t\t\t\tself._grabber = TriggerGrabber(self, self._axis_data[TRIGGER_AREAS[what]])\n\t\t\telif hasattr(SCButtons, what):\n\t\t\t\tself._grabber = InputGrabber(self, getattr(SCButtons, what))\n\n\n\tdef on_mnuStickPress_activate(self, *a):\n\t\tmnuStick = self.builder.get_object(\"mnuStick\")\n\t\tself._grabber = InputGrabber(self, getattr(SCButtons, mnuStick._what),\n\t\t\t\ttext=_(\"Press stick or button...\"))\n\n\n\tdef on_mnuStickmove_activate(self, *a):\n\t\tmnuStick = self.builder.get_object(\"mnuStick\")\n\t\tself._grabber = StickGrabber(self, mnuStick._axes)\n\n\n\tdef on_btCancelInput_clicked(self, *a):\n\t\tif self._grabber:\n\t\t\tself._grabber.cancel()\n\n\n\tdef refresh_devices(self, *a):\n\t\tlog.debug(\"Refreshing device list\")\n\t\tlstDevices = self.builder.get_object(\"lstDevices\")\n\t\tcbShowAllDevices = self.builder.get_object(\"cbShowAllDevices\")\n\t\tlstDevices.clear()\n\t\tfor fname in evdev.list_devices():\n\t\t\tdev = evdev.InputDevice(fname)\n\t\t\tis_gamepad = ControllerRegistration.does_he_looks_like_a_gamepad(dev)\n\t\t\t# bustype 3 is BUS_USB, which is the type used for emulated\n\t\t\t# gamepads. phys is blank for BUS_BLUETOOTH devices.\n\t\t\tif dev.info.bustype == 3 and not dev.phys:\n\t\t\t\t# Skipping over virtual devices so list doesn't show\n\t\t\t\t# gamepads emulated by SCC\n\t\t\t\tcontinue\n\t\t\tif is_gamepad or cbShowAllDevices.get_active():\n\t\t\t\tlstDevices.append(( fname, dev.name,\n\t\t\t\t\tself._gamepad_icon if is_gamepad else self._other_icon ))\n\n\n\tdef refresh_controller_image(self, *a):\n\t\tcbControllerButtons = self.builder.get_object(\"cbControllerButtons\")\n\t\tcbControllerType = self.builder.get_object(\"cbControllerType\")\n\t\trvController = self.builder.get_object(\"rvController\")\n\t\tgroup = cbControllerButtons.get_model()[cbControllerButtons.get_active()][0]\n\t\tcontroller = cbControllerType.get_model()[cbControllerType.get_active()][0]\n\t\tconfig = { 'gui' : { 'background' : controller, 'buttons': self._groups[group] }}\n\n\t\tif self._controller_image:\n\t\t\tself._controller_image.use_config(config)\n\t\telse:\n\t\t\tself._controller_image = ControllerImage(self.app)\n\t\t\tself._controller_image.connect('hover', self.on_area_hover)\n\t\t\tself._controller_image.connect('leave', self.on_area_leave)\n\t\t\tself._controller_image.connect('click', self.on_area_click)\n\t\t\trvController.add(self._controller_image)\n"
  },
  {
    "path": "scc/gui/creg/grabs.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Controller Registration - Grabs\n\nHelper classes for grabbing buttons and axes from physical gamepads.\n\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.constants import STICK_PAD_MAX, STICK_PAD_MIN\nfrom scc.gui.creg.data import AxisData, DPadEmuData\nfrom scc.gui.creg.constants import X, Y\nfrom scc.tools import nameof\n\nimport logging\nlog = logging.getLogger(\"CReg.grabs\")\n\n\nclass InputGrabber(object):\n\t\"\"\"\n\tBase class for input grabbing. Waits for physical button being pressed\n\tby default.\n\t\"\"\"\n\t\n\tdef __init__(self, parent, what, text=_(\"Press a button...\")):\n\t\tself.parent = parent\n\t\tself.what = what\n\t\tself.set_message(text)\n\t\tself.dlgPressButton = parent.builder.get_object(\"dlgPressButton\")\n\t\tself.dlgPressButton.show()\n\t\n\t\n\tdef set_message(self, text):\n\t\tself.parent.builder.get_object(\"lblPressButton\").set_text(text)\n\t\n\t\n\tdef cancel(self):\n\t\tself.dlgPressButton.hide()\n\t\tself.parent._grabber = None\n\t\n\t\n\tdef on_button(self, keycode, pressed):\n\t\tif not pressed:\n\t\t\tself.set_mapping(keycode, self.what)\n\t\n\t\n\tdef set_mapping(self, keycode, what):\n\t\tparent = self.parent\n\t\t\n\t\tif isinstance(what, AxisData) and what in parent._mappings.values():\n\t\t\tfor c in parent._mappings.copy():\n\t\t\t\tif parent._mappings[c] == what:\n\t\t\t\t\tdel parent._mappings[c]\n\t\t\n\t\tparent._mappings[keycode] = what\n\t\tlog.debug(\"Reassigned %s to %s\", keycode, what)\n\t\t\n\t\tif nameof(what) in parent._unassigned:\n\t\t\tparent._unassigned.remove(nameof(what))\n\t\t\tparent.unhilight(nameof(what))\n\t\t\n\t\tself.parent.generate_unassigned()\n\t\tself.parent.generate_raw_data()\n\t\tself.cancel()\n\t\n\t\n\tdef on_axis(self, number, value):\n\t\tpass\n\n\nclass TriggerGrabber(InputGrabber):\n\t\"\"\"\n\tInputGrabber modified to grab trigger bindings.\n\tThat may be button or axis with at least 0-250 range is accepted.\n\t\"\"\"\n\tdef __init__(self, parent, what, text=_(\"Pull a trigger...\")):\n\t\tInputGrabber.__init__(self, parent, what, text)\n\t\tself.orig_pos = { k: parent._input_axes[k] for k in parent._input_axes }\n\t\tself.new_pos  = { k: parent._input_axes[k] for k in parent._input_axes }\n\t\n\t\n\tdef on_button(self, keycode, pressed):\n\t\tif not pressed:\n\t\t\tself.set_mapping(keycode, self.what)\n\t\t\tself.what.min = STICK_PAD_MIN\n\t\t\tself.what.max = STICK_PAD_MAX\n\t\n\t\n\tdef on_axis(self, number, value):\n\t\tif number > 50:\n\t\t\t# TODO: Remove this condition\n\t\t\treturn\n\t\tself.new_pos[number] = value\n\t\tif number not in self.orig_pos:\n\t\t\tself.orig_pos[number] = 0\n\t\t\n\t\t# Get avgerage absolute change for all axes\n\t\tavg = float(sum([\n\t\t\t\tabs( self.orig_pos[k] - self.new_pos[k] )\n\t\t\t\tfor k in self.new_pos\n\t\t\t])) / float(len(self.new_pos))\n\t\t\n\t\t# Get absolute change for _this_ axis\n\t\tchange = abs( self.orig_pos[number] - self.new_pos[number] )\n\t\tif change > 2 and change > avg * 0.5:\n\t\t\t# TODO: change > 2 may be too strict\n\t\t\t# if there is pad going from -1 to 1 somewhere around\n\t\t\tself.axis_change(number, value, change)\n\t\n\t\n\tdef axis_change(self, number, value, change):\n\t\tif value > 250:\n\t\t\tself.what.reset()\n\t\t\tself.set_mapping(number, self.what)\n\t\t\tself.parent.generate_unassigned()\n\t\t\tself.parent.generate_raw_data()\n\t\t\tself.cancel()\n\n\nclass StickGrabber(TriggerGrabber):\n\t\"\"\"\n\tInputGrabber modified to grab stick or pad bindings, in two phases for\n\tboth X and Y axis.\n\t\"\"\"\n\t\n\tdef __init__(self, parent, what):\n\t\tTriggerGrabber.__init__(self, parent, what,\n\t\t\t\ttext=_(\"Move stick left and right...\"))\n\t\tself.xy = X\n\t\tself.grabbed = [ None, None ]\n\t\n\t\n\tdef on_button(self, keycode, pressed):\n\t\t#if len(self.grabbed) == 2 and self.grabbed[X] != None:\n\t\t#\t# Already grabbed one axis, don't grab buttons\n\t\t#\treturn\n\t\tif keycode in self.grabbed:\n\t\t\t# Don't allow same button to be used twice\n\t\t\treturn\n\t\tif not pressed:\n\t\t\tif len(self.grabbed) < 4:\n\t\t\t\tself.grabbed = [ None ] * 4\n\t\t\tif self.grabbed[0] is None:\n\t\t\t\tself.grabbed[0] = keycode\n\t\t\t\tself.set_message(_(\"Move DPAD to right\"))\n\t\t\telif self.grabbed[1] is None:\n\t\t\t\tself.grabbed[1] = keycode\n\t\t\t\tself.set_message(_(\"Move DPAD up\"))\n\t\t\telif self.grabbed[2] is None:\n\t\t\t\tself.grabbed[2] = keycode\n\t\t\t\tself.set_message(_(\"Move DPAD down\"))\n\t\t\telif self.grabbed[3] is None:\n\t\t\t\tself.grabbed[3] = keycode\n\t\t\t\tself.set_message(str(self.grabbed))\n\t\t\t\tgrabbed = [] + self.grabbed\n\t\t\t\tfor w in self.what:\n\t\t\t\t\tfor negative in (False, True):\n\t\t\t\t\t\tkeycode, grabbed = grabbed[0], grabbed[1:]\n\t\t\t\t\t\tw.reset()\n\t\t\t\t\t\tself.set_mapping(keycode, DPadEmuData(w, negative))\n\t\t\t\tself.parent.generate_unassigned()\n\t\t\t\tself.parent.generate_raw_data()\n\t\t\t\tself.cancel()\n\t\n\t\n\tdef on_axis(self, number, value):\n\t\tif len(self.grabbed) > 2:\n\t\t\t# Already started grabbing 4 buttons, don't grab axes now\n\t\t\treturn\n\t\tif self.xy == X:\n\t\t\tself.grabbed[X] = number\n\t\t\tself.xy = Y\n\t\t\tself.set_message(_(\"Move stick up and down...\"))\n\t\telse:\n\t\t\tif number != self.grabbed[X]:\n\t\t\t\tself.grabbed[Y] = number\n\t\t\t\tfor i in range(len(self.grabbed)):\n\t\t\t\t\tself.what[i].reset()\n\t\t\t\t\tself.set_mapping(self.grabbed[i], self.what[i])\n\t\t\t\tself.parent.generate_unassigned()\n\t\t\t\tself.parent.generate_raw_data()\n\t\t\t\tself.cancel()\n"
  },
  {
    "path": "scc/gui/creg/tester.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Controller Registration - Tester\n\nClass that interacts with `scc hid_test` and `scc evdev_test` commands.\n\"\"\"\nfrom gi.repository import GObject, Gio\nfrom scc.tools import find_binary\n\nimport logging\nlog = logging.getLogger(\"CReg.Tester\")\n\n\nclass Tester(GObject.GObject):\n\t\"\"\"\n\tList of signals:\n\t\tready ()\n\t\t\tEmited when subprocess signalizes it's ready to send data\n\t\terror (code)\n\t\t\tEmited when driver test subprocess exits with non-zero return code\n\t\tfinished ()\n\t\t\tEmited when driver test subprocess exits with zero return code\n\t\taxis (number, value)\n\t\t\tEmited when position on axis is changed\n\t\tbutton (keycode, pressed)\n\t\t\tEmited when button on tested gamepad is pressed or released\n\t\"\"\"\n\n\t__gsignals__ = {\n\t\t\"error\"\t\t: (GObject.SignalFlags.RUN_FIRST, None, (int, )),\n\t\t\"ready\"\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\"finished\"\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\"axis\"\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (int, int)),\n\t\t\"button\"\t\t: (GObject.SignalFlags.RUN_FIRST, None, (int, bool)),\n\t}\n\n\tdef __init__(self, driver, device_id):\n\t\tGObject.GObject.__init__(self)\n\t\tself.buffer = b\"\"\n\t\tself.buttons = []\n\t\tself.axes = []\n\t\tself.subprocess = None\n\t\tself.driver = driver\n\t\tself.device_id = device_id\n\t\tself.errorred = False\t# To prevent sending 'error' signal multiple times\n\n\n\tdef __del__(self):\n\t\tif self.subprocess:\n\t\t\tself.subprocess.send_signal(9)\n\n\n\tdef start(self):\n\t\t\"\"\" Starts driver test subprocess \"\"\"\n\t\tcmd = [find_binary(\"scc\")] + [\"test_\" + self.driver, self.device_id]\n\t\tself.subprocess = Gio.Subprocess.new(cmd, Gio.SubprocessFlags.STDOUT_PIPE)\n\t\tself.subprocess.wait_async(None, self._on_finished)\n\t\tself.subprocess.get_stdout_pipe().read_bytes_async(\n\t\t\t32, 0, None, self._on_read)\n\n\n\tdef stop(self):\n\t\tif self.subprocess:\n\t\t\tself.subprocess.send_signal(2)\t# Sigint\n\n\n\tdef _on_finished(self, subprocess, result):\n\t\tsubprocess.wait_finish(result)\n\t\tif self.errorred:\n\t\t\treturn\n\t\tif subprocess.get_exit_status() == 0:\n\t\t\tself.emit('finished')\n\t\telse:\n\t\t\tself.errorred = True\n\t\t\tself.emit('error', subprocess.get_exit_status())\n\n\n\tdef _on_read(self, stream, result):\n\t\ttry:\n\t\t\tdata = stream.read_bytes_finish(result).get_data()\n\t\texcept Exception as e:\n\t\t\tlog.exception(e)\n\t\t\tself.subprocess.send_signal(2)\n\t\t\tif not self.errorred:\n\t\t\t\tself.errorred = True\n\t\t\t\tself.emit('error', 1)\n\t\t\treturn\n\t\tif len(data) > 0:\n\t\t\tself.buffer += data\n\t\t\twhile b\"\\n\" in self.buffer:\n\t\t\t\tline, self.buffer = self.buffer.split(b\"\\n\", 1)\n\t\t\t\ttry:\n\t\t\t\t\tself._on_line(line)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.exception(e)\n\t\t\tself.subprocess.get_stdout_pipe().read_bytes_async(\n\t\t\t\t32, 0, None, self._on_read)\n\n\n\tdef _on_line(self, line):\n\t\tif line.startswith(b\"Axis\"):\n\t\t\ttrash, number, value = line.split(b\" \")\n\t\t\tnumber, value = int(number), int(value)\n\t\t\tself.emit('axis', number, value)\n\t\telif line.startswith(b\"ButtonPress\"):\n\t\t\ttrash, code = line.split(b\" \")\n\t\t\tself.emit('button', int(code), True)\n\t\telif line.startswith(b\"ButtonRelease\"):\n\t\t\ttrash, code = line.split(b\" \")\n\t\t\tself.emit('button', int(code), False)\n\t\telif line.startswith(b\"Ready\"):\n\t\t\tself.emit('ready')\n\t\telif line.startswith(b\"Axes:\"):\n\t\t\tself.axes = [int(x) for x in line.split(b\" \")[1:] if len(x.strip())]\n\t\telif line.startswith(b\"Buttons:\"):\n\t\t\tself.buttons = [int(x) for x in line.split(b\" \")[1:] if len(x.strip())]\n"
  },
  {
    "path": "scc/gui/daemon_manager.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - DaemonManager\n\nStarts, kills and controls sccdaemon instance.\n\nI'd call it DaemonController normally, but having something with\nfull name of \"Steam Controller Controller Daemon Controller\" sounds\nprobably too crazy even for me.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.tools import find_binary, find_button_image, nameof\nfrom scc.paths import get_daemon_socket\nfrom scc.constants import SCButtons\nfrom scc.gui import BUTTON_ORDER\nfrom gi.repository import GObject, Gio, GLib\n\nimport os, json, logging\nlog = logging.getLogger(\"DaemonCtrl\")\n\n\nclass DaemonManager(GObject.GObject):\n\t\"\"\"\n\tCommunicates with daemon socket and provides wrappers around everything\n\tit can do.\n\t\n\tList of signals:\n\t\talive ()\n\t\t\tEmited after daemon is started or found to be alraedy running\n\t\t\n\t\tcontroller-count-changed(count)\n\t\t\tEmited after daemon reports change in controller count, ie when\n\t\t\tnew controller is connnected or disconnected.\n\t\t\tAlso emited shortly after connection to daemon is initiated.\n\t\t\n\t\tdead ()\n\t\t\tEmited after daemon is killed (or exits for some other reason)\n\t\t\n\t\terror (description)\n\t\t\tEmited when daemon reports error, most likely not being able to\n\t\t\taccess to USB dongle.\n\t\t\n\t\tevent (controller, pad_stick_or_button, values)\n\t\t\tAs 'event' signal on Controller. Allows for capturing events from\n\t\t\tall controllers using single signal.\n\t\t\n\t\tprofile-changed (profile)\n\t\t\tEmited after profile set for first controller is changed.\n\t\t\tProfile is filename of currently active profile\n\t\t\n\t\treconfigured()\n\t\t\tEmited when daemon reports change in configuration file\n\t\t\n\t\tunknown-msg (message)\n\t\t\tEmited when message that can't be parsed internally\n\t\t\tis recieved from daemon.\n\t\t\n\t\tversion (ver)\n\t\t\tEmited daemon reports its version - usually only once per connection.\n\t\"\"\"\n\t\n\t__gsignals__ = {\n\t\t\t\"alive\"\t\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t\"controller-count-changed\"\t: (GObject.SignalFlags.RUN_FIRST, None, (int,)),\n\t\t\t\"dead\"\t\t\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t\"error\"\t\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t\t\t\"event\"\t\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,object,object)),\n\t\t\t\"profile-changed\"\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t\t\t\"reconfigured\"\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t\"unknown-msg\"\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t\t\t\"version\"\t\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t}\n\t\n\tRECONNECT_INTERVAL = 5\n\t\n\tdef __init__(self):\n\t\tGObject.GObject.__init__(self)\n\t\tself.alive = None\n\t\tself.connection = None\n\t\tself.connecting = False\n\t\tself.buffer = b\"\"\n\t\tself._connect()\n\t\tself._requests = []\n\t\tself._controllers = []\t\t\t# Ordered as daemon says\n\t\tself._controller_by_id = {}\t\t# Source of memory leak\n\t\n\t\n\tdef get_controllers(self):\n\t\t\"\"\"\n\t\tReturns list of all controllers connected to daemon.\n\t\tValue is cached locally.\n\t\t\"\"\"\n\t\treturn [] + self._controllers\n\t\n\t\n\tdef get_controller(self, controller_id, type=None):\n\t\t\"\"\"\n\t\tReturns ControllerManager instance bound to provided controller_id.\n\t\tNote that this method will return instance for any controller_id,\n\t\teven if controller with such ID is not connected to daemon.\n\t\t\n\t\tFor same controller_id, there is always same instance returned.\n\t\t\"\"\"\n\t\tif controller_id not in self._controller_by_id:\n\t\t\tc = self._controller_by_id[controller_id] = ControllerManager(self, controller_id, type)\n\t\telse:\n\t\t\tc = self._controller_by_id[controller_id]\n\t\t\tc.set_type(type)\n\t\treturn c\n\t\n\t\n\tdef has_controller(self):\n\t\t\"\"\"\n\t\tReturns True if there is at lease one controller connected to daemon.\n\t\t\"\"\"\n\t\treturn len(self._controllers) > 0\n\t\n\t\n\tdef _connect(self):\n\t\tif self.connecting : return\n\t\tself.connecting = True\n\t\tsc = Gio.SocketClient()\n\t\taddress = Gio.UnixSocketAddress.new(get_daemon_socket())\n\t\tsc.connect_async(address, None, self._on_connected)\n\t\n\t\n\tdef _on_daemon_died(self, *a):\n\t\t\"\"\" Called from various places when daemon looks like dead \"\"\"\n\t\t# Log stuff\n\t\tif self.alive is True:\n\t\t\tlog.debug(\"Connection to daemon lost\")\n\t\tif self.alive is True or self.alive is None:\n\t\t\tself.alive = False\n\t\t\tself.emit(\"dead\")\n\t\tself.alive = False\n\t\t# Close connection, if any\n\t\tif self.connection is not None:\n\t\t\tself.connection.close()\n\t\t\tself.connection = None\n\t\t# Emit event\n\t\t# Try to reconnect\n\t\tGLib.timeout_add_seconds(self.RECONNECT_INTERVAL, self._connect)\n\t\n\t\n\tdef _on_connected(self, sc, results):\n\t\t\"\"\" Called when connection to daemon socket is initiated \"\"\"\n\t\tself.connecting = False\n\t\ttry:\n\t\t\tself.connection = sc.connect_finish(results)\n\t\t\tif self.connection == None:\n\t\t\t\traise Exception(\"Unknown error\")\n\t\texcept Exception as e:\n\t\t\tself._on_daemon_died()\n\t\t\treturn\n\t\tself.buffer = b\"\"\n\t\tself.connection.get_input_stream().read_bytes_async(102400,\n\t\t\t1, None, self._on_read_data)\n\t\n\t\n\tdef _on_read_data(self, sc, results):\n\t\t\"\"\" Called when daemon sends some data \"\"\"\n\t\ttry:\n\t\t\tresponse = sc.read_bytes_finish(results)\n\t\t\tif response == None:\n\t\t\t\traise Exception(\"No data recieved\")\n\t\texcept Exception as e:\n\t\t\t# Broken sonnection, daemon was probbaly terminated\n\t\t\tself._on_daemon_died()\n\t\t\treturn\n\t\tdata = response.get_data()\n\t\tif len(data) == 0:\n\t\t\t# Connection terminated\n\t\t\tself._on_daemon_died()\n\t\t\treturn\n\t\tself.buffer += data\n\t\twhile b\"\\n\" in self.buffer:\n\t\t\tline, self.buffer = self.buffer.split(b\"\\n\", 1)\n\t\t\tline = line.decode(\"utf-8\")\n\t\t\tif line.startswith(\"Version:\"):\n\t\t\t\tversion = line.split(\":\", 1)[-1].strip()\n\t\t\t\tlog.debug(\"Connected to daemon, version %s\", version)\n\t\t\t\tself.emit('version', version)\n\t\t\telif line.startswith(\"Ready.\"):\n\t\t\t\tlog.debug(\"Daemon is ready.\")\n\t\t\t\tself.alive = True\n\t\t\t\tself.emit('alive')\n\t\t\telif line.startswith(\"OK.\"):\n\t\t\t\tif len(self._requests) > 0:\n\t\t\t\t\tsuccess_cb, error_cb = self._requests[0]\n\t\t\t\t\tself._requests = self._requests[1:]\n\t\t\t\t\tsuccess_cb()\n\t\t\telif line.startswith(\"Fail:\"):\n\t\t\t\tif len(self._requests) > 0:\n\t\t\t\t\tsuccess_cb, error_cb = self._requests[0]\n\t\t\t\t\tself._requests = self._requests[1:]\n\t\t\t\t\terror_cb(line[5:].strip())\n\t\t\telif line.startswith(\"Controller:\"):\n\t\t\t\tcontroller_id, type, flags, config_file = line[11:].strip().split(\" \", 3)\n\t\t\t\tc = self.get_controller(controller_id, type)\n\t\t\t\tc._connected = True\n\t\t\t\tc._type = type\n\t\t\t\tc._flags = int(flags)\n\t\t\t\tc._config_file = None if config_file in (\"\", \"None\") else config_file\n\t\t\t\twhile c in self._controllers:\n\t\t\t\t\tself._controllers.remove(c)\n\t\t\t\tself._controllers.append(c)\n\t\t\telif line.startswith(\"Controller profile:\"):\n\t\t\t\tcontroller_id, profile = line[19:].strip().split(\" \", 1)\n\t\t\t\tc = self.get_controller(controller_id)\n\t\t\t\tc._profile = profile.strip()\n\t\t\t\tc.emit(\"profile-changed\", c._profile)\n\t\t\t\tlog.debug(\"Daemon reported profile change for %s: %s\", controller_id, c._profile)\n\t\t\telif line.startswith(\"Controller Count:\"):\n\t\t\t\tcount = int(line[17:])\n\t\t\t\tif count == 0:\n\t\t\t\t\told, self._controllers = self._controllers, []\n\t\t\t\telse:\n\t\t\t\t\told, self._controllers = self._controllers, self._controllers[-count:]\n\t\t\t\tself.emit('controller-count-changed', count)\n\t\t\t\tfor c in old:\n\t\t\t\t\tif c not in self._controllers:\n\t\t\t\t\t\tc.emit('lost')\n\t\t\telif line.startswith(\"Event:\"):\n\t\t\t\tdata = line[6:].strip().split(\" \")\n\t\t\t\tc = self.get_controller(data[0])\n\t\t\t\tc.emit('event', data[1], [ int(float(x)) for x in data[2:] ])\n\t\t\t\tself.emit('event', c, data[1], [ int(float(x)) for x in data[2:] ])\n\t\t\telif line.startswith(\"Error:\"):\n\t\t\t\terror = line.split(\":\", 1)[-1].strip()\n\t\t\t\tself.alive = True\n\t\t\t\tlog.debug(\"Daemon reported error '%s'\", error)\n\t\t\t\tself.emit('error', error)\n\t\t\telif line.startswith(\"Current profile:\"):\n\t\t\t\tself._profile = line.split(\":\", 1)[-1].strip()\n\t\t\t\tself.emit('profile-changed', self._profile)\n\t\t\telif line.startswith(\"Reconfigured.\"):\n\t\t\t\tself.emit('reconfigured')\n\t\t\telif line.startswith(\"PID:\") or line == \"SCCDaemon\":\n\t\t\t\t# ignore\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\tself.emit('unknown-msg', line)\n\t\t# Connection is held forever to detect when daemon exits\n\t\tif self.connection:\n\t\t\tself.connection.get_input_stream().read_bytes_async(102400,\n\t\t\t\t1, None, self._on_read_data)\n\t\n\t\n\tdef is_alive(self):\n\t\t\"\"\" Returns True if daemon is running \"\"\"\n\t\treturn self.alive\n\t\n\t\n\tdef request(self, message, success_cb, error_cb):\n\t\t\"\"\"\n\t\tCreates request and remembers callback for next 'Ok' or 'Fail' message.\n\t\t\"\"\"\n\t\tif self.alive and self.connection is not None:\n\t\t\ttmp = message if type(message) == bytes else bytes(message, \"utf-8\")\n\t\t\tself._requests.append(( success_cb, error_cb ))\n\t\t\t(self.connection.get_output_stream()\n\t\t\t\t.write_all(tmp + b'\\n', None))\n\t\telse:\n\t\t\t# Instant failure\n\t\t\terror_cb(\"Not connected.\")\n\t\n\t\n\t@classmethod\n\tdef nocallback(*a):\n\t\t\"\"\" Used when request doesn't needs callback \"\"\"\n\t\tpass\n\t\n\t\n\tdef set_profile(self, filename):\n\t\t\"\"\" Asks daemon to change 1st controller profile \"\"\"\n\t\tself.request(\"Controller.\\nProfile: %s\" % (filename,),\n\t\t\t\tDaemonManager.nocallback, DaemonManager.nocallback)\n\t\n\t\n\tdef reconfigure(self):\n\t\t\"\"\" Asks daemon reload configuration file \"\"\"\n\t\tself.request(\"Reconfigure.\", DaemonManager.nocallback,\n\t\t\t\tDaemonManager.nocallback)\n\t\n\t\n\tdef rescan(self):\n\t\t\"\"\" Asks daemon to rescan for new devices \"\"\"\n\t\tself.request(\"Rescan.\", DaemonManager.nocallback,\n\t\t\t\tDaemonManager.nocallback)\n\t\n\t\n\tdef stop(self):\n\t\t\"\"\" Stops the daemon \"\"\"\n\t\tGio.Subprocess.new([ find_binary('scc-daemon'), \"/dev/null\", \"stop\" ], Gio.SubprocessFlags.NONE)\n\t\tself.connecting = False\n\t\n\t\n\tdef start(self, mode=\"start\"):\n\t\t\"\"\"\n\t\tStarts the daemon and forces connection to be created immediately.\n\t\t\"\"\"\n\t\tif self.alive:\n\t\t\t# Just to clean up living connection\n\t\t\tself.alive = None\n\t\t\tself._on_daemon_died()\n\t\tGio.Subprocess.new([ find_binary('scc-daemon'), \"/dev/null\", mode ], Gio.SubprocessFlags.NONE)\n\t\tself._connect()\n\t\tGLib.timeout_add_seconds(10, self._check_connected)\n\t\n\t\n\tdef restart(self):\n\t\t\"\"\"\n\t\tRestarts the daemon and forces connection to be created immediately.\n\t\t\"\"\"\n\t\tself.start(mode=\"restart\")\n\t\n\t\n\tdef _check_connected(self):\n\t\tif not self.alive:\n\t\t\tlog.debug(\"Started daemon but connection is not ready\")\n\t\t\tself.emit('error', \"CANT_SUMMON_THE_DAEMON\")\n\t\treturn False\n\n\nclass ControllerManager(GObject.GObject):\n\t\"\"\"\n\tRepresents controller connected to daemon.\n\tReturned by DaemonManager.get_controller or DaemonManager.get_controllers.\n\t\n\tList of signals:\n\t\tevent (pad_stick_or_button, values)\n\t\t\tEmited when pad, stick or button is locked using lock() method\n\t\t\tand position or pressed state of that button is changed\n\t\t\n\t\tlost()\n\t\t\tEmited when controller is disconnected or turned off\n\t\t\n\t\tprofile-changed (profile)\n\t\t\tEmited after profile for controller is changed.\n\t\t\tProfile is filename of currently active profile\n\t\"\"\"\n\t\n\t__gsignals__ = {\n\t\t\t\"event\"\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,object)),\n\t\t\t\"lost\"\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t\"profile-changed\"\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t}\n\t\n\tDEFAULT_ICONS = [ \"A\", \"B\", \"X\", \"Y\", \"BACK\", \"C\", \"START\",\n\t\t\"LB\", \"RB\", \"LT\", \"RT\", \"STICK\", \"LPAD\", \"RPAD\", \"RGRIP\", \"LGRIP\", \"DOTS\" ]\n\t# ^^ those are icon names\n\t\n\tdef __init__(self, daemon_manager, controller_id, controller_type):\n\t\tGObject.GObject.__init__(self)\n\t\tself._dm = daemon_manager\n\t\tself._controller_id = controller_id\n\t\tself._type = controller_type\n\t\tself._config_file = None\n\t\tself._connected = False\n\t\tself._profile = None\n\t\tself._type = None\n\t\tself._flags = 0\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<ControllerManager for ID '%s'>\" % (self._controller_id,)\n\t\n\t\n\tdef _send_id(self):\n\t\t\"\"\"\n\t\tSends Controller: message to daemon, so next message goes to correct\n\t\tcontroller.\n\t\t\"\"\"\n\t\tself._dm.request(\"Controller: %s\" % (self._controller_id,),\n\t\t\t\tDaemonManager.nocallback, DaemonManager.nocallback)\n\t\n\t\n\tdef is_connected(self):\n\t\t\"\"\"\n\t\tReturns True, if controller is still connected to daemon.\n\t\tValue is cached locally.\n\t\t\"\"\"\n\t\treturn self._connected\n\t\n\t\n\tdef get_type(self):\n\t\t\"\"\"\n\t\tReturns string identifier of controller driver.\n\t\t\n\t\tValue is cached locally, but may be None before controller is connected.\n\t\t\"\"\"\n\t\treturn self._type\n\t\n\tdef set_type(self, type):\n\t\t\"\"\"\n\t\tSets type, if none is yet set.\n\t\t\"\"\"\n\t\tif self._type is None:\n\t\t\tself._type = type\n\t\n\tdef get_flags(self):\n\t\t\"\"\"\n\t\tReturns flags for this controller. See ControllerFlags enum for more info.\n\t\t\n\t\tValue is cached locally and returns 0 until controller is connected.\n\t\t\"\"\"\n\t\treturn self._flags\n\t\n\t\n\tdef get_id(self):\n\t\t\"\"\" Returns ID of this controller. Value is cached locally. \"\"\"\n\t\treturn self._controller_id\n\t\n\t\n\tdef get_gui_config_file(self):\n\t\t\"\"\"\n\t\tReturns file name of json file that GUI can use to load more data about\n\t\tcontroller (background image, button images, available buttons and\n\t\taxes, etc...) File name may be absolute path or just name of file in\n\t\t/usr/share/scc\n\t\t\n\t\tReturns None if there is no configuration file (GUI will use\n\t\tdefaults in such case)\n\t\t\"\"\"\n\t\treturn self._config_file\n\t\n\t\n\tdef load_gui_config(self, default_path):\n\t\t\"\"\"\n\t\tAs get_gui_config_file, but returns loaded and parsed config.\n\t\tReturns None if config cannot be loaded.\n\t\t\"\"\"\n\t\tfilename = self.get_gui_config_file()\n\t\tif filename:\n\t\t\tif \"/\" not in filename:\n\t\t\t\tfilename = os.path.join(default_path, filename)\n\t\t\ttry:\n\t\t\t\tdata = json.loads(open(filename, \"r\").read()) or None\n\t\t\t\treturn data\n\t\t\texcept Exception as e:\n\t\t\t\tlog.exception(e)\n\t\treturn None\n\t\n\t@staticmethod\n\tdef get_button_icon(config, button, prefer_bw=False):\n\t\t\"\"\"\n\t\tFor config returned by load_gui_config() and SCButton constant,\n\t\treturns icon filename assigned to that button in controller config or\n\t\tdefault if config is invalid or button unassigned.\n\t\t\"\"\"\n\t\treturn find_button_image(\n\t\t\tControllerManager.get_button_name(config, button),\n\t\t\tprefer_bw=prefer_bw\n\t\t)\n\t\n\t\n\t@staticmethod\n\tdef get_button_name(config, button):\n\t\t\"\"\"\n\t\tAs get_button_icon, but returns icon name instead of filename.\n\t\t\"\"\"\n\t\tname = nameof(button)\n\t\tindex = -1\n\t\ttry:\n\t\t\tif type(button) is str:\n\t\t\t\tbutton = SCButtons.__members__[button]\n\t\t\tindex = BUTTON_ORDER.index(button)\n\t\t\tname = ControllerManager.DEFAULT_ICONS[index]\n\t\t\tbtn_index = config['gui']['buttons'].index(name)\n\t\t\tif btn_index != -1:\n\t\t\t\tname = config['gui']['buttons'][btn_index]\n\t\texcept: pass\n\t\treturn name\n\t\n\t\n\tdef get_profile(self):\n\t\t\"\"\" Returns profile set for this controller. Value is cached locally. \"\"\"\n\t\treturn self._profile\n\t\n\t\n\tdef lock(self, success_cb, error_cb, *what_to_lock):\n\t\t\"\"\"\n\t\tLocks physical button, axis or pad. Events from locked sources are\n\t\tsent to this client and processed using 'event' singal, until\n\t\tunlock_all() is called.\n\t\t\n\t\tCalls success_cb() on success or error_cb(error) on failure.\n\t\t\"\"\"\n\t\twhat = \" \".join(what_to_lock)\n\t\tself._send_id()\n\t\tself._dm.request(\"Lock: %s\" % (what,), success_cb, error_cb)\n\t\n\t\n\tdef set_led_level(self, value):\n\t\t\"\"\"\n\t\tSets brightness of controller led.\n\t\t\"\"\"\n\t\tself._send_id()\n\t\tself._dm.request(\"Led: %s\" % (int(value),), DaemonManager.nocallback,\n\t\t\tDaemonManager.nocallback)\n\t\n\t\n\tdef set_profile(self, filename):\n\t\t\"\"\" Asks daemon to change this controller profile \"\"\"\n\t\tself._send_id()\n\t\tself._dm.request(\"Profile: %s\" % (filename,),\n\t\t\t\tDaemonManager.nocallback, DaemonManager.nocallback)\n\t\n\t\n\tdef turnoff(self):\n\t\t\"\"\" Asks daemon to turn off this controller \"\"\"\n\t\tself._send_id()\n\t\tself._dm.request(\"Turnoff.\",\n\t\t\t\tDaemonManager.nocallback, DaemonManager.nocallback)\n\t\n\t\n\tdef feedback(self, position, amplitude):\n\t\t\"\"\" Generates feedback effect on controller \"\"\"\n\t\tself._send_id()\n\t\tself._dm.request(\"Feedback: %s %s\" % (position, amplitude),\n\t\t\t\tDaemonManager.nocallback, DaemonManager.nocallback)\n\t\n\t\n\tdef observe(self, success_cb, error_cb, *what_to_lock):\n\t\t\"\"\"\n\t\tEnables observing on physical button, axis or pad.\n\t\tEvents from observed sources are sent to this client and processed\n\t\tusing 'event' singal, until unlock_all() is called.\n\t\t\n\t\tCalls success_cb() on success or error_cb(error) on failure.\n\t\t\"\"\"\n\t\twhat = \" \".join(what_to_lock)\n\t\tself._send_id()\n\t\tself._dm.request(\"Observe: %s\" % (what,), success_cb, error_cb)\n\t\n\t\n\tdef replace(self, success_cb, error_cb, what, action):\n\t\t\"\"\"\n\t\tTemporally replaces action on physical button, axis or pad,\n\t\tuntil unlock_all() is called.\n\t\t\n\t\tCalls success_cb() on success or error_cb(error) on failure.\n\t\t\"\"\"\n\t\tactionstr = action.to_string().replace(\"\\n\", \" \")\n\t\tself._dm.request(\"Replace: %s %s\" % (what, actionstr),\n\t\t\t\tsuccess_cb, error_cb)\n\t\n\t\n\tdef unlock_all(self):\n\t\tif self._dm.alive:\n\t\t\tself._send_id()\n\t\t\tself._dm.request(\"Unlock.\", lambda *a: False, lambda *a: False)\n"
  },
  {
    "path": "scc/gui/dwsnc.py",
    "content": "\"\"\"\nDWSNC - Doing Weird Things in Name of Compatibility\n\nThis module, when imported, applies various fixes and monkey-patching to allow\napplication to run with older versions of GLib and/or GTK.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom gi.repository import Gtk, GObject\nimport os\n\n\ndef fix_label_missing_set_XYalign_methods():\n\t\"\"\"\n\tFix Gtk.Label missing set_xalign and set_yalign methods with older\n\tversions of Gtk.\n\t\n\tPrevents crashing, but alings are ignored.\n\t\"\"\"\n\tGtk.Label.set_xalign = Gtk.Label.set_yalign = lambda *a : None\n\ndef child_get_property(parent, child, propname):\n\t\"\"\"\n\tWrapper for child_get_property, which pygobject doesn't properly\n\tintrospect\n\t\"\"\"\n\tvalue = GObject.Value()\n\tvalue.init(GObject.TYPE_INT)\n\tparent.child_get_property(child, propname, value)\n\treturn value.get_int()\n\n\ndef headerbar(bar):\n\t\"\"\"\n\tMoves all buttons from left to right (and vice versa) if user's desktop\n\tenvironment is identified as Unity.\n\t\n\tRemoves 'icon' button otherwise\n\t\"\"\"\n\tbar.set_decoration_layout(\":minimize,close\")\n\tpass\t# Not outside of Unity\n\nIS_UNITY = False\nIS_GNOME = False\nIS_KDE = False\n\nif \"XDG_CURRENT_DESKTOP\" in os.environ:\n\tif \"GNOME\" in os.environ[\"XDG_CURRENT_DESKTOP\"].split(\":\"):\n\t\tIS_GNOME = True\n\t\n\tif \"KDE\" in os.environ[\"XDG_CURRENT_DESKTOP\"].split(\":\"):\n\t\tIS_KDE = True\n\t\n\tif \"Unity\" in os.environ[\"XDG_CURRENT_DESKTOP\"].split(\":\"):\n\t\t# User runs Unity\n\t\tIS_UNITY = True\n\t\t\n\t\tdef _headerbar(bar):\n\t\t\tchildren = [] + bar.get_children()\n\t\t\tpack_start = []\n\t\t\tpack_end = []\n\t\t\tfor c in children:\n\t\t\t\tif child_get_property(bar, c, 'pack-type') == Gtk.PackType.END:\n\t\t\t\t\tbar.remove(c)\n\t\t\t\t\tpack_start.append(c)\n\t\t\t\telse:\n\t\t\t\t\tbar.remove(c)\n\t\t\t\t\tpack_end.append(c)\n\t\t\tif len(pack_end) > 1:\n\t\t\t\tc,  pack_end = pack_end[0], pack_end[1:]\n\t\t\t\tpack_end.append(c)\n\t\t\tif (Gtk.get_major_version(), Gtk.get_minor_version()) > (3, 10):\n\t\t\t\t# Old ubuntu has this in order, new Ubuntu has it reversed\n\t\t\t\tpack_end = reversed(pack_end)\n\t\t\tfor c in pack_start: bar.pack_start(c)\n\t\t\tfor c in pack_end: bar.pack_end(c)\n\t\theaderbar = _headerbar\n\nif not hasattr(Gtk.Label, \"set_xalign\"):\n\t# GTK is old enough\n\tfix_label_missing_set_XYalign_methods()\n\n"
  },
  {
    "path": "scc/gui/editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor\n\nAllows to edit button or trigger action.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.uinput import Keys\nfrom scc.actions import MultiAction, HatLeftAction, HatRightAction\nfrom scc.actions import ButtonAction, AxisAction, MouseAction\nfrom scc.actions import HatUpAction, HatDownAction\nfrom scc.gui.svg_widget import SVGWidget\nfrom scc.gui.gdk_to_key import keyevent_to_key\nfrom scc.gui.area_to_action import AREA_TO_ACTION\n\nimport os, logging\nlog = logging.getLogger(\"Editor\")\n\n\nclass ComboSetter(object):\n\t\n\tdef set_cb(self, cb, key, keyindex=0):\n\t\t\"\"\"\n\t\tSets combobox value.\n\t\tReturns True on success or False if key is not found.\n\t\t\"\"\"\n\t\tmodel = cb.get_model()\n\t\tself._recursing = True\n\t\tfor row in model:\n\t\t\tif key == row[keyindex]:\n\t\t\t\tcb.set_active_iter(row.iter)\n\t\t\t\tself._recursing = False\n\t\t\t\treturn True\n\t\telse:\n\t\t\tlog.warning(\"Failed to set combobox value, key '%s' not found\", key)\n\t\tself._recursing = False\n\t\treturn False\n\n\nclass Editor(ComboSetter):\n\t\"\"\" Common stuff for all editor windows \"\"\"\n\tERROR_CSS = \" #error {background-color:green; color:red;} \"\n\t_error_css_provider = None\n\t\n\tdef __init__(self):\n\t\tself.added_widget = None\t\t# See add_widget method\n\t\n\t\n\tdef on_window_key_press_event(self, trash, event):\n\t\t\"\"\" Checks if pressed key was escape and if yes, closes window \"\"\"\n\t\tif event.keyval == Gdk.KEY_Escape:\n\t\t\tself.close()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tself.builder = Gtk.Builder()\n\t\tself.builder.add_from_file(os.path.join(self.app.gladepath, self.GLADE))\n\t\tself.window = self.builder.get_object(\"Dialog\")\n\t\tself.builder.connect_signals(self)\n\t\n\t\n\t@staticmethod\n\tdef install_error_css():\n\t\tif Editor._error_css_provider is None:\n\t\t\tEditor._error_css_provider = Gtk.CssProvider()\n\t\t\tEditor._error_css_provider.load_from_data(Editor.ERROR_CSS.encode('utf-8'))\n\t\t\tGtk.StyleContext.add_provider_for_screen(\n\t\t\t\t\tGdk.Screen.get_default(),\n\t\t\t\t\tEditor._error_css_provider,\n\t\t\t\t\tGtk.STYLE_PROVIDER_PRIORITY_USER)\n\t\n\t\n\tdef hide_dont_destroy(self, w, *a):\n\t\t\"\"\"\n\t\tWhen used as handler for 'delete-event' signal, prevents window from\n\t\tbeing destroyed after closing.\n\t\t\"\"\"\n\t\tw.hide()\n\t\treturn True\n\t\n\t\n\tdef set_title(self, title):\n\t\tself.window.set_title(title)\n\t\tself.builder.get_object(\"header\").set_title(title)\n\t\n\t\n\tdef close(self, *a):\n\t\tself.window.destroy()\n\t\n\t\n\tdef get_transient_for(self):\n\t\t\"\"\"\n\t\tReturns parent window for this editor. Usually main application window\n\t\t\"\"\"\n\t\treturn self._transient_for\n\t\n\t\n\tdef show(self, transient_for):\n\t\tif transient_for:\n\t\t\tself._transient_for = transient_for\n\t\t\tself.window.set_transient_for(transient_for)\n\t\t\tself.window.set_modal(True)\n\t\tself.window.show()\n\t\n\t\n\tdef add_widget(self, label, widget):\n\t\t\"\"\"\n\t\tAdds new widget into row before Action Name.\n\t\t\n\t\tWidget is automatically passed to Macro Editor or Modeshift Editor\n\t\tif either one is opened from editor window.\n\t\t\n\t\tWhen editor window is closed or destroyed, widget is automatically\n\t\tdeattached to keep it from destroying.\n\t\t\"\"\"\n\t\tlblAddedWidget = self.builder.get_object(\"lblAddedWidget\")\n\t\tvbAddedWidget = self.builder.get_object(\"vbAddedWidget\")\n\t\tlblAddedWidget.set_label(label)\n\t\tlblAddedWidget.set_visible(True)\n\t\tfor ch in vbAddedWidget.get_children():\n\t\t\tvbAddedWidget.remove(ch)\n\t\tself.added_widget = widget\n\t\tvbAddedWidget.pack_start(widget, True, False, 0)\n\t\tvbAddedWidget.set_visible(True)\n\t\n\t\n\tdef remove_added_widget(self):\n\t\t\"\"\"\n\t\tRemoves added widget, if any.\n\t\tShould be called from on_destory handlers.\n\t\t\"\"\"\n\t\tvbAddedWidget = self.builder.get_object(\"vbAddedWidget\")\n\t\tfor ch in vbAddedWidget.get_children():\n\t\t\tvbAddedWidget.remove(ch)\n\t\tself.added_widget = None\n\t\n\t\n\tdef send_added_widget(self, target):\n\t\t\"\"\" Transfers added widget to new editor window \"\"\"\n\t\tif self.added_widget:\n\t\t\tvbAddedWidget  = self.builder.get_object(\"vbAddedWidget\")\n\t\t\tlblAddedWidget = self.builder.get_object(\"lblAddedWidget\")\n\t\t\tlabel = lblAddedWidget.get_label()\n\t\t\tw = self.added_widget\n\t\t\tself.remove_added_widget()\n\t\t\ttarget.add_widget(label, w)\n"
  },
  {
    "path": "scc/gui/gdk_to_key.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - GDK_TO_KEY\n\nMaps Gdk.KEY_* constants into Keys.KEY_* constants.\nUsed by ActionEditor (when grabbing the key)\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom gi.repository import Gdk\nfrom scc.uinput import Keys\n\nGDK_TO_KEY = {\n\t# Row 1\n\tGdk.KEY_Escape\t\t: Keys.KEY_ESC,\n\tGdk.KEY_Print\t\t: Keys.KEY_PRINT,\n\tGdk.KEY_Scroll_Lock\t: Keys.KEY_SCROLLLOCK,\n\tGdk.KEY_Sys_Req\t\t: Keys.KEY_SYSRQ,\n\tGdk.KEY_Pause\t\t: Keys.KEY_PAUSE,\n\t\n\t# Row 2\n\tGdk.KEY_quoteleft\t: Keys.KEY_GRAVE,\t# tilde\n\tGdk.KEY_minus\t\t: Keys.KEY_MINUS,\n\tGdk.KEY_equal\t\t: Keys.KEY_EQUAL,\n\tGdk.KEY_BackSpace\t: Keys.KEY_BACKSPACE,\n\t\n\t# Row 3\n\tGdk.KEY_Tab\t\t\t: Keys.KEY_TAB,\n\tGdk.KEY_bracketleft\t: Keys.KEY_LEFTBRACE,\n\tGdk.KEY_bracketright: Keys.KEY_RIGHTBRACE,\n\tGdk.KEY_backslash\t: Keys.KEY_BACKSLASH,\n\t\n\t# Row 4\n\tGdk.KEY_Caps_Lock\t: Keys.KEY_CAPSLOCK,\n\tGdk.KEY_semicolon\t: Keys.KEY_SEMICOLON,\n\tGdk.KEY_apostrophe\t: Keys.KEY_APOSTROPHE,\n\tGdk.KEY_Return\t\t: Keys.KEY_ENTER,\n\t\n\t# Row 5\n\tGdk.KEY_Shift_L\t\t: Keys.KEY_LEFTSHIFT,\n\tGdk.KEY_comma\t\t: Keys.KEY_COMMA,\n\tGdk.KEY_period\t\t: Keys.KEY_DOT,\n\tGdk.KEY_slash\t\t: Keys.KEY_SLASH,\n\tGdk.KEY_Shift_R\t\t: Keys.KEY_RIGHTSHIFT,\n\t\n\t# Numpad\n\tGdk.KEY_KP_0\t\t: Keys.KEY_KP0,\n\tGdk.KEY_KP_1\t\t: Keys.KEY_KP1,\n\tGdk.KEY_KP_2\t\t: Keys.KEY_KP2,\n\tGdk.KEY_KP_3\t\t: Keys.KEY_KP3,\n\tGdk.KEY_KP_4\t\t: Keys.KEY_KP4,\n\tGdk.KEY_KP_5\t\t: Keys.KEY_KP5,\n\tGdk.KEY_KP_6\t\t: Keys.KEY_KP6,\n\tGdk.KEY_KP_7\t\t: Keys.KEY_KP7,\n\tGdk.KEY_KP_8\t\t: Keys.KEY_KP8,\n\tGdk.KEY_KP_9\t\t: Keys.KEY_KP9,\n\tGdk.KEY_KP_Delete\t: Keys.KEY_KPDOT,\n\tGdk.KEY_KP_Divide\t: Keys.KEY_KPSLASH,\n\tGdk.KEY_KP_Add\t\t: Keys.KEY_KPPLUS,\n\tGdk.KEY_KP_Multiply\t: Keys.KEY_KPASTERISK,\n\tGdk.KEY_KP_Subtract\t: Keys.KEY_KPMINUS,\n\tGdk.KEY_KP_Enter\t: Keys.KEY_KPENTER,\n\tGdk.KEY_Num_Lock\t: Keys.KEY_NUMLOCK,\n\t\n\t# Home & co\n\tGdk.KEY_Insert\t\t: Keys.KEY_INSERT,\n\tGdk.KEY_Home\t\t: Keys.KEY_HOME,\n\tGdk.KEY_Page_Up\t\t: Keys.KEY_PAGEUP,\n\tGdk.KEY_Delete\t\t: Keys.KEY_DELETE,\n\tGdk.KEY_End\t\t\t: Keys.KEY_END,\n\tGdk.KEY_Page_Down\t: Keys.KEY_PAGEDOWN,\n\t\n\t# Arrows\n\tGdk.KEY_Up\t\t\t: Keys.KEY_UP,\n\tGdk.KEY_Left\t\t: Keys.KEY_LEFT,\n\tGdk.KEY_Right\t\t: Keys.KEY_RIGHT,\n\tGdk.KEY_Down\t\t: Keys.KEY_DOWN,\n\t\n\t# Bottom row\n\tGdk.KEY_Control_L\t: Keys.KEY_LEFTCTRL,\n\tGdk.KEY_Super_L\t\t: Keys.KEY_LEFTMETA,\n\tGdk.KEY_Alt_L\t\t: Keys.KEY_LEFTALT,\n\tGdk.KEY_space\t\t: Keys.KEY_SPACE,\n\tGdk.KEY_Alt_R\t\t: Keys.KEY_RIGHTALT,\n\tGdk.KEY_Super_R\t\t: Keys.KEY_RIGHTMETA,\n\tGdk.KEY_Menu\t\t: Keys.KEY_COMPOSE,\n\tGdk.KEY_Control_R\t: Keys.KEY_RIGHTCTRL,\n}\n\n\nKEYCODE_TO_KEY = {\n\t# Row 1\n\t9\t: Keys.KEY_ESC,\n\t67\t: Keys.KEY_F1,\n\t68\t: Keys.KEY_F2,\n\t69\t: Keys.KEY_F3,\n\t70\t: Keys.KEY_F4,\n\t71\t: Keys.KEY_F5,\n\t72\t: Keys.KEY_F6,\n\t73\t: Keys.KEY_F7,\n\t74\t: Keys.KEY_F8,\n\t75\t: Keys.KEY_F9,\n\t76\t: Keys.KEY_F10,\n\t95\t: Keys.KEY_F11,\n\t96\t: Keys.KEY_F12,\n\t107\t: Keys.KEY_PRINT,\n\t78\t: Keys.KEY_SCROLLLOCK,\n\t127\t\t: Keys.KEY_PAUSE,\n\t\n\t# Row 2\n\t49\t: Keys.KEY_GRAVE,\t# tilde\n\t10\t: Keys.KEY_1,\n\t11\t: Keys.KEY_2,\n\t12\t: Keys.KEY_3,\n\t13\t: Keys.KEY_4,\n\t14\t: Keys.KEY_5,\n\t15\t: Keys.KEY_6,\n\t16\t: Keys.KEY_7,\n\t17\t: Keys.KEY_8,\n\t18\t: Keys.KEY_9,\n\t19\t: Keys.KEY_0,\n\t20\t: Keys.KEY_MINUS,\n\t21\t: Keys.KEY_EQUAL,\n\t22\t: Keys.KEY_BACKSPACE,\n\t\n\t# Row 3\n\t23\t: Keys.KEY_TAB,\n\t24\t: Keys.KEY_Q,\n\t25\t: Keys.KEY_W,\n\t26\t: Keys.KEY_E,\n\t27\t: Keys.KEY_R,\n\t28\t: Keys.KEY_T,\n\t29\t: Keys.KEY_Y,\n\t30\t: Keys.KEY_U,\n\t31\t: Keys.KEY_I,\n\t32\t: Keys.KEY_O,\n\t33\t: Keys.KEY_P,\n\t34\t: Keys.KEY_LEFTBRACE,\n\t35\t: Keys.KEY_RIGHTBRACE,\n\t51\t: Keys.KEY_BACKSLASH,\n\t\n\t# Row 4\n\t66\t: Keys.KEY_CAPSLOCK,\n\t38\t: Keys.KEY_A,\n\t39\t: Keys.KEY_S,\n\t40\t: Keys.KEY_D,\n\t41\t: Keys.KEY_F,\n\t42\t: Keys.KEY_G,\n\t43\t: Keys.KEY_H,\n\t44\t: Keys.KEY_J,\n\t45\t: Keys.KEY_K,\n\t46\t: Keys.KEY_L,\n\t47\t: Keys.KEY_SEMICOLON,\n\t48\t: Keys.KEY_APOSTROPHE,\n\t36\t: Keys.KEY_ENTER,\n\t\n\t# Row 5\n\t50\t: Keys.KEY_LEFTSHIFT,\n\t52\t: Keys.KEY_Z,\n\t53\t: Keys.KEY_X,\n\t54\t: Keys.KEY_C,\n\t55\t: Keys.KEY_V,\n\t56\t: Keys.KEY_B,\n\t57\t: Keys.KEY_N,\n\t58\t: Keys.KEY_M,\n\t59\t: Keys.KEY_COMMA,\n\t60\t: Keys.KEY_DOT,\n\t61\t: Keys.KEY_SLASH,\n\t62\t: Keys.KEY_RIGHTSHIFT,\n\t\n\t# Numpad\n\t90\t: Keys.KEY_KP0,\n\t87\t: Keys.KEY_KP1,\n\t88\t: Keys.KEY_KP2,\n\t89\t: Keys.KEY_KP3,\n\t83\t: Keys.KEY_KP4,\n\t84\t: Keys.KEY_KP5,\n\t85\t: Keys.KEY_KP6,\n\t79\t: Keys.KEY_KP7,\n\t80\t: Keys.KEY_KP8,\n\t81\t: Keys.KEY_KP9,\n\t91\t: Keys.KEY_KPDOT,\n\t106\t: Keys.KEY_KPSLASH,\n\t86\t: Keys.KEY_KPPLUS,\n\t63\t: Keys.KEY_KPASTERISK,\n\t82\t: Keys.KEY_KPMINUS,\n\t104\t: Keys.KEY_KPENTER,\n\t77\t: Keys.KEY_NUMLOCK,\n\t\n\t# Home & co\n\t118\t: Keys.KEY_INSERT,\n\t110\t: Keys.KEY_HOME,\n\t112\t: Keys.KEY_PAGEUP,\n\t119\t: Keys.KEY_DELETE,\n\t115\t: Keys.KEY_END,\n\t117\t: Keys.KEY_PAGEDOWN,\n\t\n\t# Arrows\n\t111\t: Keys.KEY_UP,\n\t113\t: Keys.KEY_LEFT,\n\t114\t: Keys.KEY_RIGHT,\n\t116\t: Keys.KEY_DOWN,\n\t\n\t# Bottom row\n\t37\t: Keys.KEY_LEFTCTRL,\n\t133\t: Keys.KEY_LEFTMETA,\n\t64\t: Keys.KEY_LEFTALT,\n\t65\t: Keys.KEY_SPACE,\n\t108\t: Keys.KEY_RIGHTALT,\n\t134\t: Keys.KEY_RIGHTMETA,\n\t135\t: Keys.KEY_COMPOSE,\n\t105\t: Keys.KEY_RIGHTCTRL,\n}\n\n# Stuff that is missing above is auto-generated here\nnames = { x.name : x for x in Keys }\n\nfor x in dir(Gdk):\n\tif x.startswith(\"KEY_\"):\n\t\tif x in names:\n\t\t\tGDK_TO_KEY[getattr(Gdk, x)] = names[x]\n\n# A-Z keys, because GDK has different codes for 'A' and 'a'\nfor x in range(ord('a'), ord('z')+1):\n\tGDK_TO_KEY[getattr(Gdk, \"KEY_\" + chr(x))] = names[\"KEY_\" + chr(x).upper()]\n\nKEY_TO_GDK = { GDK_TO_KEY[a] : a for a in GDK_TO_KEY }\nKEY_TO_KEYCODE = { KEYCODE_TO_KEY[a] : a for a in KEYCODE_TO_KEY }\n\ndef keyevent_to_key(event):\n\tif event.hardware_keycode in KEYCODE_TO_KEY:\n\t\treturn KEYCODE_TO_KEY[event.hardware_keycode]\n\treturn None\n"
  },
  {
    "path": "scc/gui/gestures.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Gesture-related GUI stuff.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, Gdk, GLib, GObject\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.osd import parse_rgba\nfrom collections import deque\n\nimport math, logging\nlog = logging.getLogger(\"Gestures\")\n\n\nclass GestureDraw(Gtk.DrawingArea):\n\tGRID_PAD = 10\n\tMAX_STEPS = 5\n\tLINE_ALPHA = 0.3;\n\tdef __init__(self, size, detector):\n\t\tGtk.DrawingArea.__init__(self)\n\t\tself._size = size\n\t\tself._detector = detector\n\t\tself._points = deque([], 256)\n\t\tself.connect('draw', self.draw)\n\t\tself.set_size_request(size, size)\n\t\tself.set_colors()\n\t\n\t\n\tdef set_colors(self, background=\"000000FF\", line=\"FF00FFFF\",\n\t\t\tgrid=\"7A7A7AFF\", hilight=\"0030AAFF\", **a):\n\t\t\"\"\" Expects colors in RRGGBB, as stored in config file \"\"\"\n\t\tself.colors = {\n\t\t\t'background' :\tparse_rgba(background),\n\t\t\t'line' : \t\tparse_rgba(line),\n\t\t\t'grid' : \t\tparse_rgba(grid),\n\t\t\t'hilight':\t\tparse_rgba(hilight),\n\t\t}\n\t\n\t\n\tdef add(self, x, y):\n\t\tfactor = self._size / float(STICK_PAD_MAX - STICK_PAD_MIN)\n\t\tx -= STICK_PAD_MIN\n\t\ty = STICK_PAD_MAX - y\n\t\tself._points.append(( x * factor, y * factor ))\n\t\tself.queue_draw()\n\t\n\t\n\tdef draw(self, another_self, cr):\n\t\tresolution = self._detector.get_resolution()\n\t\t# hilights = [ [0] * resolution for x in xrange(0, resolution) ]\n\n\t\t# Background\n\t\tGdk.cairo_set_source_rgba(cr, self.colors['background'])\n\t\tcr.rectangle(0, 0, self._size, self._size)\n\t\tcr.fill()\n\t\t\n\t\t# Hilighted boxes\n\t\t# Iterates over gesture in progress hilighting apripriate boxes,\n\t\t# so user can see what's he doing.\n\t\tbox_width = float(self._size) / float(resolution)\n\t\tcol = self.colors['hilight']\n\t\talpha = col.alpha\n\t\talpha_fallout = alpha * 0.5 / self.MAX_STEPS\n\t\tstep = 0\n\t\tfor x, y in reversed(self._detector.get_positions()):\n\t\t\tif step > self.MAX_STEPS:\n\t\t\t\tbreak\n\t\t\tcol.alpha = alpha - alpha_fallout * step\n\t\t\tGdk.cairo_set_source_rgba(cr, col)\n\t\t\tcr.rectangle(box_width * x, box_width * y, box_width, box_width)\n\t\t\tcr.fill()\n\t\t\tstep += 1\n\t\tcol.alpha = alpha\n\t\t\n\t\t# Grid\n\t\tGdk.cairo_set_source_rgba(cr, self.colors['grid'])\n\t\tfor i in range(1, resolution):\n\t\t\tcr.move_to(i * box_width, self.GRID_PAD)\n\t\t\tcr.line_to(i * box_width, self._size - self.GRID_PAD)\n\t\t\tcr.stroke()\n\t\t\tcr.move_to(self.GRID_PAD, i * box_width)\n\t\t\tcr.line_to(self._size - self.GRID_PAD, i * box_width)\n\t\t\tcr.stroke()\n\t\t\n\t\t# Line\n\t\tGdk.cairo_set_source_rgba(cr, self.colors['line'])\n\t\tdrawing = False\n\t\tfor x, y in self._points:\n\t\t\tif drawing:\n\t\t\t\tcr.line_to(x, y)\n\t\t\telse:\n\t\t\t\tcr.move_to(x, y)\n\t\t\t\tdrawing = True\n\t\tif drawing:\n\t\t\tcr.stroke()\n"
  },
  {
    "path": "scc/gui/global_settings.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Global Settings\n\nCurrently setups only one thing...\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GObject, GLib, GdkPixbuf\nfrom scc.menu_data import MenuData, MenuItem, Submenu, Separator, MenuGenerator\nfrom scc.paths import get_profiles_path, get_menus_path, get_config_path\nfrom scc.special_actions import TurnOffAction, RestartDaemonAction\nfrom scc.special_actions import ChangeProfileAction\nfrom scc.tools import find_profile, find_menu, find_binary\nfrom scc.modifiers import SensitivityModifier\nfrom scc.profile import Profile, Encoder\nfrom scc.actions import Action, NoAction\nfrom scc.constants import LEFT, RIGHT\nfrom scc.paths import get_share_path\nfrom scc.gui.osk_binding_editor import OSKBindingEditor\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.gui.editor import Editor, ComboSetter\nfrom scc.gui.parser import GuiActionParser\nfrom scc.gui.dwsnc import IS_UNITY\nfrom scc.x11.autoswitcher import AutoSwitcher, Condition\nfrom scc.osd.menu_generators import RecentListMenuGenerator\nfrom scc.osd.menu_generators import WindowListMenuGenerator\nfrom scc.osd.keyboard import Keyboard as OSDKeyboard\nfrom scc.osd.osk_actions import OSKCursorAction\nimport scc.osd.osk_actions\n\nimport re, sys, os, json, logging, traceback\nlog = logging.getLogger(\"GS\")\n\nclass GlobalSettings(Editor, UserDataManager, ComboSetter):\n\tGLADE = \"global_settings.glade\"\n\t\n\tDEFAULT_MENU_OPTIONS = [\n\t\t# label,\t\t\t\torder, class, icon, parameter\n\t\t('Recent profiles',\t\t0, RecentListMenuGenerator, None, 3),\n\t\t('Autoswitch Options',\t1, Submenu, 'system/autoswitch', '.autoswitch.menu'),\n\t\t('Switch To',\t\t\t1, Submenu, 'system/windowlist', '.windowlist.menu'),\n\t\t('Display Keyboard',\t2, MenuItem, 'system/keyboard', 'keyboard()'),\n\t\t('Turn Controller OFF', 2, MenuItem, 'system/turn-off', 'osd(turnoff())'),\n\t\t('Kill Current Window',\t1, MenuItem, 'weapons/pistol-gun',\n\t\t\t\"dialog('Really? Non-saved progress or data will be lost', \"\n\t\t\t\"name('Back', None), \"\n\t\t\t\"name('Kill', shell('kill -9 $(xdotool getwindowfocus getwindowpid)')))\"),\n\t\t('Run Program...',\t\t\t\t1, MenuItem, 'system/cog',\n\t\t\t'shell(\"scc-osd-launcher\")'),\n\t\t('Display Current Bindings...',\t1, MenuItem, 'system/binding-display',\n\t\t\t'shell(\"scc-osd-show-bindings\")'),\n\t\t('Games',\t\t\t\t1, Submenu, 'system/controller', '.games.menu'),\n\t\t('Edit Bindings',\t\t2, MenuItem, 'system/edit',\n\t\t\t'shell(\"sc-controller --osd\")'),\n\t\t# order: 0 - top, 1 - after 'options', 2 bottom\n\t]\n\t\n\tdef __init__(self, app):\n\t\tUserDataManager.__init__(self)\n\t\tself.app = app\n\t\tself.setup_widgets()\n\t\tself._timer = None\n\t\tself._recursing = False\n\t\tself._gamepad_icons = {\n\t\t\t'unknown': GdkPixbuf.Pixbuf.new_from_file(os.path.join(\n\t\t\t\t\tself.app.imagepath, \"controller-icons\", \"unknown.svg\"))\n\t\t}\n\t\tself.app.config.reload()\n\t\tAction.register_all(sys.modules['scc.osd.osk_actions'], prefix=\"OSK\")\n\t\tself.load_settings()\n\t\tself.load_profile_list()\n\t\tself._recursing = False\n\t\tself._eh_ids = (\n\t\t\tself.app.dm.connect('reconfigured', self.on_daemon_reconfigured),\n\t\t)\n\t\n\t\n\tdef _get_gamepad_icon(self, drv):\n\t\tif drv in self._gamepad_icons:\n\t\t\treturn self._gamepad_icons[drv]\n\t\ttry:\n\t\t\tp = GdkPixbuf.Pixbuf.new_from_file(os.path.join(\n\t\t\t\tself.app.imagepath, \"controller-icons\", drv + \"-4.svg\"))\n\t\texcept:\n\t\t\tlog.warning(\"Failed to load gamepad icon for driver '%s'\", drv)\n\t\t\tp = self._gamepad_icons[\"unknown\"]\n\t\tself._gamepad_icons[drv] = p\n\t\treturn p\n\t\n\t\n\tdef on_daemon_reconfigured(self, *a):\n\t\t# config is reloaded in main window 'reconfigured' handler.\n\t\t# Using GLib.idle_add here ensures that main window hanlder will run\n\t\t# *before* self.load_conditions\n\t\tGLib.idle_add(self.load_settings)\n\t\n\t\n\tdef on_Dialog_destroy(self, *a):\n\t\tfor x in self._eh_ids:\n\t\t\tself.app.dm.disconnect(x)\n\t\tself._eh_ids = ()\n\t\tAction.unregister_prefix('OSK')\n\t\n\t\n\tdef load_settings(self):\n\t\tself.load_autoswitch()\n\t\tself.load_osk()\n\t\tself.load_colors()\n\t\tself.load_cbMIs()\n\t\tself.load_drivers()\n\t\tself.load_controllers()\n\t\t# Load rest\n\t\tself._recursing = True\n\t\t(self.builder.get_object(\"cbInputTestMode\")\n\t\t\t\t.set_active(bool(self.app.config['enable_sniffing'])))\n\t\t(self.builder.get_object(\"cbEnableSerials\")\n\t\t\t\t.set_active(not bool(self.app.config['ignore_serials'])))\n\t\t(self.builder.get_object(\"cbEnableRumble\")\n\t\t\t\t.set_active(bool(self.app.config['output']['rumble'])))\n\t\t(self.builder.get_object(\"cbEnableStatusIcon\")\n\t\t\t\t.set_active(bool(self.app.config['gui']['enable_status_icon'])))\n\t\t(self.builder.get_object(\"cbMinimizeToStatusIcon\")\n\t\t\t\t.set_active(not IS_UNITY and bool(self.app.config['gui']['minimize_to_status_icon'])))\n\t\t(self.builder.get_object(\"cbMinimizeToStatusIcon\")\n\t\t\t\t.set_sensitive(not IS_UNITY and self.app.config['gui']['enable_status_icon']))\n\t\t(self.builder.get_object(\"cbMinimizeOnStart\")\n\t\t\t\t.set_active(not IS_UNITY and bool(self.app.config['gui']['minimize_on_start'])))\n\t\t(self.builder.get_object(\"cbMinimizeOnStart\")\n\t\t\t\t.set_sensitive(not IS_UNITY and self.app.config['gui']['enable_status_icon']))\n\t\t(self.builder.get_object(\"cbAutokillDaemon\")\n\t\t\t\t.set_active(self.app.config['gui']['autokill_daemon']))\n\t\t(self.builder.get_object(\"cbNewRelease\")\n\t\t\t\t.set_active(self.app.config['gui']['news']['enabled']))\n\t\tself._recursing = False\n\t\t\n\t\ttry:\n\t\t\timport evdev\n\t\texcept ImportError:\n\t\t\t# This block runs if evdev module is missing\n\t\t\tfor w in (\"cbEnableDriver_evdevdrv\", \"btAddController\"):\n\t\t\t\tself.builder.get_object(w).set_sensitive(False)\n\t\t\tself.builder.get_object(\"txEvdevMissing\").set_visible(True)\n\t\n\t\n\tdef load_drivers(self):\n\t\tfor key, value in self.app.config['drivers'].items():\n\t\t\tw = self.builder.get_object(\"cbEnableDriver_%s\" % (key, ))\n\t\t\tif w:\n\t\t\t\tw.set_active(value)\n\t\n\t\n\tdef _load_color(self, w, dct, key):\n\t\t\"\"\" Common part of load_colors \"\"\"\n\t\tif w:\n\t\t\tsuccess, color = Gdk.Color.parse(\"#%s\" % (self.app.config[dct][key],))\n\t\t\tif not success:\n\t\t\t\tsuccess, color = Gdk.Color.parse(\"#%s\" % (self.app.config[dct][key],))\n\t\t\tw.set_color(color)\n\t\n\t\n\tdef load_colors(self):\n\t\tcbOSDStyle = self.builder.get_object(\"cbOSDStyle\")\n\t\tcbOSDColorPreset = self.builder.get_object(\"cbOSDColorPreset\")\n\t\tfor k in self.app.config[\"osd_colors\"]:\n\t\t\tw = self.builder.get_object(\"cb%s\" % (k,))\n\t\t\tself._load_color(w, \"osd_colors\", k)\n\t\tfor k in self.app.config[\"osk_colors\"]:\n\t\t\tw = self.builder.get_object(\"cbosk_%s\" % (k,))\n\t\t\tself._load_color(w, \"osk_colors\", k)\n\t\ttheme = self.app.config.get(\"osd_color_theme\", \"None\")\n\t\tself.set_cb(cbOSDColorPreset, theme)\n\t\tself.set_cb(cbOSDStyle, self.app.config.get(\"osd_style\"))\n\t\n\t\n\tdef load_autoswitch(self):\n\t\t\"\"\" Transfers autoswitch settings from config to UI \"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tcbShowOSD = self.builder.get_object(\"cbShowOSD\")\n\t\tconditions = AutoSwitcher.parse_conditions(self.app.config)\n\t\tmodel = tvItems.get_model()\n\t\tmodel.clear()\n\t\tfor cond in conditions.keys():\n\t\t\to = GObject.GObject()\n\t\t\to.condition = cond\n\t\t\to.action = conditions[cond]\n\t\t\ta_str = o.action.describe(Action.AC_SWITCHER)\n\t\t\tmodel.append((o, o.condition.describe(), a_str))\n\t\tself._recursing = True\n\t\tself.on_tvItems_cursor_changed()\n\t\tcbShowOSD.set_active(bool(self.app.config['autoswitch_osd']))\n\t\tself._recursing = False\n\t\n\t\n\tdef load_osk(self):\n\t\tcbStickAction = self.builder.get_object(\"cbStickAction\")\n\t\tcbTriggersAction = self.builder.get_object(\"cbTriggersAction\")\n\t\tprofile = Profile(GuiActionParser())\n\t\tprofile.load(find_profile(OSDKeyboard.OSK_PROF_NAME))\n\t\tself._recursing = True\n\t\t\n\t\t# Load triggers\n\t\ttriggers = \"%s|%s\" % (\n\t\t\t\tprofile.triggers[LEFT].to_string(),\n\t\t\t\tprofile.triggers[RIGHT].to_string()\n\t\t)\n\t\tif not self.set_cb(cbTriggersAction, triggers, keyindex=1):\n\t\t\tself.add_custom(cbTriggersAction, triggers)\n\t\t\n\t\t# Load stick\n\t\tif not self.set_cb(cbStickAction, profile.stick.to_string(), keyindex=1):\n\t\t\tself.add_custom(cbStickAction, profile.stick.to_string())\n\t\t\n\t\t# Load sensitivity\n\t\ts = profile.pads[LEFT].compress().speed\n\t\tself.builder.get_object(\"sclSensX\").set_value(s[0])\n\t\tself.builder.get_object(\"sclSensY\").set_value(s[1])\n\t\t\n\t\tself._recursing = False\n\t\n\t\n\tdef add_custom(self, cb, key):\n\t\tfor k in cb.get_model():\n\t\t\tif k[2]:\n\t\t\t\tk[1] = key\n\t\t\t\tself.set_cb(cb, key, keyindex=1)\n\t\t\t\treturn\n\t\tcb.get_model().append(( _(\"(customized)\"), key, True ))\n\t\tself.set_cb(cb, key, keyindex=1)\n\t\n\t\n\tdef _load_osk_profile(self):\n\t\t\"\"\"\n\t\tLoads and returns on-screen keyboard profile.\n\t\tUsed by methods that are changing it.\n\t\t\"\"\"\n\t\tprofile = Profile(GuiActionParser())\n\t\tprofile.load(find_profile(OSDKeyboard.OSK_PROF_NAME))\n\t\treturn profile\n\t\n\t\n\tdef _save_osk_profile(self, profile):\n\t\t\"\"\"\n\t\tSaves on-screen keyboard profile and calls daemon.reconfigure()\n\t\tUsed by methods that are changing it.\n\t\t\"\"\"\n\t\tprofile.save(os.path.join(get_profiles_path(),\n\t\t\t\tOSDKeyboard.OSK_PROF_NAME + \".sccprofile\"))\n\t\tself.app.dm.reconfigure()\n\t\n\t\n\tdef on_cbStickAction_changed(self, cb):\n\t\tif self._recursing: return\n\t\tkey = cb.get_model().get_value(cb.get_active_iter(), 1)\n\t\tprofile = self._load_osk_profile()\n\t\tprofile.stick = GuiActionParser().restart(key).parse()\n\t\tself._save_osk_profile(profile)\n\t\n\t\n\tdef on_cbTriggersAction_changed(self, cb):\n\t\tif self._recursing: return\n\t\tkey = cb.get_model().get_value(cb.get_active_iter(), 1)\n\t\tl, r = key.split(\"|\")\n\t\tprofile = self._load_osk_profile()\n\t\tprofile.triggers[LEFT]  = GuiActionParser().restart(l).parse()\n\t\tprofile.triggers[RIGHT] = GuiActionParser().restart(r).parse()\n\t\tself._save_osk_profile(profile)\n\t\n\t\n\tdef on_osd_color_set(self, *a):\n\t\t\"\"\"\n\t\tCalled when user selects color.\n\t\t\"\"\"\n\t\t# Following lambdas converts Gdk.Color into #rrggbb notation.\n\t\t# Gdk.Color can do similar, except it uses #rrrrggggbbbb notation that\n\t\t# is not understood by Gdk css parser....\n\t\tcbOSDColorPreset = self.builder.get_object(\"cbOSDColorPreset\")\n\t\tstriphex = lambda a: hex(a).strip(\"0x\").zfill(2)\n\t\ttohex = lambda a: \"\".join([ striphex(int(x * 0xFF)) for x in a.to_floats() ])\n\t\tfor k in self.app.config[\"osd_colors\"]:\n\t\t\tw = self.builder.get_object(\"cb%s\" % (k,))\n\t\t\tif w:\n\t\t\t\tself.app.config[\"osd_colors\"][k] = tohex(w.get_color())\n\t\tfor k in self.app.config[\"osk_colors\"]:\n\t\t\tw = self.builder.get_object(\"cbosk_%s\" % (k,))\n\t\t\tif w:\n\t\t\t\tself.app.config[\"osk_colors\"][k] = tohex(w.get_color())\n\t\tself.app.config[\"osd_color_theme\"] = None\n\t\tself.set_cb(cbOSDColorPreset, \"None\")\n\t\tself.app.save_config()\n\t\n\t\n\tdef schedule_save_config(self):\n\t\t\"\"\"\n\t\tSchedules config saving in 3s.\n\t\tDone to prevent literal madness when user moves slider.\n\t\t\"\"\"\n\t\tdef cb(*a):\n\t\t\tself._timer = None\n\t\t\tself.app.save_config()\n\t\t\t\n\t\tif self._timer is not None:\n\t\t\tGLib.source_remove(self._timer)\n\t\tself._timer = GLib.timeout_add_seconds(3, cb)\n\t\n\t\n\tdef save_config(self):\n\t\t\"\"\" Transfers settings from UI back to config \"\"\"\n\t\t# Store hard stuff\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tcbShowOSD = self.builder.get_object(\"cbShowOSD\")\n\t\tcbEnableStatusIcon = self.builder.get_object(\"cbEnableStatusIcon\")\n\t\tcbMinimizeToStatusIcon = self.builder.get_object(\"cbMinimizeToStatusIcon\")\n\t\tconds = []\n\t\tfor row in tvItems.get_model():\n\t\t\tconds.append({\n\t\t\t\t'condition' : row[0].condition.encode(),\n\t\t\t\t'action' : row[0].action.to_string()\n\t\t\t})\n\t\t# Apply status icon settings\n\t\tif self.app.config['gui']['enable_status_icon'] != cbEnableStatusIcon.get_active():\n\t\t\tself.app.config['gui']['enable_status_icon'] = cbEnableStatusIcon.get_active()\n\t\t\tcbMinimizeToStatusIcon.set_sensitive(not IS_UNITY and cbEnableStatusIcon.get_active())\n\t\t\tif cbEnableStatusIcon.get_active():\n\t\t\t\tself.app.setup_statusicon()\n\t\t\telse:\n\t\t\t\tself.app.destroy_statusicon()\n\t\t# Store rest\n\t\tself.app.config['autoswitch'] = conds\n\t\tself.app.config['autoswitch_osd'] = cbShowOSD.get_active()\n\t\tself.app.config['enable_sniffing'] = self.builder.get_object(\"cbInputTestMode\").get_active()\n\t\tself.app.config['ignore_serials'] = not self.builder.get_object(\"cbEnableSerials\").get_active()\n\t\tself.app.config['output']['rumble'] = self.builder.get_object(\"cbEnableRumble\").get_active()\n\t\tself.app.config['gui']['enable_status_icon'] = self.builder.get_object(\"cbEnableStatusIcon\").get_active()\n\t\tself.app.config['gui']['minimize_to_status_icon'] = self.builder.get_object(\"cbMinimizeToStatusIcon\").get_active()\n\t\tself.app.config['gui']['minimize_on_start'] = self.builder.get_object(\"cbMinimizeOnStart\").get_active()\n\t\tself.app.config['gui']['autokill_daemon'] = self.builder.get_object(\"cbAutokillDaemon\").get_active()\n\t\tself.app.config['gui']['news']['enabled'] = self.builder.get_object(\"cbNewRelease\").get_active()\n\t\t\n\t\t# Save\n\t\tself.app.save_config()\n\t\n\t\n\tdef on_cbShowOSD_toggled(self, cb):\n\t\tif self._recursing: return\n\t\tself.save_config()\n\t\n\t\n\tdef on_btRestartEmulation_clicked(self, *a):\n\t\trvRestartWarning = self.builder.get_object(\"rvRestartWarning\")\n\t\tself.app.dm.stop()\n\t\trvRestartWarning.set_reveal_child(False)\n\t\tGLib.timeout_add_seconds(1, self.app.dm.start)\n\t\n\t\n\tdef on_restarting_checkbox_toggled(self, *a):\n\t\tif self._recursing: return\n\t\tself.on_random_checkbox_toggled()\n\t\tself._needs_restart()\n\t\n\t\n\tdef _needs_restart(self):\n\t\tif self.app.dm.is_alive():\n\t\t\trvRestartWarning = self.builder.get_object(\"rvRestartWarning\")\n\t\t\trvRestartWarning.set_reveal_child(True)\n\t\n\t\n\tDRIVER_DEPS = {\n\t\t'ds4drv' : ( \"evdevdrv\", \"hiddrv\" )\n\t}\n\t\n\tdef on_cbEnableDriver_toggled(self, cb):\n\t\tif self._recursing: return\n\t\tdrv = cb.get_name()\n\t\tself.app.config[\"drivers\"][drv] = cb.get_active()\n\t\tif cb.get_active() and drv in self.DRIVER_DEPS:\n\t\t\t# Driver has dependencies, make sure at least one of them is active\n\t\t\tone_active = any([ self.app.config[\"drivers\"].get(x)\n\t\t\t\t\t\t\t\t\tfor x in self.DRIVER_DEPS[drv] ])\n\t\t\tif not one_active:\n\t\t\t\t# Nothing is, make everything active just to be sure\n\t\t\t\tself._recursing = True\n\t\t\t\tfor x in self.DRIVER_DEPS[drv]:\n\t\t\t\t\tw = self.builder.get_object(\"cbEnableDriver_%s\" % (x, ))\n\t\t\t\t\tif w : w.set_active(True)\n\t\t\t\t\tself.app.config[\"drivers\"][x] = True\n\t\t\t\tself._recursing = False\n\t\t\n\t\tif not cb.get_active() and any([ drv in x for x in self.DRIVER_DEPS.values() ]):\n\t\t\t# Something depends on this driver,\n\t\t\t# disable anything that has no dependent drivers active\n\t\t\tself._recursing = True\n\t\t\tfor x, deps in self.DRIVER_DEPS.items():\n\t\t\t\tw = self.builder.get_object(\"cbEnableDriver_%s\" % (x, ))\n\t\t\t\tone_active = any([ self.app.config[\"drivers\"].get(y)\n\t\t\t\t\t\t\t\t\t\tfor y in self.DRIVER_DEPS[x] ])\n\t\t\t\tif not one_active and w:\n\t\t\t\t\tw.set_active(False)\n\t\t\t\t\tself.app.config[\"drivers\"][x] = False\n\t\t\tself._recursing = False\n\t\t\n\t\tself.save_config()\n\t\tself._needs_restart()\n\t\n\t\n\tdef on_random_checkbox_toggled(self, *a):\n\t\tif self._recursing: return\n\t\tself.save_config()\n\t\n\t\n\tdef on_butEditKeyboardBindings_clicked(self, *a):\n\t\te = OSKBindingEditor(self.app)\n\t\te.show(self.window)\n\t\n\t\n\tdef btEdit_clicked_cb(self, *a):\n\t\t\"\"\" Handler for \"Edit condition\" button \"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tcbProfile = self.builder.get_object(\"cbProfile\")\n\t\tce = self.builder.get_object(\"ConditionEditor\")\n\t\tentTitle = self.builder.get_object(\"entTitle\")\n\t\tentClass = self.builder.get_object(\"entClass\")\n\t\tcbMatchTitle = self.builder.get_object(\"cbMatchTitle\")\n\t\tcbMatchClass = self.builder.get_object(\"cbMatchClass\")\n\t\tcbExactTitle = self.builder.get_object(\"cbExactTitle\")\n\t\tcbRegExp = self.builder.get_object(\"cbRegExp\")\n\t\trbProfile = self.builder.get_object(\"rbProfile\")\n\t\trbTurnOff = self.builder.get_object(\"rbTurnOff\")\n\t\trbRestart = self.builder.get_object(\"rbRestart\")\n\t\t# Grab data\n\t\tmodel, iter = tvItems.get_selection().get_selected()\n\t\to = model.get_value(iter, 0)\n\t\tprofile = model.get_value(iter, 2)\n\t\tcondition = o.condition\n\t\taction = o.action\n\t\t\n\t\t# Clear editor\n\t\tfor cb in (cbMatchTitle, cbMatchClass, cbExactTitle, cbRegExp):\n\t\t\tcb.set_active(False)\n\t\tfor ent in (entClass, entTitle):\n\t\t\tent.set_text(\"\")\n\t\t# Setup action\n\t\tif isinstance(o.action, ChangeProfileAction):\n\t\t\trbProfile.set_active(True)\n\t\t\tself.set_cb(cbProfile, o.action.profile)\n\t\telif isinstance(o.action, TurnOffAction):\n\t\t\trbTurnOff.set_active(True)\n\t\telif isinstance(o.action, RestartDaemonAction):\n\t\t\trbRestart.set_active(True)\n\t\t\n\t\t# Setup editor\n\t\tif condition.title:\n\t\t\tentTitle.set_text(condition.title or \"\")\n\t\t\tcbMatchTitle.set_active(True)\n\t\telif condition.exact_title:\n\t\t\tentTitle.set_text(condition.exact_title or \"\")\n\t\t\tcbExactTitle.set_active(True)\n\t\t\tcbMatchTitle.set_active(True)\n\t\telif condition.regexp:\n\t\t\ttry:\n\t\t\t\tentTitle.set_text(condition.regexp.pattern)\n\t\t\texcept:\n\t\t\t\tentTitle.set_text(\"\")\n\t\t\tcbRegExp.set_active(True)\n\t\t\tcbMatchTitle.set_active(True)\n\t\tif condition.wm_class:\n\t\t\tentClass.set_text(condition.wm_class or \"\")\n\t\t\tcbMatchClass.set_active(True)\n\t\t\n\t\t# Show editor\n\t\tce.show()\n\t\n\t\n\tdef on_btSave_clicked(self, *a):\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tcbProfile = self.builder.get_object(\"cbProfile\")\n\t\tentTitle = self.builder.get_object(\"entTitle\")\n\t\tentClass = self.builder.get_object(\"entClass\")\n\t\tcbMatchTitle = self.builder.get_object(\"cbMatchTitle\")\n\t\tcbMatchClass = self.builder.get_object(\"cbMatchClass\")\n\t\tcbExactTitle = self.builder.get_object(\"cbExactTitle\")\n\t\tcbRegExp = self.builder.get_object(\"cbRegExp\")\n\t\trbProfile = self.builder.get_object(\"rbProfile\")\n\t\trbTurnOff = self.builder.get_object(\"rbTurnOff\")\n\t\trbRestart = self.builder.get_object(\"rbRestart\")\n\t\tce = self.builder.get_object(\"ConditionEditor\")\n\t\t\n\t\t# Build condition\n\t\tdata = {}\n\t\tif cbMatchTitle.get_active() and entTitle.get_text():\n\t\t\tif cbExactTitle.get_active():\n\t\t\t\tdata['exact_title'] = entTitle.get_text()\n\t\t\telif cbRegExp.get_active():\n\t\t\t\tdata['regexp'] = entTitle.get_text()\n\t\t\telse:\n\t\t\t\tdata['title'] = entTitle.get_text()\n\t\tif cbMatchClass.get_active() and entClass.get_text():\n\t\t\tdata['wm_class'] = entClass.get_text()\n\t\tcondition = Condition(**data)\n\t\t\n\t\t# Grab selected action\n\t\tmodel, iter = cbProfile.get_model(), cbProfile.get_active_iter()\n\t\taction = NoAction()\n\t\tif rbProfile.get_active():\n\t\t\taction = ChangeProfileAction(model.get_value(iter, 0))\n\t\telif rbTurnOff.get_active():\n\t\t\taction = TurnOffAction()\n\t\telif rbRestart.get_active():\n\t\t\taction = RestartDaemonAction()\n\t\t\n\t\t# Grab & update current row\n\t\tmodel, iter = tvItems.get_selection().get_selected()\n\t\to = model.get_value(iter, 0)\n\t\to.condition = condition\n\t\to.action = action\n\t\tmodel.set_value(iter, 1, condition.describe())\n\t\tmodel.set_value(iter, 2, action.describe(Action.AC_SWITCHER))\n\t\tself.hide_dont_destroy(ce)\n\t\tself.save_config()\n\t\n\t\n\tdef on_btAdd_clicked(self, *a):\n\t\t\"\"\" Handler for \"Add Item\" button \"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tmodel = tvItems.get_model()\n\t\to = GObject.GObject()\n\t\to.condition = Condition()\n\t\to.action = NoAction()\n\t\titer = model.append((o, o.condition.describe(), \"None\"))\n\t\ttvItems.get_selection().select_iter(iter)\n\t\tself.on_tvItems_cursor_changed()\n\t\tself.btEdit_clicked_cb()\n\t\n\t\n\tdef on_btRemove_clicked(self, *a):\n\t\t\"\"\" Handler for \"Remove Condition\" button \"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tmodel, iter = tvItems.get_selection().get_selected()\n\t\tif iter is not None:\n\t\t\tmodel.remove(iter)\n\t\tself.save_config()\n\t\tself.on_tvItems_cursor_changed()\n\t\n\t\n\tdef on_tvItems_cursor_changed(self, *a):\n\t\t\"\"\"\n\t\tHandles moving cursor in Item List.\n\t\tBasically just sets Edit Item and Remove Item buttons sensitivity.\n\t\t\"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tbtEdit = self.builder.get_object(\"btEdit\")\n\t\tbtRemove = self.builder.get_object(\"btRemove\")\n\t\t\n\t\tmodel, iter = tvItems.get_selection().get_selected()\n\t\tbtRemove.set_sensitive(iter is not None)\n\t\tbtEdit.set_sensitive(iter is not None)\n\t\n\t\n\tdef on_profiles_loaded(self, profiles):\n\t\tcb = self.builder.get_object(\"cbProfile\")\n\t\tmodel = cb.get_model()\n\t\tmodel.clear()\n\t\tfor f in profiles:\n\t\t\tname = f.get_basename()\n\t\t\tif type(name) is not str:\n\t\t\t\ttry:\n\t\t\t\t\tname = name.decode(\"utf-8\")\n\t\t\t\texcept Exception:\n\t\t\t\t\tcontinue\n\t\t\tif name.endswith(\".mod\"):\n\t\t\t\tcontinue\n\t\t\tif name.endswith(\".sccprofile\"):\n\t\t\t\tname = name[0:-11]\n\t\t\tmodel.append((name, f, None))\n\t\t\n\t\tcb.set_active(0)\n\t\n\t\n\tdef on_ConditionEditor_key_press_event(self, w, event):\n\t\t\"\"\" Checks if pressed key was escape and if yes, closes window \"\"\"\n\t\tif event.keyval == Gdk.KEY_Escape:\n\t\t\tself.hide_dont_destroy(w)\n\t\n\t\n\tdef on_cbExactTitle_toggled(self, tg):\n\t\t# Ensure that 'Match Title' checkbox is checked and only one of\n\t\t# 'match exact title' and 'use regexp' is checked\n\t\tif tg.get_active():\n\t\t\tcbMatchTitle = self.builder.get_object(\"cbMatchTitle\")\n\t\t\tcbRegExp = self.builder.get_object(\"cbRegExp\")\n\t\t\tcbMatchTitle.set_active(True)\n\t\t\tcbRegExp.set_active(False)\n\t\n\t\n\tdef on_cbRegExp_toggled(self, tg):\n\t\t# Ensure that 'Match Title' checkbox is checked and only one of\n\t\t# 'match exact title' and 'use regexp' is checked\n\t\tif tg.get_active():\n\t\t\tcbMatchTitle = self.builder.get_object(\"cbMatchTitle\")\n\t\t\tcbExactTitle = self.builder.get_object(\"cbExactTitle\")\n\t\t\tcbMatchTitle.set_active(True)\n\t\t\tcbExactTitle.set_active(False)\n\t\n\t\n\tdef on_btClearSensX_clicked(self, *a):\n\t\tself.builder.get_object(\"sclSensX\").set_value(1.0)\n\t\n\t\n\tdef on_btClearSensY_clicked(self, *a):\n\t\tself.builder.get_object(\"sclSensY\").set_value(1.0)\n\t\n\t\n\tdef on_sens_value_changed(self, *a):\n\t\tif self._recursing : return\n\t\ts = (self.builder.get_object(\"sclSensX\").get_value(),\n\t\t\tself.builder.get_object(\"sclSensY\").get_value())\n\t\t\n\t\tprofile = self._load_osk_profile()\n\t\tif s == (1.0, 1.0):\n\t\t\tprofile.pads[LEFT]  = OSKCursorAction(LEFT)\n\t\t\tprofile.pads[RIGHT] = OSKCursorAction(RIGHT)\n\t\telse:\n\t\t\tprofile.pads[LEFT]  = SensitivityModifier(s[0], s[1], OSKCursorAction(LEFT))\n\t\t\tprofile.pads[RIGHT] = SensitivityModifier(s[0], s[1], OSKCursorAction(RIGHT))\n\t\tself._save_osk_profile(profile)\n\t\n\t\n\tdef on_entTitle_changed(self, ent):\n\t\tcbRegExp = self.builder.get_object(\"cbRegExp\")\n\t\tbtSave = self.builder.get_object(\"btSave\")\n\t\tcbMatchTitle = self.builder.get_object(\"cbMatchTitle\")\n\t\t# Ensure that 'Match Title' checkbox is checked if its entry gets text\n\t\tif ent.get_text():\n\t\t\tcbMatchTitle.set_active(True)\n\t\tif cbRegExp.get_active():\n\t\t\t# If regexp combobox is active, try to compile expression typed\n\t\t\t# in field and don't allow user to save unless expression is valid\n\t\t\ttry:\n\t\t\t\tre.compile(ent.get_text())\n\t\t\texcept Exception as e:\n\t\t\t\tlog.error(e)\n\t\t\t\tbtSave.set_sensitive(False)\n\t\t\t\treturn\n\t\tbtSave.set_sensitive(True)\n\t\n\t\n\tdef on_cbOSDColorPreset_changed(self, cb):\n\t\ttheme = cb.get_model().get_value(cb.get_active_iter(), 0)\n\t\tif theme in (None, \"None\"): return\n\t\tfilename = os.path.join(get_share_path(), \"osd-styles\", theme)\n\t\tdata = json.loads(open(filename, \"r\").read())\n\t\t\n\t\t# Transfer values from json to config\n\t\tfor grp in (\"osd_colors\", \"osk_colors\"):\n\t\t\tif grp in data:\n\t\t\t\tfor subkey in self.app.config[grp]:\n\t\t\t\t\tif subkey in data[grp]:\n\t\t\t\t\t\tself.app.config[grp][subkey] = data[grp][subkey]\n\t\t\n\t\t# Save\n\t\tself.app.config[\"osd_color_theme\"] = theme\n\t\tself.app.save_config()\n\t\n\t\n\tdef on_cbOSDStyle_changed(self, cb):\n\t\tcolor_keys = self.app.config['osk_colors'].keys() + self.app.config['osd_colors'].keys()\n\t\tosd_style = cb.get_model().get_value(cb.get_active_iter(), 0)\n\t\tcss_file = os.path.join(get_share_path(), \"osd-styles\", osd_style)\n\t\tfirst_line = open(css_file, \"r\").read().split(\"\\n\")[0]\n\t\tused_colors = None\t\t\t\t# None means \"all\"\n\t\tif \"Used colors:\" in first_line:\n\t\t\tused_colors = set(first_line.split(\":\", 1)[1].strip(\" */\").split(\" \"))\n\t\t\tif \"all\" in used_colors:\n\t\t\t\tused_colors = None\t\t# None means \"all\"\n\t\t\n\t\tfor key in color_keys:\n\t\t\tcb = self.builder.get_object(\"cb%s\" % (key, ))\n\t\t\tlbl = self.builder.get_object(\"lbl%s\" % (key, ))\n\t\t\tif cb:  cb.set_sensitive ((used_colors is None) or (key in used_colors))\n\t\t\tif lbl: lbl.set_sensitive((used_colors is None) or (key in used_colors))\n\t\tself.app.config[\"osd_style\"] = osd_style\n\t\tself.app.save_config()\n\t\n\t\n\t@staticmethod\n\tdef _make_mi_instance(index):\n\t\t\"\"\" Helper method used by on_cbMI_toggled and load_cbMIs \"\"\"\n\t\t# label,\t\t\t\tclass, icon, *init_parameters\n\t\tlabel, order, cls, icon, parameter = GlobalSettings.DEFAULT_MENU_OPTIONS[index]\n\t\tif cls == MenuItem:\n\t\t\tinstance = MenuItem(\"item_i%s\" % (index,), label, parameter, icon=icon)\n\t\telif cls == Submenu:\n\t\t\tinstance = Submenu(parameter, label, icon=icon)\n\t\telse:\n\t\t\tinstance = cls(parameter)\n\t\t\tinstance.icon = icon\n\t\t\tinstance.label = label\n\t\treturn instance\n\t\n\t\n\tdef on_cbMI_toggled(self, widget):\n\t\t\"\"\"\n\t\tCalled when one of 'Default Menu Items' checkboxes is toggled.\n\t\tThis actually does kind of magic:\n\t\t- 1st, default menu file is loaded\n\t\t- 2nd, based on widget name, option from DEFAULT_MENU_OPTIONS is\n\t\t  selected\n\t\t- 3rd, if this option is not present in loaded menu and checkbox is\n\t\t  toggled on, option is added\n\t\t- (same for option that is present while checkbox was toggled off)\n\t\t- 4rd, default menu is saved\n\t\t\"\"\"\n\t\tif self._recursing: return\n\t\ttry:\n\t\t\tdata = MenuData.from_fileobj(\n\t\t\t\topen(find_menu(\"Default.menu\"), \"r\"),\n\t\t\t\tGuiActionParser())\n\t\t\tindex = int(widget.get_name().split(\"_\")[-1])\n\t\t\tinstance = GlobalSettings._make_mi_instance(index)\n\t\texcept Exception as e:\n\t\t\tlog.error(traceback.format_exc())\n\t\t\tself._recursing = True\n\t\t\twidget.set_active(not widget.get_active())\n\t\t\tself._recursing = False\n\t\t\treturn\n\t\t\n\t\tpresent = instance.describe().strip(\" >\") in [ x.describe().strip(\" >\") for x in data ]\n\t\tif bool(present) == bool(widget.get_active()):\n\t\t\t# User requested to add menu item that's already there\n\t\t\t# (or remove one that's not there)\n\t\t\treturn\n\t\t\n\t\titems = [ x for x in data ]\n\t\tif widget.get_active():\n\t\t\t# Add item to menu\n\t\t\torder = GlobalSettings.DEFAULT_MENU_OPTIONS[index][1]\n\t\t\tpos = 0\n\t\t\tif order == 1:\n\t\t\t\t# After last separator\n\t\t\t\tseparators = [ x for x in items[1:] if isinstance(x, Separator) ]\n\t\t\t\tif len(separators) > 0:\n\t\t\t\t\tpos = items.index(separators[-1]) + 1\n\t\t\telif order == 2:\n\t\t\t\t# At very end\n\t\t\t\tpos = len(items)\n\t\t\tif isinstance(instance, MenuGenerator):\n\t\t\t\titems.insert(pos, Separator(instance.label))\n\t\t\t\tpos += 1\n\t\t\titems.insert(pos, instance)\n\t\telse:\n\t\t\tif isinstance(instance, MenuGenerator):\n\t\t\t\titems = [ x for x in items\n\t\t\t\t\tif not (isinstance(x, Separator)\n\t\t\t\t\tand x.label == instance.label) ]\n\t\t\titems = [ x for x in items\n\t\t\t\tif instance.describe().strip(\" >\") != x.describe().strip(\" >\") ]\n\t\t\n\t\tpath = os.path.join(get_menus_path(), \"Default.menu\")\n\t\tdata = MenuData(*items)\n\t\tjstr = Encoder(sort_keys=True, indent=4).encode(data)\n\t\topen(path, \"w\").write(jstr)\n\t\tlog.debug(\"Wrote menu file %s\", path)\n\t\n\t\n\tdef load_cbMIs(self):\n\t\t\"\"\"\n\t\tSee above. This method just parses Default menu and checks\n\t\tboxes for present menu items.\n\t\t\"\"\"\n\t\ttry:\n\t\t\tdata = MenuData.from_fileobj(open(find_menu(\"Default.menu\"), \"r\"))\n\t\texcept Exception as e:\n\t\t\t# Shouldn't really happen\n\t\t\tlog.error(traceback.format_exc())\n\t\t\treturn\n\t\tself._recursing = True\n\t\t\n\t\tfor index in range(0, len(GlobalSettings.DEFAULT_MENU_OPTIONS)):\n\t\t\tid = \"cbMI_%s\" % (index,)\n\t\t\tinstance = GlobalSettings._make_mi_instance(index)\n\t\t\tpresent = ( instance.describe().strip(\" >\")\n\t\t\t\tin [ x.describe().strip(\" >\") for x in data ] )\n\t\t\tself.builder.get_object(id).set_active(present)\n\t\t\n\t\t# cbMI_5, 'Kill Current Window' is special case here. This checkbox\n\t\t# should be available only if xdotool utility is installed.\n\t\tcbMI_5 = self.builder.get_object(\"cbMI_5\")\n\t\tif find_binary(\"xdotool\") == \"xdotool\":\n\t\t\t# Not found\n\t\t\tcbMI_5.set_sensitive(False)\n\t\t\tcbMI_5.set_tooltip_text(_(\"Please, install xdotool package to use this feature\"))\n\t\telse:\n\t\t\tcbMI_5.set_sensitive(True)\n\t\t\tcbMI_5.set_tooltip_text(\"\")\n\t\n\t\n\tdef on_btAddController_clicked(self, *a):\n\t\tfrom scc.gui.creg.dialog import ControllerRegistration\n\t\tcr = ControllerRegistration(self.app)\n\t\tcr.window.connect(\"destroy\", self.load_controllers)\n\t\tcr.show(self.window)\n\t\n\t\n\tdef on_btRemoveController_clicked(self, *a):\n\t\ttvControllers = self.builder.get_object(\"tvControllers\")\n\t\td = Gtk.MessageDialog(parent=self.window,\n\t\t\tflags = Gtk.DialogFlags.MODAL,\n\t\t\ttype = Gtk.MessageType.WARNING,\n\t\t\tbuttons = Gtk.ButtonsType.YES_NO,\n\t\t\tmessage_format = _(\"Unregister controller?\"),\n\t\t)\n\t\td.format_secondary_text(_(\"You'll lose all settings for it\"))\n\t\tif d.run() == -8:\n\t\t\t# Yes\n\t\t\tmodel, iter = tvControllers.get_selection().get_selected()\n\t\t\tpath = model[iter][0]\n\t\t\ttry:\n\t\t\t\tos.unlink(path)\n\t\t\texcept Exception as e:\n\t\t\t\tlog.exception(e)\n\t\t\tself._needs_restart()\n\t\t\tself.load_controllers()\n\t\td.destroy()\n\t\n\t\n\tdef load_controllers(self, *a):\n\t\tlstControllers = self.builder.get_object(\"lstControllers\")\n\t\tlstControllers.clear()\n\t\tdevices_path = os.path.join(get_config_path(), \"devices\")\n\t\tif not os.path.exists(devices_path):\n\t\t\tos.makedirs(os.path.join(get_config_path(), \"devices\"))\n\t\tfor filename in os.listdir(os.path.join(get_config_path(), \"devices\")):\n\t\t\tif filename.endswith(\".json\"):\n\t\t\t\tif filename.startswith(\"hid-\"):\n\t\t\t\t\tdrv, usbid, name = filename.split(\"-\", 2)\n\t\t\t\t\tname = \"%s <i>(%s)</i>\" % (name[0:-5], usbid.upper())\n\t\t\t\telif \"-\" in filename:\n\t\t\t\t\tdrv, name = filename.split(\"-\", 1)\n\t\t\t\t\tname = name[0:-5]\n\t\t\t\telse:\n\t\t\t\t\tcontinue\n\t\t\t\tpath = os.path.join(get_config_path(), \"devices\", filename)\n\t\t\t\tlstControllers.append((path, name, self._get_gamepad_icon(drv)))\n"
  },
  {
    "path": "scc/gui/icon_chooser.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Icon Chooser\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, Gio, GdkPixbuf, GObject\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.gui.dwsnc import headerbar\nfrom scc.gui.editor import Editor\nfrom scc.paths import get_menuicons_path\nfrom scc.tools import find_icon\nimport os, traceback, logging, re\nlog = logging.getLogger(\"IconChooser\")\nRE_URL = re.compile(r\"(.*)(https?://[^ ]+)(.*)\")\nDEFAULT_ICON_CATEGORIES = ( \"items\", \"media\", \"weapons\", \"system\" )\n\nclass IconChooser(Editor, UserDataManager):\n\tGLADE = \"icon_chooser.glade\"\n\n\tdef __init__(self, app, callback):\n\t\tUserDataManager.__init__(self)\n\t\tself.app = app\n\t\tself.callback = callback\n\t\tself.setup_widgets()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\tclIcon = self.builder.get_object(\"clIcon\")\n\t\tcrIconName = self.builder.get_object(\"crIconName\")\n\t\tbtUserFolder = self.builder.get_object(\"btUserFolder\")\n\t\tcr = CellRendererMenuIcon(32)\n\t\tclIcon.clear()\n\t\tclIcon.pack_start(cr, False)\n\t\tclIcon.pack_start(crIconName, True)\n\t\tclIcon.set_attributes(cr, icon=1, has_colors=2)\n\t\tclIcon.set_attributes(crIconName, text=0)\n\t\tbtUserFolder.set_label(\"Add icons...\")\n\t\tbtUserFolder.set_uri(\"file://%s\" % (get_menuicons_path(),))\n\t\t\n\t\theaderbar(self.builder.get_object(\"header\"))\n\t\tself.load_menu_icons()\n\t\n\t\n\tdef on_btUserFolder_activate_link(self, *a):\n\t\tfor c in DEFAULT_ICON_CATEGORIES:\n\t\t\ttry:\n\t\t\t\tos.makedirs(os.path.join(get_menuicons_path(), c))\n\t\t\texcept:\n\t\t\t\t# Dir. exists\n\t\t\t\tpass\n\t\n\t\n\tdef on_btOk_clicked(self, *a):\n\t\ticon = self.get_selected()\n\t\tself.window.destroy()\n\t\tif icon:\n\t\t\tself.callback(icon)\n\t\n\t\n\tdef get_selected(self):\n\t\t\"\"\"\n\t\tReturns 'category/name' of currently selected icon.\n\t\tReturns None if nothing is selected.\n\t\t\"\"\"\n\t\ttsCategories = self.builder.get_object(\"tsCategories\")\n\t\ttsIcons = self.builder.get_object(\"tsIcons\")\n\t\ttry:\n\t\t\tmodel, iter = tsCategories.get_selected()\n\t\t\tcategory = model.get_value(iter, 0)\n\t\t\tmodel, iter = tsIcons.get_selected()\n\t\t\ticon_name = model.get_value(iter, 0)\n\t\t\treturn \"%s/%s\" % (category, icon_name)\n\t\texcept TypeError:\n\t\t\t# This part may throw TypeError if either list has nothing selected.\n\t\t\treturn None\n\t\n\t\n\tdef on_entName_changed(self, *a): pass\n\t\n\t\n\tdef on_tvItems_cursor_changed(self, view):\n\t\tentName = self.builder.get_object(\"entName\")\n\t\tlblLicense = self.builder.get_object(\"lblLicense\")\n\t\trvLicense = self.builder.get_object(\"rvLicense\")\n\t\ticon = self.get_selected()\n\t\tif icon:\n\t\t\tentName.set_text(icon)\n\t\t\tfull_path, trash = find_icon(icon)\n\t\t\tif full_path:\n\t\t\t\tpath, name = os.path.split(full_path)\n\t\t\t\tlicense = IconChooser.find_license(path, name)\n\t\t\t\tif license and \"(CC 0)\" in license:\n\t\t\t\t\t# My own icons\n\t\t\t\t\tlicense = license.replace(\"(CC 0)\", \"\").strip(\" ,\")\n\t\t\telse:\n\t\t\t\tlicense = None\n\t\t\tif license:\n\t\t\t\tm = RE_URL.match(license)\n\t\t\t\tif m:\n\t\t\t\t\tlicense = \"%s<a href='%s'>%s</a>%s\" % (\n\t\t\t\t\t\tm.group(1), m.group(2), m.group(2), m.group(3))\n\t\t\t\tlblLicense.set_markup(_(\"Free-use icon created by %s\" % (license,)))\n\t\t\trvLicense.set_reveal_child(bool(license))\n\t\n\t\n\tdef on_tvCategories_cursor_changed(self, view):\n\t\tmodel, iter = view.get_selection().get_selected()\n\t\tcategory = model.get_value(iter, 0)\n\t\tself.load_menu_icons(category=category)\n\t\n\t\n\t@staticmethod\n\tdef color_icon_exists(model, search_name):\n\t\tfor name, pb, has_colors in model:\n\t\t\tif has_colors and search_name == name:\n\t\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef on_menuicons_loaded(self, icons):\n\t\ttvIcons = self.builder.get_object(\"tvIcons\")\n\t\ttvCategories = self.builder.get_object(\"tvCategories\")\n\t\tmodel = tvIcons.get_model()\n\t\tmodel.clear()\n\t\tfor f in icons:\n\t\t\tname = f.get_basename()\n\t\t\tif f.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, None).get_file_type() == Gio.FileType.DIRECTORY:\n\t\t\t\t# ^^ woo, Gio is fun...\n\t\t\t\ttvCategories.get_model().append(( name, name.title() ))\n\t\t\telse:\n\t\t\t\thas_colors = True\n\t\t\t\tif name.startswith(\".\"):\n\t\t\t\t\t# Ignore hidden files\n\t\t\t\t\tcontinue\n\t\t\t\tname = name.split(\".\")\n\t\t\t\tif name[-1] not in (\"svg\", \"png\"):\n\t\t\t\t\t# Ignore non-supported files\n\t\t\t\t\tcontinue\n\t\t\t\tname = name[0:-1]\n\t\t\t\tif name[-1] == \"bw\":\n\t\t\t\t\thas_colors = False\n\t\t\t\t\tname = name[0:-1]\n\t\t\t\tname = \".\".join(name)\n\t\t\t\t\n\t\t\t\tif IconChooser.color_icon_exists(model, name):\n\t\t\t\t\tcontinue\n\t\t\t\t\n\t\t\t\tpb = None\n\t\t\t\ttry:\n\t\t\t\t\tpb = GdkPixbuf.Pixbuf.new_from_file(f.get_path())\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.error(e)\n\t\t\t\t\tlog.error(traceback.format_exc())\n\t\t\t\t\tcontinue\n\t\t\t\t\n\t\t\t\tmodel.append(( name, pb, has_colors ))\n\t\t\n\t\ttrash, selected = tvCategories.get_selection().get_selected()\n\t\tif not selected:\n\t\t\ttry:\n\t\t\t\t# Try to select 1st category, but ignore if that fails\n\t\t\t\ttvCategories.get_selection().select_path(( 0, ))\n\t\t\t\tself.on_tvCategories_cursor_changed(tvCategories)\n\t\t\texcept: pass\n\t\n\t\n\t@staticmethod\n\tdef find_license(path, name):\n\t\t\"\"\" Parses LICENSE file, if any, and returns license for give file \"\"\"\n\t\tlicensefile = os.path.join(path, \"LICENCES\")\n\t\tif not os.path.exists(licensefile):\n\t\t\treturn None\n\t\tfor line in open(licensefile, \"r\").readlines():\n\t\t\tif line.startswith(name):\n\t\t\t\tif \"-\" in line:\n\t\t\t\t\treturn line.split(\"-\")[-1].strip(\"\\t\\r\\n \")\n\t\treturn None\n\n\nclass CellRendererMenuIcon(Gtk.CellRenderer):\n\ticon = GObject.property(type=GdkPixbuf.Pixbuf)\n\thas_colors = GObject.property(type=bool, default=False)\n\t\n\tdef __init__(self, size):\n\t\tGtk.CellRenderer.__init__(self)\n\t\tself.size = size\n\t\n\tdef do_get_size(self, *a):\n\t\treturn 0, 0, self.size, self.size\n\t\n\t\n\tdef do_render(self, cr, treeview, background_area, cell_area, flags):\n\t\tcontext = Gtk.Widget.get_style_context(treeview)\n\t\tGtk.render_background(context, cr,\n\t\t\t\tcell_area.x, cell_area.y,\n\t\t\t\tcell_area.x + cell_area.width,\n\t\t\t\tcell_area.y + cell_area.height\n\t\t)\n\t\t\n\t\tscaled = self.icon.scale_simple(\n\t\t\t\tself.size, self.size,\n\t\t\t\tGdkPixbuf.InterpType.BILINEAR\n\t\t)\n\t\tsurf = Gdk.cairo_surface_create_from_pixbuf(scaled, 1)\n\t\tif self.has_colors:\n\t\t\tcr.set_source_surface(surf, cell_area.x, cell_area.y)\n\t\t\tcr.rectangle(cell_area.x, cell_area.y, self.size, self.size)\n\t\telse:\n\t\t\tcolor_flags = Gtk.StateFlags.NORMAL\n\t\t\tif (flags & Gtk.CellRendererState.SELECTED) != 0:\n\t\t\t\tcolor_flags = Gtk.StateFlags.SELECTED\n\t\t\tGdk.cairo_set_source_rgba(cr, context.get_color(color_flags))\n\t\t\tcr.mask_surface(surf, cell_area.x, cell_area.y)\n\t\tcr.fill()\n"
  },
  {
    "path": "scc/gui/importexport/__init__.py",
    "content": "#!/usr/bin/env python2\n"
  },
  {
    "path": "scc/gui/importexport/dialog.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Import / Export Dialog\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.gui.editor import Editor, ComboSetter\nfrom scc.tools import find_profile, profile_is_default, profile_is_override\nfrom .export import Export\nfrom .import_vdf import ImportVdf\nfrom .import_sccprofile import ImportSccprofile\n\nimport sys, os, tarfile, logging, json, traceback\nlog = logging.getLogger(\"IE.Dialog\")\n\nclass Dialog(Editor, ComboSetter, Export, ImportVdf, ImportSccprofile):\n\tGLADE = \"import_export.glade\"\n\t\n\tdef __init__(self, app):\n\t\tself.app = app\n\t\tself._back = []\n\t\tself._recursing = False\n\t\tself._next_callback = None\n\t\tself.setup_widgets()\n\t\tExport.__init__(self)\n\t\tImportVdf.__init__(self)\n\t\tImportSccprofile.__init__(self)\n\t\n\t\n\t@staticmethod\n\tdef determine_type(filename):\n\t\t\"\"\"\n\t\tDetects and returns type of passed file, if it can be imported.\n\t\tReturns one of 'sccprofile', 'sccprofile.tar.gz', 'vdf', 'vdffz'\n\t\tor None if type is not supported.\n\t\t\"\"\"\n\t\ttry:\n\t\t\tf = open(filename, 'rb').read(1024)\n\t\texcept Exception as e:\n\t\t\t# File not readable\n\t\t\tlog.error(traceback.format_exc())\n\t\t\treturn None\n\t\ttry:\n\t\t\tif f.decode(\"utf-8\").strip(\" \\t\\r\\n\").startswith(\"{\"):\n\t\t\t\t# Looks like json\n\t\t\t\tdata = json.loads(open(filename, \"r\").read())\n\t\t\t\tif \"buttons\" in data and \"gyro\" in data:\n\t\t\t\t\treturn 'sccprofile'\n\t\t\t\tif \"GameName\" in data and \"FileName\" in data:\n\t\t\t\t\treturn 'vdffz'\n\t\texcept:\n\t\t\t# Definitelly not json\n\t\t\tpass\n\t\t\n\t\tif f[0:2] == b\"\\x1f\\x8b\":\n\t\t\t# gzip, hopefully tar.gz\n\t\t\ttry:\n\t\t\t\ttar = tarfile.open(filename, \"r:gz\")\n\t\t\t\tnames = [ x.name for x in tar ]\n\t\t\t\tany_profile = any([ x.endswith(\".sccprofile\") for x in names ])\n\t\t\t\tif any_profile and \"profile-name\" in names:\n\t\t\t\t\treturn \"sccprofile.tar.gz\"\n\t\t\texcept:\n\t\t\t\t# Not a tarball\n\t\t\t\tpass\n\t\t\n\t\t# Rest is decided by extension\n\t\tif filename.endswith(\".sccprofile.tar.gz\"):\n\t\t\treturn \"sccprofile.tar.gz\"\n\t\tif filename.endswith(\".vdf\"):\n\t\t\treturn \"vdf\"\n\t\t# Fallbacks if above fails\n\t\tif filename.endswith(\".sccprofile\"):\n\t\t\treturn \"sccprofile\"\n\t\tif filename.endswith(\".vdffz\"):\n\t\t\treturn \"vdffz\"\n\t\treturn None\n\t\n\t\n\t@staticmethod\n\tdef check_name(name):\n\t\tif len(name.strip()) <= 0: return False\n\t\tif \"/\" in name: return False\n\t\tif find_profile(name):\n\t\t\t# Profile already exists\n\t\t\tif profile_is_default(name) and not profile_is_override(name):\n\t\t\t\t# Existing profile is default and has no override yet\n\t\t\t\treturn True\n\t\t\treturn False\n\t\treturn True\n\t\n\t\n\tdef import_file(self, filename, filetype = None):\n\t\t\"\"\"\n\t\tAttempts to import passed file.\n\t\t\n\t\tSwitches to apropriate page automatically, or, if file cannot be\n\t\timported, does nothing.\n\t\t\"\"\"\n\t\tfiletype = filetype or Dialog.determine_type(filename)\n\t\tif filetype == \"sccprofile\":\n\t\t\tself.import_scc(filename=filename)\n\t\telif filetype == \"sccprofile.tar.gz\":\n\t\t\tself.import_scc_tar(filename=filename)\n\t\telif filetype in (\"vdf\", \"vdffz\"):\n\t\t\tself.import_vdf(filename=filename)\n\t\n\t\n\tdef next_page(self, page):\n\t\tstDialog = self.builder.get_object(\"stDialog\")\n\t\tbtBack = self.builder.get_object(\"btBack\")\n\t\tself._back.append(stDialog.get_visible_child())\n\t\tstDialog.set_visible_child(page)\n\t\tbtBack.set_visible(True)\n\t\tself._page_selected(page)\n\t\n\t\n\tdef _page_selected(self, page):\n\t\tstDialog\t= self.builder.get_object(\"stDialog\")\n\t\thbDialog\t= self.builder.get_object(\"hbDialog\")\n\t\thbDialog.set_title(stDialog.child_get_property(page, \"title\"))\n\t\thname = \"on_%s_activated\" % (page.get_name(),)\n\t\tif hasattr(self, hname):\n\t\t\tgetattr(self, hname)()\n\t\n\t\n\tdef enable_next(self, enabled=True, callback=None):\n\t\t\"\"\"\n\t\tMakes 'Next' button visible and assigns callback that will be\n\t\tcalled when button is clicked. 'Next' button is automatically hidden\n\t\tbefore callback is called.\n\t\t\n\t\tReturns 'Next' button widget.\n\t\t\"\"\"\n\t\tassert not enabled or callback\n\t\tbtNext = self.builder.get_object(\"btNext\")\n\t\tbtNext.set_visible(enabled)\n\t\tbtNext.set_use_stock(False)\n\t\tbtNext.set_sensitive(True)\n\t\tbtNext.set_label(_(\"Next\"))\n\t\tself._next_callback = callback\n\t\treturn btNext\n\t\n\t\n\tdef on_btNext_clicked(self, *a):\n\t\tcb = self._next_callback\n\t\tself.enable_next(enabled=False)\n\t\tcb()\n\t\n\t\n\tdef on_btBack_clicked(self, *a):\n\t\tbtBack\t\t\t= self.builder.get_object(\"btBack\")\n\t\tstDialog\t\t= self.builder.get_object(\"stDialog\")\n\t\tbtSaveAs\t\t= self.builder.get_object(\"btSaveAs\")\n\t\tbtNext\t\t\t= self.builder.get_object(\"btNext\")\n\t\tpage, self._back = self._back[-1], self._back[:-1]\n\t\tstDialog.set_visible_child(page)\n\t\tbtNext.set_visible(False)\n\t\tbtSaveAs.set_visible(False)\n\t\tbtBack.set_visible(len(self._back) > 0)\n\t\tself._page_selected(page)\n\t\n\t\n\tdef on_btExport_clicked(self, *a):\n\t\tgrSelectProfile\t= self.builder.get_object(\"grSelectProfile\")\n\t\tself.next_page(grSelectProfile)\n\t\n\t\n\tdef on_btImportVdf_clicked(self, *a):\n\t\tgrVdfImport\t= self.builder.get_object(\"grVdfImport\")\n\t\tself.next_page(grVdfImport)\n"
  },
  {
    "path": "scc/gui/importexport/export.py",
    "content": "#!/usr/bin/env python2\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gio\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.tools import get_profiles_path, find_profile, find_menu\nfrom scc.special_actions import ChangeProfileAction, MenuAction\nfrom scc.tools import profile_is_default, menu_is_default\nfrom scc.parser import ActionParser, TalkingActionParser\nfrom scc.menu_data import MenuData, Submenu\nfrom scc.profile import Profile\n\nimport sys, os, json, tarfile, tempfile, logging\nlog = logging.getLogger(\"IE.Export\")\n\nclass Export(UserDataManager):\n\tTP_MENU = 0\n\tTP_PROFILE = 1\n\tPN_NAME = \"profile-name\"\n\n\tdef __init__(self):\n\t\tself.__profile_load_started = False\n\t\n\t\n\tdef on_grSelectProfile_activated(self, *a):\n\t\t# Not an event handler, called from page_selected\n\t\tif not self.__profile_load_started:\n\t\t\tself.__profile_load_started = True\n\t\t\tself.load_profile_list()\n\t\tself.on_tvProfiles_cursor_changed()\n\t\n\t\n\tdef on_profile_selected(self, *a):\n\t\tgrMakePackage\t= self.builder.get_object(\"grMakePackage\")\n\t\tbtSaveAs\t\t= self.builder.get_object(\"btSaveAs\")\t\t\n\t\tbtSaveAs.set_visible(True)\n\t\tself.next_page(grMakePackage)\n\n\t\n\tdef on_profiles_loaded(self, lst):\n\t\ttvProfiles = self.builder.get_object(\"tvProfiles\")\n\t\tmodel = tvProfiles.get_model()\n\t\tcurrent = self.app.get_current_profile()\n\t\ti, current_index = 0, -1\n\t\tfor f in sorted(lst, key=lambda f: f.get_basename()):\n\t\t\tname = f.get_basename()\n\t\t\tif name.endswith(\".mod\"):\n\t\t\t\tcontinue\n\t\t\tif name.startswith(\".\"):\n\t\t\t\tcontinue\n\t\t\tif name.endswith(\".sccprofile\"):\n\t\t\t\tname = name[0:-11]\n\t\t\tif name == current:\n\t\t\t\tcurrent_index = i\n\t\t\tmodel.append((i, f, name))\n\t\t\ti += 1\n\t\tif current_index >= 0:\n\t\t\ttvProfiles.set_cursor((current_index,))\n\t\n\t\n\tdef _add_refereced_profile(self, model, giofile, used):\n\t\t\"\"\"\n\t\tLoads profile file and recursively adds all profiles and menus\n\t\treferenced by it into 'package' list.\n\t\t\n\t\tReturns True on success or False if something cannot be parsed.\n\t\t\"\"\"\n\t\t# Load & parse selected profile and check every action in it\n\t\tprofile = Profile(ActionParser())\n\t\ttry:\n\t\t\tprofile.load(giofile.get_path())\n\t\texcept Exception as e:\n\t\t\t# Profile that cannot be parsed shouldn't be exported\n\t\t\tlog.error(e)\n\t\t\treturn False\n\t\t\n\t\tfor action in profile.get_all_actions():\n\t\t\tself._parse_action(model, action, used)\n\t\t\n\t\tfor menu in profile.menus:\n\t\t\tfor item in profile.menus[menu]:\n\t\t\t\tif isinstance(item, Submenu):\n\t\t\t\t\tself._add_refereced_menu(model, os.path.split(item.filename)[-1], used)\n\t\t\t\tif hasattr(item, \"action\"):\n\t\t\t\t\tself._parse_action(model, item.action, used)\n\t\treturn True\n\t\n\t\n\tdef _add_refereced_menu(self, model, menu_id, used):\n\t\t\"\"\"\n\t\tAs _add_refereced_profile, but reads and parses menu file.\n\t\t\"\"\"\n\t\tif \".\" in menu_id and menu_id not in used:\n\t\t\t# Dot in id means filename\n\t\t\tused.add(menu_id)\n\t\t\tfilename = find_menu(menu_id)\n\t\t\tname = \".\".join(menu_id.split(\".\")[0:-1])\n\t\t\tif name.startswith(\".\") and menu_is_default(menu_id):\n\t\t\t\t# Default and hidden, don't bother user with it\n\t\t\t\treturn\n\t\t\tif filename:\n\t\t\t\tmodel.append((not menu_is_default(menu_id), _(\"Menu\"), name,\n\t\t\t\t\t\tfilename, True, self.TP_MENU))\n\t\t\t\ttry:\n\t\t\t\t\tmenu = MenuData.from_file(filename, ActionParser())\n\t\t\t\texcept Exception as e:\n\t\t\t\t\t# Menu that cannot be parsed shouldn't be exported\n\t\t\t\t\tlog.error(e)\n\t\t\t\t\treturn\n\t\t\t\tfor item in menu:\n\t\t\t\t\tif isinstance(item, Submenu):\n\t\t\t\t\t\tself._add_refereced_menu(model, os.path.split(item.filename)[-1], used)\n\t\t\t\t\tif hasattr(item, \"action\"):\n\t\t\t\t\t\tself._parse_action(model, item.action, used)\n\t\t\telse:\n\t\t\t\tmodel.append((False, _(\"Menu\"), _(\"%s (not found)\") % (name,),\n\t\t\t\t\t\t\"\", False, self.TP_MENU))\n\t\n\t\n\tdef _parse_action(self, model, action, used):\n\t\t\"\"\"\n\t\tCommon part of _add_refereced_profile and _add_refereced_menu\n\t\t\"\"\"\n\t\tif isinstance(action, ChangeProfileAction):\n\t\t\tif action.profile not in used:\n\t\t\t\tfilename = find_profile(action.profile)\n\t\t\t\tused.add(action.profile)\n\t\t\t\tif filename:\n\t\t\t\t\tmodel.append((not profile_is_default(action.profile),\n\t\t\t\t\t\t_(\"Profile\"), action.profile, filename, True, self.TP_PROFILE))\n\t\t\t\t\tself._add_refereced_profile(model,\n\t\t\t\t\t\tGio.File.new_for_path(filename), used)\n\t\t\t\telse:\n\t\t\t\t\tmodel.append((False, _(\"Profile\"),\n\t\t\t\t\t\t_(\"%s (not found)\") % (action.profile,), \"\",\n\t\t\t\t\t\tFalse, self.TP_PROFILE))\n\t\telif isinstance(action, MenuAction):\n\t\t\tself._add_refereced_menu(model, action.menu_id, used)\n\t\n\t\n\tdef on_tvProfiles_cursor_changed(self, *a):\n\t\t\"\"\"\n\t\tCalled when user selects profile.\n\t\t\"\"\"\n\t\ttvProfiles\t= self.builder.get_object(\"tvProfiles\")\n\t\ttvPackage\t= self.builder.get_object(\"tvPackage\")\n\t\tbtSaveAs\t= self.builder.get_object(\"btSaveAs\")\n\t\tbtClose\t\t= self.builder.get_object(\"btClose\")\n\t\t\n\t\tpackage = tvPackage.get_model()\n\t\tpackage.clear()\n\t\tused = set()\n\t\t\n\t\tmodel, iter = tvProfiles.get_selection().get_selected()\n\t\tif iter:\n\t\t\tgiofile = model[iter][1]\n\t\t\ts = self._add_refereced_profile(package, giofile, used)\n\t\t\tif self._needs_package():\n\t\t\t\t# Profile references other menus or profiles\n\t\t\t\tself.enable_next(True, self.on_profile_selected)\n\t\t\t\tbtSaveAs.set_visible(False)\n\t\t\telse:\n\t\t\t\t# Profile can be exported directly\n\t\t\t\tself.enable_next(enabled=False)\n\t\t\t\tbtSaveAs.set_visible(True)\n\t\telse:\n\t\t\t# Nothing selected\n\t\t\t\tself.enable_next(enabled=False)\n\t\t\t\tbtSaveAs.set_visible(False)\n\t\n\t\n\tdef _needs_package(self):\n\t\t\"\"\"\n\t\tReturns True if there is any file checked on 2nd page,\n\t\tmeaning that profile has to be exported as archive.\n\t\t\"\"\"\n\t\ttvPackage = self.builder.get_object(\"tvPackage\")\n\t\tpackage = tvPackage.get_model()\n\t\treturn any([ row[0] for row in package ])\n\t\n\t\n\tdef on_btSelectAll_clicked(self, *a):\n\t\ttvPackage = self.builder.get_object(\"tvPackage\")\n\t\tpackage = tvPackage.get_model()\n\t\tfor row in package:\n\t\t\tif row[4]:\t# if enabled\n\t\t\t\trow[0] = True\t# then selected\n\t\n\t\n\tdef on_crPackageCheckbox_toggled(self, cr, path):\n\t\ttvPackage = self.builder.get_object(\"tvPackage\")\n\t\tpackage = tvPackage.get_model()\n\t\tpackage[path][0] = not package[path][0]\n\t\n\t\n\tdef on_btSaveAs_clicked(self, *a):\n\t\t# Grab stuff\n\t\ttvProfiles\t= self.builder.get_object(\"tvProfiles\")\n\t\tmodel, iter = tvProfiles.get_selection().get_selected()\n\t\t\n\t\t# Determine format\n\t\tf = Gtk.FileFilter()\n\t\tif self._needs_package():\n\t\t\tf.set_name(\"SC-Controller Profile Archive\")\n\t\t\tfmt = \"sccprofile.tar.gz\"\n\t\telse:\n\t\t\tf.set_name(\"SC-Controller Profile\")\n\t\t\tfmt = \"sccprofile\"\n\t\tf.add_pattern(\"*.%s\" % (fmt,))\n\t\t\n\t\t# Create dialog\n\t\td = Gtk.FileChooserNative.new(_(\"Export to File...\"),\n\t\t\t\tself.window, Gtk.FileChooserAction.SAVE)\n\t\td.add_filter(f)\n\t\td.set_do_overwrite_confirmation(True)\n\t\t# Set default filename\n\t\td.set_current_name(\"%s.%s\" % (model[iter][2], fmt))\n\t\tif d.run() == Gtk.ResponseType.ACCEPT:\n\t\t\tfn = d.get_filename()\n\t\t\tif len(os.path.split(fn)[-1].split(\".\")) < 2:\n\t\t\t\t# User wrote filename without extension\n\t\t\t\tfn = \"%s.%s\" % (fn, fmt)\n\t\t\t\n\t\t\tif self._needs_package():\n\t\t\t\tif self._export_package(model[iter][1], fn):\n\t\t\t\t\tself.window.destroy()\n\t\t\telse:\n\t\t\t\tif self._export(model[iter][1], fn):\n\t\t\t\t\tself.window.destroy()\n\t\n\t\n\tdef _export(self, giofile, target_filename):\n\t\t\"\"\"\n\t\tPerforms actual exporting.\n\t\tThis method is used when only profile with no referenced files\n\t\tis to be exported and works pretty simple - load, parse, save in new file.\n\t\t\"\"\"\n\t\tprofile = Profile(TalkingActionParser())\n\t\ttry:\n\t\t\tprofile.load(giofile.get_path())\n\t\texcept Exception as e:\n\t\t\t# Profile that cannot be parsed shouldn't be exported\n\t\t\tlog.error(e)\n\t\t\treturn False\n\t\t\n\t\tprofile.save(target_filename)\n\t\treturn True\n\t\n\t\n\tdef _export_package(self, giofile, target_filename):\n\t\t\"\"\"\n\t\tPerforms actual exporting.\n\t\tThis method is used when profile is to be exported _with_ some\n\t\treferenced files. It reads not only passed giofile, but all files\n\t\tmarked on 2nd page of export dialog.\n\t\t\n\t\tBoth profiles and menus are parsed before saving, but menu actions are\n\t\tnot parsed, so it is possible (but not very probable) to export\n\t\tinvalid menu file with this.\n\t\t\"\"\"\n\t\ttvPackage = self.builder.get_object(\"tvPackage\")\n\t\tpackage = tvPackage.get_model()\n\t\ttar = tarfile.open(target_filename, \"w:gz\")\n\t\t\n\t\tdef export_profile(tar, filename):\n\t\t\tprofile = Profile(TalkingActionParser())\n\t\t\ttry:\n\t\t\t\tout = tempfile.NamedTemporaryFile()\n\t\t\t\tprofile.load(filename)\n\t\t\t\tprofile.save(out.name)\n\t\t\t\ttar.add(out.name, arcname=os.path.split(filename)[-1], recursive=False)\n\t\t\texcept Exception as e:\n\t\t\t\t# Profile that cannot be parsed shouldn't be exported\n\t\t\t\tlog.error(e)\n\t\t\t\treturn False\n\t\t\treturn True\n\t\t\n\t\tdef export_menu(tar, filename):\n\t\t\ttry:\n\t\t\t\tmenu = MenuData.from_json_data(json.loads(open(filename, \"r\").read()), ActionParser())\n\t\t\t\ttar.add(filename, arcname=os.path.split(filename)[-1], recursive=False)\n\t\t\texcept Exception as e:\n\t\t\t\t# Menu that cannot be parsed shouldn't be exported\n\t\t\t\tlog.error(e)\n\t\t\t\treturn False\n\t\t\treturn True\n\t\t\n\t\t\n\t\tif not export_profile(tar, giofile.get_path()):\n\t\t\treturn False\n\t\t\n\t\tfor row in package:\n\t\t\tenabled, tp, filename = row[0], row[5], row[3]\n\t\t\tif enabled:\n\t\t\t\tif tp == self.TP_PROFILE:\n\t\t\t\t\tif not export_profile(tar, filename):\n\t\t\t\t\t\treturn False\n\t\t\t\telif tp == self.TP_MENU:\n\t\t\t\t\tif not export_menu(tar, filename):\n\t\t\t\t\t\treturn False\n\t\t\n\t\t# Store original profile name so import knows which profile is\n\t\t# \"important\" and which just tagged along as referenced by some action.\n\t\tout = tempfile.NamedTemporaryFile()\n\t\tout.write(\".\".join(giofile.get_basename().split(\".\")[0:-1]))\n\t\tout.flush()\n\t\ttar.add(out.name, arcname=Export.PN_NAME, recursive=False)\n\t\ttar.close()\n\t\treturn True\n"
  },
  {
    "path": "scc/gui/importexport/import_sccprofile.py",
    "content": "#!/usr/bin/env python2\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gio, GLib, GObject\nfrom scc.tools import get_profiles_path, get_menus_path, find_profile, find_menu\nfrom scc.special_actions import ChangeProfileAction, MenuAction\nfrom scc.special_actions import ShellCommandAction\nfrom scc.profile import Profile, Encoder\nfrom scc.menu_data import MenuData\nfrom scc.gui.parser import GuiActionParser\nfrom .export import Export\n\nimport sys, os, json, tarfile, tempfile, logging\nlog = logging.getLogger(\"IE.ImportSSCC\")\n\nclass ImportSccprofile(object):\n\t\n\tdef on_btImportSccprofile_clicked(self, *a):\n\t\t# Create filters\n\t\tf1 = Gtk.FileFilter()\n\t\tf1.set_name(\"SC-Controller Profile or Archive\")\n\t\tf1.add_pattern(\"*.sccprofile\")\n\t\tf1.add_pattern(\"*.sccprofile.tar.gz\")\n\t\t\n\t\t# Create dialog\n\t\td = Gtk.FileChooserNative.new(_(\"Import Profile...\"),\n\t\t\t\tself.window, Gtk.FileChooserAction.OPEN)\n\t\td.add_filter(f1)\n\t\tif d.run() == Gtk.ResponseType.ACCEPT:\n\t\t\tif d.get_filename().endswith(\".tar.gz\"):\n\t\t\t\tself.import_scc_tar(d.get_filename())\n\t\t\telse:\n\t\t\t\tself.import_scc(d.get_filename())\n\t\n\t\n\tdef error(self, text):\n\t\t\"\"\"\n\t\tDisplays error page (reused from VDF import).\n\t\t\"\"\"\n\t\ttbError =\t\t\tself.builder.get_object(\"tbError\")\n\t\tgrImportFailed =\tself.builder.get_object(\"grImportFailed\")\n\t\t\n\t\ttbError.set_text(text)\n\t\tself.next_page(grImportFailed)\n\t\n\t\n\tdef import_scc(self, filename):\n\t\t\"\"\"\n\t\tImports simple, single-file scc-profile.\n\t\tJust loads it, checks for shell() actions and asks user to enter name.\n\t\t\"\"\"\n\t\tfiles = self.builder.get_object(\"lstImportPackage\")\n\t\t# Load profile\n\t\tprofile = Profile(GuiActionParser())\n\t\ttry:\n\t\t\tprofile.load(filename)\n\t\texcept Exception as e:\n\t\t\t# Profile cannot be parsed. Display error message and let user to quit\n\t\t\t# Error message reuses page from VDF import, because they are\n\t\t\t# basically the same\n\t\t\tlog.error(e)\n\t\t\tself.error(str(e))\n\t\t\treturn\n\t\t\n\t\tname = \".\".join(os.path.split(filename)[-1].split(\".\")[0:-1])\n\t\tfiles.clear()\n\t\to = GObject.GObject()\n\t\to.obj = profile\n\t\tfiles.append(( 2, name, name, _(\"(profile)\"), o ))\n\t\t\n\t\tself.check_shell_commands()\n\t\n\t\n\tdef import_scc_tar(self, filename):\n\t\t\"\"\"\n\t\tImports packaged profiles.\n\t\tChecks for shell() actions everywhere and ask user to\n\t\tenter main name, check generated ones and optionaly change\n\t\tthem as he wish.\n\t\t\"\"\"\n\t\tfiles = self.builder.get_object(\"lstImportPackage\")\n\t\ttry:\n\t\t\t# Open tar\n\t\t\ttar = tarfile.open(filename, \"r:gz\")\n\t\t\tfiles.clear()\n\t\t\t# Grab 1st profile\n\t\t\tname = tar.extractfile(Export.PN_NAME).read()\n\t\t\tmain_profile = \"%s.sccprofile\" % name\n\t\t\tparser = GuiActionParser()\n\t\t\to = GObject.GObject()\n\t\t\to.obj = Profile(parser).load_fileobj(tar.extractfile(main_profile))\n\t\t\tfiles.append(( 2, name, name, _(\"(profile)\"), o ))\n\t\t\tfor x in tar:\n\t\t\t\tname = \".\".join(x.name.split(\".\")[0:-1])\n\t\t\t\tif x.name.endswith(\".sccprofile\") and x.name != main_profile:\n\t\t\t\t\to = GObject.GObject()\n\t\t\t\t\to.obj = Profile(parser).load_fileobj(tar.extractfile(x))\n\t\t\t\t\tfiles.append(( True, name, name, _(\"(profile)\"), o ))\n\t\t\t\telif x.name.endswith(\".menu\"):\n\t\t\t\t\to = GObject.GObject()\n\t\t\t\t\to.obj = MenuData.from_fileobj(tar.extractfile(x), parser)\n\t\t\t\t\tfiles.append(( True, name, name, _(\"(menu)\"), o ))\n\t\texcept Exception as e:\n\t\t\t# Either entire tar or some profile cannot be parsed.\n\t\t\t# Display error message and let user to quit\n\t\t\t# Error message reuses same page as above.\n\t\t\tlog.error(e)\n\t\t\tself.error(str(e))\n\t\t\treturn\n\t\tself.check_shell_commands()\n\t\n\t\n\tdef check_shell_commands(self):\n\t\t\"\"\"\n\t\tCheck for shell commands in profiles being imported.\n\t\tIf there are any shell commands found, displays warning page\n\t\tand lets user to confirm import of them.\n\t\t\n\t\tOthewise, goes straight to next page as if user already confirmed them.\n\t\t\"\"\"\n\t\tgrShellCommands =\tself.builder.get_object(\"grShellCommands\")\n\t\ttvShellCommands =\tself.builder.get_object(\"tvShellCommands\")\n\t\tfiles =\t\t\t\tself.builder.get_object(\"lstImportPackage\")\n\t\tmodel = tvShellCommands.get_model()\n\t\tmodel.clear()\n\t\t# Get all shell commands in all profiles\n\t\tfor trash, trash, trash, trash, obj in files:\n\t\t\tif isinstance(obj.obj, Profile):\n\t\t\t\tfor a in obj.obj.get_all_actions():\n\t\t\t\t\tif isinstance(a, ShellCommandAction):\n\t\t\t\t\t\tmodel.append((False, a.command))\n\t\t\n\t\tif len(model) > 0:\n\t\t\t# If there is shell command present, jump to warning page\n\t\t\tself.next_page(grShellCommands)\n\t\t\tbtNext = self.enable_next(True, self.shell_import_confirmed)\n\t\t\tbtNext.set_label(_(\"Continue\"))\n\t\t\tbtNext.set_sensitive(False)\n\t\telse:\n\t\t\t# Otherwise continue to next one\n\t\t\tself.shell_import_confirmed()\t\n\t\n\t\n\tdef on_crShellCommandChecked_toggled(self, cr, path):\n\t\ttvShellCommands =\tself.builder.get_object(\"tvShellCommands\")\n\t\tbtNext =\t\t\tself.builder.get_object(\"btNext\")\n\t\tmodel = tvShellCommands.get_model()\n\t\tmodel[path][0] = not model[path][0]\n\t\tbtNext.set_sensitive(True)\n\t\tfor row in model:\n\t\t\tif not row[0]:\n\t\t\t\tbtNext.set_sensitive(False)\n\t\t\t\treturn\n\t\n\t\n\tdef shell_import_confirmed(self):\n\t\tgrSccImportFinished =\tself.builder.get_object(\"grSccImportFinished\")\n\t\tlblSccImportFinished =\tself.builder.get_object(\"lblSccImportFinished\")\n\t\ttxName2 =\t\t\t\tself.builder.get_object(\"txName2\")\n\t\tfiles =\t\t\t\t\tself.builder.get_object(\"lstImportPackage\")\n\t\tvbImportPackage =\t\tself.builder.get_object(\"vbImportPackage\")\n\t\t\n\t\tenabled, trash, name, trash, obj = files[0]\n\t\tlblSccImportFinished.set_text(_(\"Profile sucessfully imported\"))\n\t\ttxName2.set_text(name)\n\t\tvbImportPackage.set_visible(len(files) > 1)\n\t\tself.next_page(grSccImportFinished)\n\t\tself.on_txName2_changed()\n\t\n\t\n\tdef on_txName2_changed(self, *a):\n\t\ttxName2 =\t\t\tself.builder.get_object(\"txName2\")\n\t\tbtNext =\t\t\tself.enable_next(True, self.on_scc_import_confirmed)\n\t\tfiles =\t\t\t\tself.builder.get_object(\"lstImportPackage\")\n\t\tcbImportHidden =\tself.builder.get_object(\"cbImportPackageHidden\")\n\t\tcbImportVisible =\tself.builder.get_object(\"cbImportPackageVisible\")\n\t\tcbImportNone =\t\tself.builder.get_object(\"cbImportPackageNone\")\n\t\trvAdvanced =\t\tself.builder.get_object(\"rvImportPackageAdvanced\")\n\t\tbtNext.set_label('Apply')\n\t\tbtNext.set_use_stock(True)\n\t\tmain_name = txName2.get_text()\n\t\tif self.check_name(main_name):\n\t\t\tbtNext.set_sensitive(True)\n\t\telse:\n\t\t\tbtNext.set_sensitive(False)\n\t\t\n\t\tcbImportHidden.set_label(_(\"Import as hidden menus and profiles named \\\".%s:name\\\"\") % (main_name,))\n\t\tcbImportVisible.set_label(_(\"Import normaly, with names formated as \\\"%s:name\\\"\") % (main_name,))\n\t\t\n\t\tfor i in range(0, len(files)):\n\t\t\tenabled, name, importas, type, obj = files[i]\n\t\t\tif enabled == 2:\n\t\t\t\timportas = main_name\n\t\t\telif cbImportHidden.get_active():\n\t\t\t\timportas = \".%s:%s\" % (main_name, name)\n\t\t\t\tenabled = 1\n\t\t\telif cbImportVisible.get_active():\n\t\t\t\timportas = \"%s:%s\" % (main_name, name)\n\t\t\t\tenabled = 1\n\t\t\telif cbImportNone.get_active():\n\t\t\t\tenabled = 0\n\t\t\tfiles[i] = enabled, name, importas, type, obj\n\t\n\t\n\tdef on_cbImportPackageAdvanced_toggled(self, *a):\n\t\trvImportPackageAdvanced =\tself.builder.get_object(\"rvImportPackageAdvanced\")\n\t\tcbImportPackageAdvanced =\tself.builder.get_object(\"cbImportPackageAdvanced\")\n\t\trvImportPackageAdvanced.set_reveal_child(cbImportPackageAdvanced.get_active())\n\t\n\t\n\tdef on_crIPKGEnabled_toggled(self, renderer, path):\n\t\tfiles = self.builder.get_object(\"lstImportPackage\")\n\t\ti = int(path)\n\t\tenabled, name, importas, type, obj = files[i]\n\t\t# 1st rown cannot be toggled\n\t\tif enabled != 2:\n\t\t\tenabled = 1 if enabled == 0 else 0\n\t\t\tfiles[i] = enabled, name, importas, type, obj\n\t\n\t\n\tdef on_crIPKGImportAs_edited(self, renderer, path, new_name):\n\t\tfiles =\t\tself.builder.get_object(\"lstImportPackage\")\n\t\ttxName2 =\tself.builder.get_object(\"txName2\")\n\t\ti = int(path)\n\t\tenabled, name, importas, type, obj = files[i]\n\t\timportas = new_name\n\t\tif enabled == 2:\n\t\t\ttxName2.set_text(importas)\n\t\tfiles[i] = enabled, name, importas, type, obj\n\t\n\t\n\tdef on_scc_import_confirmed(self, *a):\n\t\tfiles =\t\tself.builder.get_object(\"lstImportPackage\")\n\t\tnew_profile_names = {}\n\t\tnew_menu_names = {}\n\t\tfor enabled, name, importas, trash, obj in files:\n\t\t\tif enabled != 0:\n\t\t\t\tif isinstance(obj.obj, Profile):\n\t\t\t\t\tnew_profile_names[name] = importas\n\t\t\t\telif isinstance(obj.obj, MenuData):\n\t\t\t\t\tnew_menu_names[\"%s.menu\" % (name,)] = \"%s.menu\" % (importas,)\n\t\t\n\t\tdef apply_replacements(obj):\n\t\t\tfor a in obj.get_all_actions():\n\t\t\t\tif isinstance(a, ChangeProfileAction):\n\t\t\t\t\tif a.profile in new_profile_names:\n\t\t\t\t\t\ta.profile = new_profile_names[a.profile]\n\t\t\t\t\t\ta.parameters = tuple([ a.profile ] + list(a.parameters[1:]))\n\t\t\t\telif isinstance(a, MenuAction):\n\t\t\t\t\tif a.menu_id in new_menu_names:\n\t\t\t\t\t\ta.menu_id = new_menu_names[a.menu_id]\n\t\t\t\t\t\ta.parameters = tuple([ a.menu_id ] + list(a.parameters[1:]))\n\t\t\n\t\tfor enabled, trash, importas, trash, obj in files:\n\t\t\tif enabled != 0:\n\t\t\t\t# TODO: update references\n\t\t\t\tif isinstance(obj.obj, Profile):\n\t\t\t\t\tapply_replacements(obj.obj)\n\t\t\t\t\tobj.obj.save(os.path.join(get_profiles_path(), \"%s.sccprofile\" % (importas,)))\n\t\t\t\telif isinstance(obj.obj, MenuData):\n\t\t\t\t\tapply_replacements(obj.obj)\n\t\t\t\t\tjstr = Encoder(sort_keys=True, indent=4).encode(obj.obj)\n\t\t\t\t\tfilename = os.path.join(get_menus_path(), \"%s.menu\" % (importas,))\n\t\t\t\t\topen(filename, \"w\").write(jstr)\n\t\t\n\t\ttrash, trash, importas, trash, obj = files[0]\t# 1st is always profile that's being imported\n\t\tself.app.new_profile(obj.obj, importas)\n\t\tGLib.idle_add(self.window.destroy)\n"
  },
  {
    "path": "scc/gui/importexport/import_vdf.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Global Settings\n\nCurrently setups only one thing...\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gdk, GObject, GLib\nfrom scc.gui.editor import Editor, ComboSetter\nfrom scc.tools import get_profiles_path\nfrom scc.foreign.vdf import VDFProfile\nfrom scc.foreign.vdffz import VDFFZProfile\nfrom scc.lib.vdf import parse_vdf\n\nfrom io import StringIO\n\nimport re, sys, os, collections, threading, logging\nlog = logging.getLogger(\"IE.ImportVdf\")\n\nclass ImportVdf(object):\n\tPROFILE_LIST = \"config/localconfig.vdf\"\n\tSTEAMPATH = '~/.steam/steam/'\n\t\n\tdef __init__(self):\n\t\tself._profile = None\n\t\tself._lstVdfProfiles = self.builder.get_object(\"tvVdfProfiles\").get_model()\n\t\tself._q_games    = collections.deque()\n\t\tself._q_profiles = collections.deque()\n\t\tself._s_games    = threading.Semaphore(0)\n\t\tself._s_profiles = threading.Semaphore(0)\n\t\tself._lock = threading.Lock()\n\t\tself.__profile_load_started = False\n\t\tself._on_preload_finished = None\n\t\n\tdef on_grVdfImport_activated(self, *a):\n\t\tif not self.__profile_load_started:\n\t\t\tself.__profile_load_started = True\n\t\t\tthreading.Thread(target=self._load_profiles).start()\n\t\t\tthreading.Thread(target=self._load_game_names).start()\n\t\t\tthreading.Thread(target=self._load_profile_names).start()\n\t\tself.on_tvVdfProfiles_cursor_changed()\n\t\n\t\n\tdef _load_profiles(self):\n\t\t\"\"\"\n\t\tSearch for file containign list of profiles and reads it.\n\t\t\n\t\tThis is done in thread, with crazy hope that it will NOT crash GTK\n\t\tin the process.\n\t\t\"\"\"\n\t\tp = os.path.join(os.path.expanduser(self.STEAMPATH), \"userdata\")\n\t\ti = 0\n\t\tif os.path.exists(p):\n\t\t\tfor user in os.listdir(p):\n\t\t\t\tprofilelist = os.path.join(p, user, self.PROFILE_LIST)\n\t\t\t\tif os.path.isfile(profilelist):\n\t\t\t\t\tself._lock.acquire()\n\t\t\t\t\tlog.debug(\"Loading profile list from '%s'\", profilelist)\n\t\t\t\t\ttry:\n\t\t\t\t\t\ti = self._parse_profile_list(i, profilelist, user)\n\t\t\t\t\texcept Exception as e:\n\t\t\t\t\t\tlog.exception(e)\n\t\t\t\t\tself._lock.release()\n\t\tGLib.idle_add(self._load_finished)\n\t\n\tdef _parse_profile_list(self, i, filename, userid):\n\t\t\"\"\"\n\t\tParses localconfig.vdf and loads game and profile IDs. That is later\n\t\tdecoded into name of game and profile name.\n\t\t\n\t\tCalled from _load_profiles, in thread. Exceptions are catched and logged\n\t\tfrom there.\n\t\tCalls GLib.idle_add to send loaded data into UI.\n\t\t\"\"\"\n\t\t# VDF file is a ISO-8859-1 encoded file. Not UTF-8\n\t\tdata = parse_vdf(open(filename, \"r\", encoding = \"ISO-8859-1\"))\n\t\t# Sanity check\n\t\tif \"UserLocalConfigStore\" not in data: return\n\t\tif \"controller_config\" not in data[\"UserLocalConfigStore\"]: return\n\t\t\n\t\t# Grab config - currently only grabs SC configs!\n\t\tcc = data[\"UserLocalConfigStore\"][\"controller_config\"][userid][\"controller_steamcontroller_gordon\"][\"DEFAULT_FOR_TYPE\"]\n\t\t# Go through all games\n\t\tlistitems = []\n\t\ti = 0\n\t\tfor gameid in cc:\n\t\t\t# skip templates\n\t\t\tif 'selected' not in cc[gameid]:\n\t\t\t\tcontinue\n\t\t\t\n\t\t\tif not self._check_for_app_manifest(gameid):\n\t\t\t\tcontinue\n\t\t\t\t\n\t\t\tprofile_id = cc[gameid][\"selected\"]\n\t\t\tlistitems.append(( i, gameid, profile_id, None ))\n\t\t\ti += 1\n\t\t\tif len(listitems) > 10:\n\t\t\t\tGLib.idle_add(self.fill_list, listitems)\n\t\t\t\tlistitems = []\n\t\t\n\t\tGLib.idle_add(self.fill_list, listitems)\n\t\treturn i\n\t\n\tdef _check_for_app_manifest(self, gameid):\n\t\t\"\"\"\n\t\tChecks if an app manifest exists for a game.\n\t\tIt seems like a better idea to only worry about importing configs for games the user \t\t\talready has installed.\n\t\t\"\"\"\n\t\tsa_path = self._find_steamapps()\n\t\tif gameid.isdigit():\n\t\t\tfilename = os.path.join(sa_path, \"appmanifest_%s.acf\" % (gameid))\n\t\t\tif os.path.exists(filename):\n\t\t\t\treturn True\n\t\t\t\n\t\n\tdef _load_game_names(self):\n\t\t\"\"\"\n\t\tLoads names for game ids in q_games.\n\t\t\n\t\tThis is done in thread (not in same thread as _load_profiles),\n\t\tbecause it involves searching for apropriate file and parsing it\n\t\tentirely.\n\t\t\n\t\tCalls GLib.idle_add to send loaded data into UI.\n\t\t\"\"\"\n\t\t\n\t\tsa_path = self._find_steamapps()\n\t\twhile True:\n\t\t\tself._s_games.acquire(True)\t# Wait until something is added to the queue\n\t\t\ttry:\n\t\t\t\tindex, gameid = self._q_games.popleft()\n\t\t\texcept IndexError:\n\t\t\t\tbreak\n\t\t\tif gameid.isdigit():\n\t\t\t\tname = _(\"Unknown App ID %s\") % (gameid)\n\t\t\t\tfilename = os.path.join(sa_path, \"appmanifest_%s.acf\" % (gameid,))\n\t\t\t\tself._lock.acquire()\n\t\t\t\tif os.path.exists(filename):\n\t\t\t\t\ttry:\n\t\t\t\t\t\tdata = parse_vdf(open(filename, \"r\"))\n\t\t\t\t\t\tname = data['AppState']['name']\n\t\t\t\t\texcept Exception as e:\n\t\t\t\t\t\tlog.error(\"Failed to load app manifest for '%s'\", gameid)\n\t\t\t\t\t\tlog.exception(e)\n\t\t\t\telse:\n\t\t\t\t\tlog.warning(\"Skiping non-existing app manifest '%s'\", filename)\n\t\t\t\tself._lock.release()\n\t\t\telse:\n\t\t\t\tname = gameid\n\t\t\tGLib.idle_add(self._set_game_name, index, name)\n\t\n\t\n\t@staticmethod\n\tdef _find_legacy_bin(path):\n\t\t\"\"\"\n\t\tSearchs specified folder for any file ending in '_legacy.bin'\n\t\tand returns full path to first matching file.\n\t\tReturns None if path doesn't point to directory or there is\n\t\tno such file.\n\t\t\"\"\"\n\t\tif os.path.exists(path):\n\t\t\tfor f in os.listdir(path):\n\t\t\t\tif f.endswith(\"_legacy.bin\"):\n\t\t\t\t\treturn os.path.join(path, f)\n\t\treturn None\n\t\n\t\n\tdef _load_profile_names(self):\n\t\t\"\"\"\n\t\tLoads names for profiles ids in q_profiles.\n\t\t\n\t\tThis is same as _load_game_names, but for profiles.\n\t\t\"\"\"\n\t\tcontent_path = os.path.join(self._find_steamapps(), \"workshop/content\")\n\t\tif not os.path.exists(content_path):\n\t\t\tlog.warning(\"Cannot find '%s'; Cannot import anything without it\", content_path)\n\t\t\treturn\n\t\twhile True:\n\t\t\tself._s_profiles.acquire(True)\t# Wait until something is added to the queue\n\t\t\ttry:\n\t\t\t\tindex, gameid, profile_id = self._q_profiles.popleft()\n\t\t\texcept IndexError:\n\t\t\t\tbreak\n\t\t\tself._lock.acquire()\n\t\t\tfor user in os.listdir(content_path):\n\t\t\t\tfilename = os.path.join(content_path, user, profile_id, \"controller_configuration.vdf\")\n\t\t\t\tif not os.path.exists(filename):\n\t\t\t\t\t# If there is no 'controller_configuration.vdf', try finding *_legacy.bin\n\t\t\t\t\tfilename = self._find_legacy_bin(os.path.join(content_path, user, profile_id))\n\t\t\t\tif not filename or not os.path.exists(filename):\n\t\t\t\t\t# If not even *_legacy.bin is found, skip to next user\n\t\t\t\t\tcontinue\n\t\t\t\tlog.info(\"Reading '%s'\", filename)\n\t\t\t\ttry:\n\t\t\t\t\tdata = parse_vdf(open(filename, \"r\"))\n\t\t\t\t\tname = data['controller_mappings']['title']\n\t\t\t\t\tGLib.idle_add(self._set_profile_name, index, name, filename)\n\t\t\t\t\tbreak\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.error(\"Failed to read profile name from '%s'\", filename)\n\t\t\t\t\tlog.exception(e)\n\t\t\telse:\n\t\t\t\tlog.warning(\"Profile %s for game %s not found.\", profile_id, gameid)\n\t\t\t\tname = _(\"(not found)\")\n\t\t\t\tGLib.idle_add(self._set_profile_name, index, name, None)\n\t\t\tself._lock.release()\n\t\t\t\n\t\n\t\n\tdef _load_finished(self):\n\t\t\"\"\" Called in main thread after _load_profiles is finished \"\"\"\n\t\tself.builder.get_object(\"rvLoading\").set_reveal_child(False)\n\t\tself.loading = False\n\t\tself._s_games.release()\n\t\tself._s_profiles.release()\n\t\tif self._on_preload_finished:\n\t\t\tcb, data = self._on_preload_finished\n\t\t\tGLib.idle_add(cb, *data)\n\t\t\tself._on_preload_finished = None\n\t\n\t\n\tdef _find_steamapps(self):\n\t\t\"\"\"\n\t\tReturns path to SteamApps folder or None if it cannot be found.\n\t\tThis is done because Steam apparently supports both SteamApps and\n\t\tsteamapps as name for this folder.\n\t\t\"\"\"\n\t\tfor x in (\"SteamApps\", \"steamapps\", \"Steamapps\", \"steamApps\"):\n\t\t\tpath = os.path.join(os.path.expanduser(self.STEAMPATH), x)\n\t\t\tif os.path.exists(path):\n\t\t\t\treturn path\n\t\tlog.warning(\"Cannot find SteamApps directory\")\n\t\treturn None\n\t\n\t\n\tdef _set_game_name(self, index, name):\n\t\tself._lstVdfProfiles[index][1] = name\n\t\n\t\n\tdef _set_profile_name(self, index, name, filename):\n\t\tself._lstVdfProfiles[index][2] = name\n\t\tself._lstVdfProfiles[index][3] = filename\n\t\n\t\n\tdef fill_list(self, items):\n\t\t\"\"\"\n\t\tAdds items to profile list. Has to run in main thread, \n\t\totherwise, GTK will crash.\n\t\t\"\"\"\n\t\tfor i in items:\n\t\t\tself._lstVdfProfiles.append(i)\n\t\t\tself._q_games.append(( i[0], i[1] ))\n\t\t\tself._s_games.release()\n\t\t\tself._q_profiles.append(( i[0], i[1], i[2] ))\n\t\t\tself._s_profiles.release()\n\t\n\t\n\tdef on_tvVdfProfiles_cursor_changed(self, *a):\n\t\t\"\"\"\n\t\tCalled when user selects profile.\n\t\tCheck if file for that profile is known and if yes, enables next page.\n\t\t\"\"\"\n\t\ttvVdfProfiles = self.builder.get_object(\"tvVdfProfiles\")\n\t\tmodel, iter = tvVdfProfiles.get_selection().get_selected()\n\t\tfilename = None\n\t\tif iter:\n\t\t\tfilename = model.get_value(iter, 3)\n\n\t\tself.enable_next(filename is not None, self.import_vdf)\n\n\t\n\t@staticmethod\n\tdef gen_aset_name(base_name, set_name):\n\t\t\"\"\" Generates name for profile converted from action set \"\"\"\n\t\tif set_name == 'default':\n\t\t\treturn base_name\n\t\treturn (\".\" + base_name + \":\" + set_name.lower()).encode('utf-8')\n\t\n\t\n\tdef on_txName_changed(self, *a):\n\t\t\"\"\"\n\t\tCalled when text in profile name field is changed.\n\t\tBasically enables 'Save' button if name is not empty string.\n\t\t\"\"\"\n\t\ttxName\t\t\t= self.builder.get_object(\"txName\")\n\t\tlblASetsNotice\t= self.builder.get_object(\"lblASetsNotice\")\n\t\tlblASetList\t\t= self.builder.get_object(\"lblASetList\")\n\t\t\n\t\tbtNext = self.enable_next(True, self.vdf_import_confirmed)\n\t\tbtNext.set_label('Apply')\n\t\tbtNext.set_use_stock(True)\n\t\tif len(self._profile.action_sets) > 1:\n\t\t\tlblASetsNotice.set_visible(True)\n\t\t\tlblASetList.set_visible(True)\n\t\t\tlog.info(\"Imported profile contains action sets\")\n\t\t\tlblASetList.set_text(\"\\n\".join([\n\t\t\t\tself.gen_aset_name(txName.get_text().strip(), x)\n\t\t\t\tfor x in self._profile.action_sets\n\t\t\t\tif x != 'default'\n\t\t\t]))\n\t\telse:\n\t\t\tlblASetsNotice.set_visible(False)\n\t\t\tlblASetList.set_visible(False)\t\n\t\tbtNext.set_sensitive(self.check_name(txName.get_text()))\n\t\n\t\n\tdef on_preload_finished(self, callback, *data):\n\t\t\"\"\"\n\t\tSchedules callback to be called after initial profile list is loaded\n\t\t\"\"\"\n\t\tself._on_preload_finished = (callback, data)\n\t\n\t\n\tdef set_vdf_file(self, filename):\n\t\t# TODO: Jump directly to page\n\t\ttvVdfProfiles = self.builder.get_object(\"tvVdfProfiles\")\n\t\titer = self._lstVdfProfiles.append(( -1, _(\"No game\"), _(\"Dropped profile\"), filename ))\n\t\ttvVdfProfiles.get_selection().select_iter(iter)\n\t\tself.window.set_page_complete(self.window.get_nth_page(0), True)\n\t\tself.window.set_current_page(1)\n\t\n\t\n\tdef on_btDump_clicked(self, *a):\n\t\ttvError = self.builder.get_object(\"tvError\")\n\t\tswError = self.builder.get_object(\"swError\")\n\t\tbtDump = self.builder.get_object(\"btDump\")\n\t\ttvVdfProfiles = self.builder.get_object(\"tvVdfProfiles\")\n\t\tmodel, iter = tvVdfProfiles.get_selection().get_selected()\n\t\tfilename = model.get_value(iter, 3)\n\t\t\n\t\tdump = StringIO()\n\t\tdump.write(\"\\nProfile filename: %s\\n\" % (filename,))\n\t\tdump.write(\"\\nProfile dump:\\n\")\n\t\ttry:\n\t\t\tdump.write(open(filename, \"r\").read())\n\t\texcept Exception as e:\n\t\t\tdump.write(\"(failed to write: %s)\" % (e,))\n\t\ttvError.get_buffer().set_text(dump.getvalue())\n\t\tswError.set_visible(True)\n\t\tbtDump.set_sensitive(False)\n\t\n\t\n\tdef import_vdf(self, filename=None):\n\t\tgrVdfImportFinished = self.builder.get_object(\"grVdfImportFinished\")\n\t\tself.next_page(grVdfImportFinished)\n\t\t\n\t\ttvVdfProfiles = self.builder.get_object(\"tvVdfProfiles\")\n\t\tlblVdfImportFinished = self.builder.get_object(\"lblVdfImportFinished\")\n\t\tlblError = self.builder.get_object(\"lblError\")\n\t\ttvError = self.builder.get_object(\"tvError\")\n\t\tswError = self.builder.get_object(\"swError\")\n\t\tlblName = self.builder.get_object(\"lblName\")\n\t\ttxName = self.builder.get_object(\"txName\")\n\t\tbtDump = self.builder.get_object(\"btDump\")\n\t\t\n\t\tif filename is None:\n\t\t\tmodel, iter = tvVdfProfiles.get_selection().get_selected()\n\t\t\tfilename = model.get_value(iter, 3)\n\t\tif filename.endswith(\".vdffz\"):\n\t\t\tself._profile = VDFFZProfile()\n\t\telse:\n\t\t\t# Best quess\n\t\t\tself._profile = VDFProfile()\n\t\t\n\t\tfailed = False\n\t\terror_log = StringIO()\n\t\tself._lock.acquire()\n\t\thandler = logging.StreamHandler(error_log)\n\t\tlogging.getLogger().addHandler(handler)\n\t\tswError.set_visible(False)\n\t\tlblError.set_visible(False)\n\t\tlblName.set_visible(True)\n\t\ttxName.set_visible(True)\n\t\tbtDump.set_sensitive(True)\n\t\t\n\t\ttry:\n\t\t\tself._profile.load(filename)\n\t\texcept Exception as e:\n\t\t\tlog.exception(e)\n\t\t\tlblName.set_visible(False)\n\t\t\ttxName.set_visible(False)\n\t\t\ttxName.set_text(\"\")\n\t\t\tself._profile = None\n\t\t\tfailed = True\n\t\t\n\t\tlogging.getLogger().removeHandler(handler)\n\t\tself._lock.release()\n\t\t\n\t\tif failed:\n\t\t\tswError.set_visible(True)\n\t\t\tlblError.set_visible(True)\n\t\t\tbtDump.set_sensitive(False)\n\t\t\t\n\t\t\tlblVdfImportFinished.set_text(_(\"Import failed\"))\n\t\t\t\n\t\t\terror_log.write(\"\\nProfile filename: %s\\n\" % (filename,))\n\t\t\terror_log.write(\"\\nProfile dump:\\n\")\n\t\t\ttry:\n\t\t\t\terror_log.write(open(filename, \"r\").read())\n\t\t\texcept Exception as e:\n\t\t\t\terror_log.write(\"(failed to write: %s)\" % (e,))\n\t\t\t\n\t\t\ttvError.get_buffer().set_text(error_log.getvalue())\n\t\telse:\n\t\t\tif len(error_log.getvalue()) > 0:\n\t\t\t\t# Some warnings were displayed\n\t\t\t\tswError.set_visible(True)\n\t\t\t\tlblError.set_visible(True)\n\t\t\t\t\n\t\t\t\tlblVdfImportFinished.set_text(_(\"Profile imported with warnings\"))\n\t\t\t\t\n\t\t\t\ttvError.get_buffer().set_text(error_log.getvalue())\n\t\t\t\ttxName.set_text(self._profile.name)\n\t\t\telse:\n\t\t\t\tlblVdfImportFinished.set_text(_(\"Profile sucessfully imported\"))\n\t\t\t\ttxName.set_text(self._profile.name)\n\t\t\tself.on_txName_changed()\n\t\n\t\n\tdef vdf_import_confirmed(self, *a):\n\t\tname = self.builder.get_object(\"txName\").get_text().strip()\n\t\t\n\t\tif len(self._profile.action_sets) > 1:\n\t\t\t# Update ChangeProfileActions with correct profile names\n\t\t\tfor x in self._profile.action_set_switches:\n\t\t\t\tid = int(x._profile.split(\":\")[-1])\n\t\t\t\ttarget_set = self._profile.action_set_by_id(id)\n\t\t\t\tx._profile = self.gen_aset_name(name, target_set)\n\t\t\t\n\t\t\t# Save action set profiles\n\t\t\tfor k in self._profile.action_sets:\n\t\t\t\tif k != 'default':\n\t\t\t\t\tfilename = self.gen_aset_name(name, k) + \".sccprofile\"\n\t\t\t\t\tpath = os.path.join(get_profiles_path(), filename)\n\t\t\t\t\tself._profile.action_sets[k].save(path)\n\t\t\n\t\tself.app.new_profile(self._profile, name)\n\t\tGLib.idle_add(self.window.destroy)\n"
  },
  {
    "path": "scc/gui/key_grabber.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor\n\nAllows to edit button or trigger action.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.gui.controller_widget import ControllerButton\nfrom scc.gui.gdk_to_key import keyevent_to_key\nfrom scc.gui.editor import Editor\nfrom scc.actions import Action, ButtonAction, NoAction\nfrom scc.macros import Macro, Repeat, SleepAction, PressAction, ReleaseAction\nfrom scc.modifiers import ModeModifier\nfrom scc.constants import SCButtons\nfrom scc.profile import Profile\nfrom scc.uinput import Keys\n\nfrom gi.repository import Gtk, Gdk, GLib\nimport os, logging\nlog = logging.getLogger(\"KeyGrabber\")\n\nMODIFIERS = [ Keys.KEY_LEFTCTRL, Keys.KEY_LEFTMETA, Keys.KEY_LEFTALT,\n\tKeys.KEY_RIGHTALT, Keys.KEY_RIGHTMETA, Keys.KEY_RIGHTCTRL,\n\tKeys.KEY_LEFTSHIFT, Keys.KEY_RIGHTSHIFT\n]\n\n\ndef merge_modifiers(mods):\n\treturn \"+\".join([ key.name.split(\"_\")[-1] for key in mods ])\n\n\n# Just to speed shit up, KeyGrabber is singleton\nclass KeyGrabber(object):\n\tGLADE = \"key_grabber.glade\"\n\t_singleton = None\n\t\n\tdef __new__(cls, *a):\n\t\tif cls._singleton is None:\n\t\t\tcls._singleton = object.__new__(cls)\n\t\treturn cls._singleton\n\t\n\t\n\tdef __init__(self, app):\n\t\tself.app = app\n\t\tself.builder = None\n\t\tself.active_mods = []\n\t\n\t\n\tdef grab(self, modal_for, action, callback):\n\t\tif self.builder is None:\n\t\t\tself.setup_widgets()\n\t\tself.callback = callback\n\t\tself.builder.get_object(\"lblKey\").set_label(\"...\")\n\t\t# TODO: Display 'action' in lblKey if possible\n\t\tfor key in MODIFIERS:\n\t\t\tself.builder.get_object(\"tg\" + key.name).set_active(False)\n\t\tself.active_mods = []\n\t\tself.window.set_transient_for(modal_for)\n\t\tself.window.set_modal(True)\n\t\tself.window.show()\n\t\tself.window.set_focus()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tself.builder = Gtk.Builder()\n\t\tself.builder.add_from_file(os.path.join(self.app.gladepath, self.GLADE))\n\t\tself.window = self.builder.get_object(\"KeyGrab\")\n\t\tself.builder.connect_signals(self)\n\t\n\t\n\tdef on_KeyGrab_destroy(self, *a):\n\t\t# Don't allow destroying\n\t\treturn True\n\t\n\t\n\tdef on_keyGrab_key_press_event(self, trash, event):\n\t\t\"\"\"\n\t\tHandles keypress on \"Grab Key\" dialog.\n\t\t\n\t\tRemembers modifiers and displays text in middle of dialog.\n\t\tDialog is dismissed (and key is accepted) by key_release handler bellow.\n\t\t\"\"\"\n\t\tkey = keyevent_to_key(event)\n\t\tif key is None:\n\t\t\tlog.warning(\"Unknown keycode %s/%s\" % (event.keyval, event.hardware_keycode))\n\t\t\treturn\n\t\t\n\t\tif key in MODIFIERS:\n\t\t\tself.active_mods.append(key)\n\t\t\tself.builder.get_object(\"tg\" + key.name).set_active(True)\n\t\t\tself.builder.get_object(\"lblKey\").set_label(merge_modifiers(self.active_mods))\n\t\t\treturn\n\t\t\n\t\tlabel = merge_modifiers(self.active_mods)\n\t\tif len(self.active_mods) > 0:\n\t\t\tlabel = label + \"+\" + key.name\n\t\telse:\n\t\t\tlabel = key.name\n\t\tself.builder.get_object(\"lblKey\").set_label(label)\n\t\n\t\n\tdef on_keyGrab_key_release_event(self, trash, event):\n\t\t\"\"\"\n\t\tHandles keyrelease on \"Grab Key\" dialog.\n\t\t\n\t\tKey is accepted if either:\n\t\t- released key is not modifier\n\t\t- released key is modifier, but there is no other modifier key pressed\n\t\t\n\t\tCalls callback if key is accepted\n\t\t\"\"\"\n\t\tkey = keyevent_to_key(event)\n\t\tif key is not None:\n\t\t\tif key in MODIFIERS:\n\t\t\t\tif key in self.active_mods:\n\t\t\t\t\tif len(self.active_mods) == 1:\n\t\t\t\t\t\t# Releasing last modifier\n\t\t\t\t\t\tself.callback([key])\n\t\t\t\t\t\tself.window.hide()\n\t\t\t\t\t\treturn\n\t\t\t\t\tself.active_mods.remove(key)\n\t\t\t\t\tself.builder.get_object(\"tg\" + key.name).set_active(False)\n\t\t\t\tself.builder.get_object(\"lblKey\").set_label(\"+\".join([key.name.split(\"_\")[-1] for key in self.active_mods]))\n\t\t\t\treturn\n\t\t\t\n\t\t\tself.callback(self.active_mods + [key])\n\t\t\tself.window.hide()\n\t\n\t\n\tdef on_tgkey_toggled(self, obj, *a):\n\t\t\"\"\"\n\t\tHandles when user clicks on modifier buttons in \"Grab Key\" dialog\n\t\t\"\"\"\n\t\tfor key in MODIFIERS:\n\t\t\tif self.builder.get_object(\"tg\" + key.name) == obj:\n\t\t\t\tif obj.get_active() and not key in self.active_mods:\n\t\t\t\t\tself.active_mods.append(key)\n\t\t\t\t\tself.builder.get_object(\"lblKey\").set_label(merge_modifiers(self.active_mods))\n\t\t\t\telif not obj.get_active() and key in self.active_mods:\n\t\t\t\t\tself.active_mods.remove(key)\n\t\t\t\t\tself.builder.get_object(\"lblKey\").set_label(merge_modifiers(self.active_mods))\n\t\t\t\treturn\n"
  },
  {
    "path": "scc/gui/keycode_to_key.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - KEYCODE_TO_KEY\n\nSimilar to GDK_TO_KEY, maps X11 keycodes to Keys.KEY_* constants.\nUsed by OSD keyboard\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.uinput import Keys\nfrom .gdk_to_key import KEYCODE_TO_KEY\n\nKEY_TO_KEYCODE = { KEYCODE_TO_KEY[a] : a for a in KEYCODE_TO_KEY }\n"
  },
  {
    "path": "scc/gui/macro_editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor\n\nAllows to edit button or trigger action.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.gui.controller_widget import ControllerButton\nfrom scc.gui.editor import Editor\nfrom scc.macros import SleepAction, PressAction, ReleaseAction\nfrom scc.actions import Action, ButtonAction, NoAction\nfrom scc.macros import Macro, Repeat, Cycle\nfrom scc.modifiers import ModeModifier\nfrom scc.constants import SCButtons\nfrom scc.profile import Profile\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom collections import namedtuple\nimport os, logging\nlog = logging.getLogger(\"MacroEditor\")\n\nclass MacroEditor(Editor):\n\tGLADE = \"macro_editor.glade\"\n\t\n\tdef __init__(self, app, callback):\n\t\tEditor.__init__(self)\n\t\tself.app = app\n\t\tself.id = None\n\t\tself.mode = Action.AC_BUTTON\n\t\tself.ac_callback = callback\n\t\tself.added_widget = None\n\t\tself.setup_widgets()\n\t\tself.actions = []\n\t\n\t\n\tdef update_action_field(self):\n\t\t\"\"\" Updates field on bottom \"\"\"\n\t\tentAction = self.builder.get_object(\"entAction\")\n\t\tcbMacroType = self.builder.get_object(\"cbMacroType\")\n\t\tbtAddDelay = self.builder.get_object(\"btAddDelay\")\n\t\tentAction.set_text(self._make_action().to_string())\n\t\t\n\t\t# Disable all action type comboboxes in Cycle mode,\n\t\t# only click is allowed there;\n\t\t# Reenable them if action type is set to anything else.\n\t\tsens = cbMacroType.get_active() != 2\n\t\tfor ad in self.actions:\n\t\t\tif isinstance(ad.action, ButtonAction) or isinstance(ad.action, PressAction):\n\t\t\t\tif sens:\n\t\t\t\t\tad.combo.set_sensitive(True)\n\t\t\t\telse:\n\t\t\t\t\tad.combo.set_active(0)\n\t\t\t\t\tad.combo.set_sensitive(False)\n\t\t\telif isinstance(ad.action, SleepAction):\n\t\t\t\tad.label.set_sensitive(sens)\n\t\t\t\tad.scale.set_sensitive(sens)\n\t\t# Do same thing for 'Add Delay' button\n\t\tbtAddDelay.set_sensitive(sens)\n\t\n\t\n\tdef _make_action(self):\n\t\t\"\"\" Generates and returns Action instance \"\"\"\n\t\tentName = self.builder.get_object(\"entName\")\n\t\tcbMacroType = self.builder.get_object(\"cbMacroType\")\n\t\tpars = [ x[0] for x in self.actions ]\n\t\tif len(pars) == 0:\n\t\t\t# No action is actually set\n\t\t\taction = NoAction()\n\t\telif cbMacroType.get_active() == 2:\n\t\t\t# Cycle\n\t\t\tpars = filter(lambda a : not isinstance(a, SleepAction), pars)\n\t\t\taction = Cycle(*pars)\n\t\telif cbMacroType.get_active() == 1:\n\t\t\t# Repeating macro\n\t\t\taction = Macro(*pars)\n\t\t\taction.repeat = True\n\t\telif len(pars) == 1:\n\t\t\t# Only one action\n\t\t\taction = pars[0]\n\t\telse:\n\t\t\t# Macro\n\t\t\taction = Macro(*pars)\n\t\tif entName.get_text().strip() != \"\":\n\t\t\taction.name = entName.get_text().strip()\n\t\treturn action\n\t\n\t\n\tdef _add_action(self, action):\n\t\t\"\"\" Adds widgets for new action \"\"\"\n\t\tgrActions = self.builder.get_object(\"grActions\")\n\t\tmodel = self.builder.get_object(\"lstPressClickOrHold\")\n\t\ti = len(self.actions) + 1\n\t\taction.name = None\n\t\taction_data = None\n\t\t\n\t\t# Buttons\n\t\tbutton_up, button_down, button_clear = Gtk.Button(), Gtk.Button(), Gtk.Button()\n\t\tb = Gtk.Button.new_with_label(action.describe(self.mode))\n\t\t\n\t\t# Action button\n\t\tb.set_property(\"hexpand\", True)\n\t\tb.set_property(\"margin-left\", 10)\n\t\tb.set_property(\"margin-right\", 10)\n\t\t\n\t\tif isinstance(action, ButtonAction) or isinstance(action, PressAction):\n\t\t\t# Combobox\n\t\t\tc = Gtk.ComboBox()\n\t\t\tc.set_model(model)\n\t\t\tc.set_size_request(100, -1)\n\t\t\trenderer = Gtk.CellRendererText()\n\t\t\tc.pack_start(renderer, True)\n\t\t\tc.add_attribute(renderer, 'text', 1)\n\t\t\tif isinstance(action, ReleaseAction):\n\t\t\t\tc.set_active(2)\n\t\t\t\tb.set_label(action.describe_short())\n\t\t\telif isinstance(action, PressAction):\n\t\t\t\tc.set_active(1)\n\t\t\t\tb.set_label(action.describe_short())\n\t\t\telse:\n\t\t\t\tc.set_active(0)\n\t\t\taction_data = ActionData( action = action, button_up = button_up,\n\t\t\t\tbutton_down = button_down, button_clear = button_clear,\n\t\t\t\tcombo = c, button_action = b)\n\t\t\t\n\t\t\tc.connect('changed', self.on_buttonaction_type_change, i - 1, action_data)\n\t\t\tgrActions.attach(c,\t\t\t0, i, 1, 1)\n\t\t\tgrActions.attach(b,\t\t\t1, i, 1, 1)\n\t\telif isinstance(action, SleepAction):\n\t\t\t# Label and scale\n\t\t\tl = Gtk.Label(\"\")\n\t\t\tl.set_xalign(0.0)\n\t\t\tl.set_size_request(100, -1)\n\t\t\ts = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 5, 5000, 5)\n\t\t\ts.set_draw_value(False)\n\t\t\ts.set_value(action.delay * 1000)\n\t\t\taction_data = ActionData( action = action, button_up = button_up,\n\t\t\t\tbutton_down = button_down, button_clear = button_clear,\n\t\t\t\tlabel = l, scale = s)\n\t\t\t\n\t\t\ts.connect('change_value', self.on_change_delay, action_data)\n\t\t\tgrActions.attach(l,\t\t\t0, i, 1, 1)\n\t\t\tgrActions.attach(s,\t\t\t1, i, 1, 1)\n\t\t\tself.on_change_delay(s, None, action.delay * 1000, action_data)\n\t\telse:\n\t\t\t# Placeholder\n\t\t\tl = Gtk.Label(\"\")\n\t\t\tl.set_size_request(100, -1)\n\t\t\t\n\t\t\taction_data = ActionData( action = action, button_up = button_up,\n\t\t\t\tbutton_down = button_down, button_clear = button_clear,\n\t\t\t\tlabel = l, button_action = b)\n\t\t\t\n\t\t\tgrActions.attach(l,\t\t\t0, i, 1, 1)\n\t\t\tgrActions.attach(b,\t\t\t1, i, 1, 1)\n\t\t\n\t\tb.connect('clicked',\t\t\tself.on_actionb_clicked, i - 1, action_data)\n\t\tbutton_clear.connect('clicked',\tself.on_clearb_clicked, action_data)\n\t\tbutton_up.connect('clicked',\tself.on_moveb_clicked, -1, action_data)\n\t\tbutton_down.connect('clicked',\tself.on_moveb_clicked,  1, action_data)\n\t\t\n\t\t# Move Up button\n\t\tbutton_up.set_image(Gtk.Image.new_from_stock(\"gtk-go-up\", Gtk.IconSize.SMALL_TOOLBAR))\n\t\tbutton_up.set_relief(Gtk.ReliefStyle.NONE)\n\t\t\n\t\t# Move Down button\n\t\tbutton_down.set_image(Gtk.Image.new_from_stock(\"gtk-go-down\", Gtk.IconSize.SMALL_TOOLBAR))\n\t\tbutton_down.set_relief(Gtk.ReliefStyle.NONE)\n\t\t\n\t\t# Clear button\n\t\tbutton_clear.set_image(Gtk.Image.new_from_stock(\"gtk-delete\", Gtk.IconSize.SMALL_TOOLBAR))\n\t\tbutton_clear.set_relief(Gtk.ReliefStyle.NONE)\n\t\t\n\t\t# Pack\n\t\tgrActions.attach(button_up,\t\t2, i, 1, 1)\n\t\tgrActions.attach(button_down,\t3, i, 1, 1)\n\t\tgrActions.attach(button_clear,\t4, i, 1, 1)\n\t\t\n\t\t# Disable 'up' button on 1st aciton\n\t\tif len(self.actions) == 0:\n\t\t\tbutton_up.set_sensitive(False)\n\t\t# Reenable 'down' button on last action\n\t\tif len(self.actions) > 0:\n\t\t\tself.actions[-1].button_down.set_sensitive(True)\n\t\t# Disable 'down' on added (now last) action\n\t\tbutton_down.set_sensitive(False)\n\t\t\n\t\tself.actions.append(action_data)\n\t\tself.update_action_field()\n\t\tgrActions.show_all()\n\t\n\t\n\tdef on_moveb_clicked(self, trash, direction, action_data):\n\t\t\"\"\" Handler for 'move action' buttons \"\"\"\n\t\taction = action_data.action\n\t\treadd = [ x.action for x in self.actions ]\n\t\tindex = readd.index(action) + direction\n\t\tif index < 0:\n\t\t\t# Not possible to move 1st item up\n\t\t\treturn\n\t\tif index > len(readd):\n\t\t\t# Not possible to move last item down\n\t\t\treturn\n\t\treadd.remove(action)\n\t\treadd.insert(index, action)\n\t\tself._refill_grid(readd)\n\t\tself.update_action_field()\n\t\n\t\n\tdef on_clearb_clicked(self, trash, action_data):\n\t\t\"\"\" Handler for 'delete action' button \"\"\"\n\t\tself._clear_grid()\n\t\tself.actions.remove(action_data)\n\t\treadd = [ x.action for x in self.actions ]\n\t\tself._refill_grid(readd)\n\t\tself.update_action_field()\n\t\n\t\n\tdef on_cbMacroType_changed(self, *a):\n\t\tself.update_action_field()\n\t\n\t\n\tdef on_buttonaction_type_change(self, cb, i, action_data):\n\t\taction = action_data.action\n\t\tif isinstance(action, (PressAction, ReleaseAction)):\n\t\t\taction = action.action\n\t\tif cb.get_active() == 0:\n\t\t\tif isinstance(action, ButtonAction):\n\t\t\t\tself.actions[i] = action_data._replace(action = ButtonAction(action.button))\n\t\telif cb.get_active() == 1:\n\t\t\tself.actions[i] = action_data._replace(action = PressAction(action))\n\t\telse:\n\t\t\tself.actions[i] = action_data._replace(action = ReleaseAction(action))\n\t\tself.update_action_field()\n\t\t\n\t\n\tdef _clear_grid(self):\n\t\t\"\"\" Removes everything from UI \"\"\"\n\t\tgrActions = self.builder.get_object(\"grActions\")\n\t\tfor child in [] + grActions.get_children():\n\t\t\tgrActions.remove(child)\n\t\n\t\n\tdef _refill_grid(self, new_actions):\n\t\t\"\"\" Removes everything from UI and then adds updated stuff back \"\"\"\n\t\tself._clear_grid()\n\t\tself.actions = []\n\t\tfor a in new_actions:\n\t\t\tself._add_action(a)\n\t\n\t\n\tdef on_change_delay(self, scale, trash, value, action_data):\n\t\t\"\"\" Called when delay slider is moved \"\"\"\n\t\tms = int(value)\n\t\taction = action_data.action\n\t\tlabel = action_data.label\n\t\taction.delay = value / 1000.0\n\t\tif ms < 1000:\n\t\t\tlabel.set_markup(_(\"<b>Delay: %sms</b>\") % (ms,))\n\t\telse:\n\t\t\ts = ms / 1000.0\n\t\t\tlabel.set_markup(_(\"<b>Delay: %0.2fs</b>\") % (s,))\n\t\tself.update_action_field()\n\t\n\t\n\tdef on_actionb_clicked(self, button, i, action_data):\n\t\t\"\"\" Handler clicking on action name \"\"\"\n\t\tdef on_chosen(id, action):\n\t\t\treadd = [ x.action for x in self.actions ]\n\t\t\treadd[i] = action\n\t\t\tself._refill_grid(readd)\n\t\t\tself.update_action_field()\n\t\t\n\t\tfrom scc.gui.action_editor import ActionEditor\t# Cannot be imported @ top\n\t\tae = ActionEditor(self.app, on_chosen)\n\t\tae.set_title(_(\"Edit Action\"))\n\t\tae.set_input(self.id, action_data.action, mode=self.mode)\n\t\tae.hide_modeshift()\n\t\tae.hide_macro()\n\t\tae.hide_name()\n\t\tae.show(self.window)\n\t\n\t\n\tdef on_btAddAction_clicked(self, *a):\n\t\t\"\"\" Handler for Add Action button \"\"\"\n\t\tself._add_action(NoAction())\n\t\n\t\n\tdef on_btAddDelay_clicked(self, *a):\n\t\t\"\"\" Handler for Add Delay button \"\"\"\n\t\tself._add_action(SleepAction(0.5))\n\t\n\t\n\tdef on_btClear_clicked(self, *a):\n\t\t\"\"\" Handler for clear button \"\"\"\n\t\taction = NoAction()\n\t\tif self.ac_callback is not None:\n\t\t\tself.ac_callback(self.id, action)\n\t\tself.close()\n\t\n\t\n\tdef on_btCustomActionEditor_clicked(self, *a):\n\t\t\"\"\" Handler for 'Custom Editor' button \"\"\"\n\t\tfrom scc.gui.action_editor import ActionEditor\t# Can't be imported on top\n\t\te = ActionEditor(self.app, self.ac_callback)\n\t\te.set_input(self.id, self._make_action(), mode = self.mode)\n\t\te.hide_action_buttons()\n\t\te.hide_advanced_settings()\n\t\te.set_title(self.window.get_title())\n\t\te.force_page(e.load_component(\"custom\"), True)\n\t\tself.send_added_widget(e)\n\t\tself.close()\n\t\te.show(self.get_transient_for())\n\t\n\t\n\tdef on_btOK_clicked(self, *a):\n\t\t\"\"\" Handler for OK button \"\"\"\n\t\ta = self._make_action()\n\t\tif self.ac_callback is not None:\n\t\t\tself.ac_callback(self.id, a)\n\t\tself.close()\n\t\n\t\n\tdef add_widget(self, label, widget):\n\t\t\"\"\"\n\t\tSee ActionEditor.add_widget\n\t\t\"\"\"\n\t\tlblAddedWidget = self.builder.get_object(\"lblAddedWidget\")\n\t\tvbAddedWidget = self.builder.get_object(\"vbAddedWidget\")\n\t\tlblAddedWidget.set_label(label)\n\t\tlblAddedWidget.set_visible(True)\n\t\tfor ch in vbAddedWidget.get_children():\n\t\t\tvbAddedWidget.remove(ch)\n\t\tself.added_widget = widget\n\t\tvbAddedWidget.pack_start(widget, True, False, 0)\n\t\tvbAddedWidget.set_visible(True)\n\t\n\t\n\tdef on_Dialog_destroy(self, *a):\n\t\tvbAddedWidget = self.builder.get_object(\"vbAddedWidget\")\n\t\tfor ch in vbAddedWidget.get_children():\n\t\t\tvbAddedWidget.remove(ch)\t\n\t\n\t\n\tdef allow_first_page(self):\n\t\t\"\"\" For compatibility with action editor. Does nothing \"\"\"\n\t\tpass\n\t\n\t\n\tdef set_input(self, id, action, mode=Action.AC_BUTTON):\n\t\t\"\"\" Common part of editor setup \"\"\"\n\t\tbtDefault = self.builder.get_object(\"btDefault\")\n\t\tentName = self.builder.get_object(\"entName\")\n\t\tcbMacroType = self.builder.get_object(\"cbMacroType\")\n\t\tself.id = id\n\t\tself.mode = mode\n\t\tself.set_title(\"Macro for %s\" % (id.name if id in SCButtons.__members__.values() else str(id),))\n\t\tif isinstance(action, Cycle):\n\t\t\tcbMacroType.set_active(2)\n\t\telif action.repeat:\n\t\t\tcbMacroType.set_active(1)\n\t\telse:\n\t\t\tcbMacroType.set_active(0)\n\t\tfor a in action.actions:\n\t\t\tself._add_action(a)\n\t\tif action.name is not None:\n\t\t\tentName.set_text(action.name)\n\t\n\t\n\tdef hide_name(self):\n\t\t\"\"\"\n\t\tHides (and clears) name field.\n\t\t\"\"\"\n\t\tself.builder.get_object(\"lblName\").set_visible(False)\n\t\tself.builder.get_object(\"entName\").set_visible(False)\n\t\tself.builder.get_object(\"entName\").set_text(\"\")\n\n\nActionData = namedtuple('ActionData',\n\t'action, button_up, button_down, button_clear, button_action,'\n\t'combo, label, scale')\nActionData.__new__.__defaults__ = (None,) * len(ActionData._fields)\n"
  },
  {
    "path": "scc/gui/menu_editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Menu Editor\n\nEdits .menu files and menus stored in profile.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib, GObject\nfrom scc.gui.action_editor import ActionEditor\nfrom scc.gui.icon_chooser import IconChooser\nfrom scc.gui.dwsnc import headerbar\nfrom scc.gui.editor import Editor\nfrom scc.osd.menu_generators import ProfileListMenuGenerator\nfrom scc.osd.menu_generators import RecentListMenuGenerator\nfrom scc.osd.menu_generators import GameListMenuGenerator\nfrom scc.osd.menu import MenuIcon\nfrom scc.menu_data import MenuData, MenuItem, Submenu, Separator, MenuGenerator\nfrom scc.paths import get_menus_path, get_default_menus_path\nfrom scc.parser import TalkingActionParser\nfrom scc.actions import Action, NoAction\nfrom scc.tools import find_icon\nfrom scc.profile import Encoder\nimport os, traceback, logging, json\nlog = logging.getLogger(\"MenuEditor\")\n\n\nclass MenuEditor(Editor):\n\tGLADE = \"menu_editor.glade\"\n\tTYPE_INTERNAL\t= 1\n\tTYPE_GLOBAL\t\t= 2\n\t\n\t\n\tOPEN = set()\t# Set of menus that are being edited.\n\t\n\t\n\tdef __init__(self, app, callback):\n\t\tself.app = app\n\t\tself.next_auto_id = 1\n\t\tself.callback = callback\n\t\tself.selected_icon = None\n\t\tself.original_id = None\n\t\tself.original_type = MenuEditor.TYPE_INTERNAL\n\t\tEditor.install_error_css()\n\t\tself.setup_widgets()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tself.builder = Gtk.Builder()\n\t\tself.builder.add_from_file(os.path.join(self.app.gladepath, self.GLADE))\n\t\tlblItemIconName  = self.builder.get_object(\"lblItemIconName\")\n\t\tvbChangeItemIcon = self.builder.get_object(\"vbChangeItemIcon\")\n\t\tself.window = self.builder.get_object(\"Dialog\")\n\t\tself.menu_icon = MenuIcon(None, True)\n\t\tvbChangeItemIcon.remove(lblItemIconName)\n\t\tvbChangeItemIcon.pack_start(self.menu_icon, False, True, 0)\n\t\tvbChangeItemIcon.pack_start(lblItemIconName, True, True, 0)\n\t\tself.builder.connect_signals(self)\n\t\theaderbar(self.builder.get_object(\"header\"))\n\t\n\t\n\tdef allow_menus(self, allow_globals, allow_in_profile):\n\t\t\"\"\"\n\t\tSets which type of menu should be selectable.\n\t\tBy default, both are enabled.\n\t\t\"\"\"\n\t\tif not allow_globals:\n\t\t\tself.builder.get_object(\"rbInProfile\").set_active(True)\n\t\t\tself.builder.get_object(\"rbGlobal\").set_sensitive(False)\n\t\t\tself.builder.get_object(\"rbInProfile\").set_sensitive(True)\n\t\telif not allow_in_profile:\n\t\t\tself.builder.get_object(\"rbGlobal\").set_active(True)\n\t\t\tself.builder.get_object(\"rbGlobal\").set_sensitive(True)\n\t\t\tself.builder.get_object(\"rbInProfile\").set_sensitive(False)\n\t\telse:\n\t\t\tself.builder.get_object(\"rbGlobal\").set_sensitive(True)\n\t\t\tself.builder.get_object(\"rbInProfile\").set_sensitive(True)\n\t\n\t\n\tdef on_action_chosen(self, id, a, mark_changed=True):\n\t\tmodel = self.builder.get_object(\"tvItems\").get_model()\n\t\tfor i in model:\n\t\t\titem = i[0].item\n\t\t\tif item.id == id:\n\t\t\t\tif isinstance(item, Separator):\n\t\t\t\t\titem.label = a.get_name()\n\t\t\t\telif isinstance(item, Submenu):\n\t\t\t\t\ti[0].item = item = Submenu(\n\t\t\t\t\t\ta.get_current_page().get_selected_menu(),\n\t\t\t\t\t\ta.get_name())\n\t\t\t\t\titem.icon = self.selected_icon\n\t\t\t\telif isinstance(item, RecentListMenuGenerator):\n\t\t\t\t\ti[0].item = item = RecentListMenuGenerator(\n\t\t\t\t\t\trows = a.get_current_page().get_row_count())\n\t\t\t\telif isinstance(item, MenuItem):\n\t\t\t\t\titem.action = a\n\t\t\t\t\titem.label = item.action.describe(Action.AC_OSD)\n\t\t\t\t\titem.icon = self.selected_icon\n\t\t\t\telse:\n\t\t\t\t\traise TypeError(\"Edited %s\" % (item.__class__.__name__))\n\t\t\t\ti[1] = item.describe()\n\t\t\t\tbreak\n\t\n\t\n\tdef on_btSave_clicked(self, *a):\n\t\t\"\"\" Handler for Save button \"\"\"\n\t\tself._remove_original()\n\t\tif self.builder.get_object(\"rbInProfile\").get_active():\n\t\t\tself._save_to_profile(self.builder.get_object(\"entName\").get_text())\n\t\telse:\n\t\t\tself._save_to_file(self.builder.get_object(\"entName\").get_text())\n\t\tself.close()\n\t\n\t\n\tdef on_tvItems_cursor_changed(self, *a):\n\t\t\"\"\"\n\t\tHandles moving cursor in Item List.\n\t\tBasically just sets Edit Item and Remove Item buttons sensitivity.\n\t\t\"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tbtEdit = self.builder.get_object(\"btEdit\")\n\t\tbtRemoveItem = self.builder.get_object(\"btRemoveItem\")\n\t\t\n\t\tmodel, iter = tvItems.get_selection().get_selected()\n\t\tif iter is None:\n\t\t\tbtRemoveItem.set_sensitive(False)\n\t\t\tbtEdit.set_sensitive(False)\n\t\telse:\n\t\t\tbtRemoveItem.set_sensitive(True)\n\t\t\to = model.get_value(iter, 0)\n\t\t\tif isinstance(o.item, (MenuItem, RecentListMenuGenerator)):\n\t\t\t\tbtEdit.set_sensitive(True)\n\t\t\telse:\n\t\t\t\tbtEdit.set_sensitive(False)\n\t\n\t\n\tdef btEdit_clicked_cb(self, *a):\n\t\t\"\"\" Handler for \"Edit Item\" button \"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tmodel, iter = tvItems.get_selection().get_selected()\n\t\titem = model.get_value(iter, 0).item\n\t\tself.selected_icon = None\n\t\t# Setup editor\n\t\te = ActionEditor(self.app, self.on_action_chosen)\n\t\tif isinstance(item, Separator):\n\t\t\te.set_title(_(\"Edit Separator\"))\n\t\t\te.hide_editor()\n\t\t\te.set_menu_item(item, _(\"Separator Name\"))\n\t\telif isinstance(item, Submenu):\n\t\t\te.set_title(_(\"Edit Submenu\"))\n\t\t\te.hide_action_str()\n\t\t\te.hide_clear()\n\t\t\t(e.force_page(e.load_component(\"menu_only\"), True)\n\t\t\t\t.allow_menus(True, False)\n\t\t\t\t.set_selected_menu(item.filename))\n\t\t\te.set_menu_item(item, _(\"Menu Label\"))\n\t\t\tself.selected_icon = item.icon\n\t\t\tself.setup_menu_icon(e)\n\t\t\tself.update_menu_icon()\n\t\telif isinstance(item, MenuItem):\n\t\t\te = ActionEditor(self.app, self.on_action_chosen)\n\t\t\te.set_title(_(\"Edit Menu Action\"))\n\t\t\te.set_input(item.id, item.action, mode = Action.AC_MENU)\n\t\t\tself.selected_icon = item.icon\n\t\t\tself.setup_menu_icon(e)\n\t\t\tself.update_menu_icon()\n\t\telif isinstance(item, RecentListMenuGenerator):\n\t\t\te.set_title(_(\"Edit Recent List\"))\n\t\t\te.hide_action_str()\n\t\t\te.hide_clear()\n\t\t\te.hide_name()\n\t\t\t(e.force_page(e.load_component(\"recent_list\"), True)\n\t\t\t\t.set_row_count(item.rows))\n\t\t\te.set_menu_item(item)\n\t\telse:\n\t\t\t# Cannot edit this\n\t\t\treturn\n\t\t# Display editor\n\t\te.show(self.window)\n\t\n\t\n\tdef _add_menuitem(self, item):\n\t\t\"\"\" Adds MenuItem or MenuGenerator object \"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tmodel = tvItems.get_model()\n\t\to = GObject.GObject()\n\t\tif not item.id:\n\t\t\titem.id = \"_auto_id_%s\" % (self.next_auto_id,)\n\t\t\tself.next_auto_id += 1\n\t\to.item = item\n\t\titer = model.append(( o, o.item.describe() ))\n\t\ttvItems.get_selection().select_iter(iter)\n\t\tself.on_tvItems_cursor_changed()\n\t\n\t\n\tdef on_btAddItem_clicked(self, *a):\n\t\t\"\"\" Handler for \"Add Action\" button and menu item \"\"\"\n\t\titem = MenuItem(None, NoAction().describe(Action.AC_OSD), NoAction())\n\t\tself._add_menuitem(item)\n\t\tself.btEdit_clicked_cb()\n\t\n\t\n\tdef on_mnuAddSeparator_clicked(self, *a):\n\t\t\"\"\" Handler for \"Add Separator\" menu item \"\"\"\n\t\tself._add_menuitem(Separator())\n\t\n\t\n\tdef on_mnuAddSubmenu_clicked(self, *a):\n\t\t\"\"\" Handler for \"Add Separator\" menu item \"\"\"\n\t\tself._add_menuitem(Submenu(\"\"))\n\t\n\t\n\tdef on_mnuAddProfList_clicked(self, *a):\n\t\t\"\"\" Handler for \"Add List of All Profiles\" menu item \"\"\"\n\t\tself._add_menuitem(ProfileListMenuGenerator())\n\t\n\t\n\tdef on_mnuAddRecentList_clicked(self, *a):\n\t\t\"\"\" Handler for \"Add List of Recent Profiles\" menu item \"\"\"\n\t\tself._add_menuitem(RecentListMenuGenerator())\n\t\n\tdef on_mnuAddGamesList_activate(self, *a):\n\t\t\"\"\" Handler for \"Add List of Games\" menu item \"\"\"\n\t\tself._add_menuitem(GameListMenuGenerator())\n\t\n\t\n\tdef on_btRemoveItem_clicked(self, *a):\n\t\t\"\"\" Handler for \"Delete Item\" button \"\"\"\n\t\ttvItems = self.builder.get_object(\"tvItems\")\n\t\tmodel, iter = tvItems.get_selection().get_selected()\n\t\tif iter is not None:\n\t\t\tmodel.remove(iter)\n\t\tself.on_tvItems_cursor_changed()\n\t\t\n\t\n\tdef on_entName_changed(self, *a):\n\t\tid = self.builder.get_object(\"entName\").get_text()\n\t\tif len(id.strip()) == 0:\n\t\t\tself._bad_id_no_id()\n\t\t\treturn\n\t\tif \".\" in id or \"/\" in id:\n\t\t\tself._bad_id_chars()\n\t\t\treturn\n\t\tif self.builder.get_object(\"rbInProfile\").get_active():\n\t\t\t# Menu stored in profile\n\t\t\tif id != self.original_id and id in self.app.current.menus:\n\t\t\t\tself._bad_id_duplicate()\n\t\t\t\treturn\n\t\telse:\n\t\t\t# Menu stored as file\n\t\t\tif id != self.original_id:\n\t\t\t\tpath = os.path.join(get_menus_path(), \"%s.menu\" % (id,))\n\t\t\t\tif os.path.exists(path):\n\t\t\t\t\tself._bad_id_duplicate()\n\t\t\t\t\treturn\n\t\tself._good_id()\n\t\treturn\n\t\n\t\n\tdef _good_id(self, *a):\n\t\tself.builder.get_object(\"rvInvalidID\").set_reveal_child(False)\n\t\tself.builder.get_object(\"btSave\").set_sensitive(True)\n\t\n\tdef _bad_id_no_id(self, *a):\n\t\tself.builder.get_object(\"btSave\").set_sensitive(False)\n\t\n\tdef _bad_id_duplicate(self, *a):\n\t\tself.builder.get_object(\"lblNope\").set_label(_('Invalid Menu ID: Menu with same ID already exists.'))\n\t\tself.builder.get_object(\"rvInvalidID\").set_reveal_child(True)\n\t\tself.builder.get_object(\"btSave\").set_sensitive(False)\n\t\n\tdef _bad_id_chars(self, *a):\n\t\tself.builder.get_object(\"lblNope\").set_label(_('Invalid Menu ID: Please, don\\'t use dots (.) or slashes (/).'))\n\t\tself.builder.get_object(\"rvInvalidID\").set_reveal_child(True)\n\t\tself.builder.get_object(\"btSave\").set_sensitive(False)\n\t\n\t\n\tdef set_new_menu(self):\n\t\t\"\"\"\n\t\tSetups editor for creating new menu.\n\t\t\"\"\"\n\t\tself.set_title(_(\"New Menu\"))\n\t\trbInProfile = self.builder.get_object(\"rbInProfile\")\n\t\tentName = self.builder.get_object(\"entName\")\n\t\t\n\t\trbInProfile.set_active(True)\n\t\tself.original_id = None\n\t\tself.original_type = MenuEditor.TYPE_INTERNAL\n\t\tentName.set_text(\"\")\n\t\n\t\n\t@staticmethod\n\tdef menu_is_global(id):\n\t\treturn \".\" in id\n\t\n\t\n\tdef set_menu(self, id):\n\t\t\"\"\"\n\t\tSetups editor for menu with specified ID.\n\t\tID may be id of menu in profile or, if it contains \".\", filename.\n\t\t\"\"\"\n\t\tself.set_title(_(\"Menu Editor\"))\n\t\trbGlobal = self.builder.get_object(\"rbGlobal\")\n\t\trbInProfile = self.builder.get_object(\"rbInProfile\")\n\t\tentName = self.builder.get_object(\"entName\")\n\t\t\n\t\tMenuEditor.OPEN.add(id)\n\t\tif MenuEditor.menu_is_global(id):\n\t\t\tid = id.split(\".\")[0]\n\t\t\trbGlobal.set_active(True)\n\t\t\tself.original_type = MenuEditor.TYPE_GLOBAL\n\t\t\titems = self._load_items_from_file(id)\n\t\telse:\n\t\t\trbInProfile.set_active(True)\n\t\t\tself.original_type = MenuEditor.TYPE_INTERNAL\n\t\t\titems = self._load_items_from_profile(id)\n\t\tself.original_id = id\n\t\tentName.set_text(id)\n\t\t\n\t\tmodel = self.builder.get_object(\"tvItems\").get_model()\n\t\tmodel.clear()\n\t\tif items is None:\n\t\t\tself.set_new_menu()\n\t\telse:\n\t\t\tfor i in items:\n\t\t\t\tself._add_menuitem(i)\n\t\n\t\n\tdef on_Dialog_delete_event(self, *a):\n\t\ttry:\n\t\t\tif self.original_type == MenuEditor.TYPE_GLOBAL:\n\t\t\t\tMenuEditor.OPEN.remove(self.original_id + \".menu\")\n\t\t\telse:\n\t\t\t\tMenuEditor.OPEN.remove(self.original_id)\n\t\texcept KeyError: pass\n\t\treturn False\n\t\n\t\n\tdef close(self, *a):\n\t\tself.on_Dialog_delete_event()\n\t\tEditor.close(self)\n\t\n\t\n\tdef _load_items_from_file(self, id):\n\t\tfor p in (get_menus_path(), get_default_menus_path()):\n\t\t\tpath = os.path.join(p, \"%s.menu\" % (id,))\n\t\t\tif os.path.exists(path):\n\t\t\t\treturn MenuData.from_file(path, TalkingActionParser())\n\t\t# Menu file not found\n\t\treturn None\n\t\n\t\n\tdef _load_items_from_profile(self, id):\n\t\ttry:\n\t\t\treturn self.app.current.menus[id]\n\t\texcept KeyError:\n\t\t\t# Menu not found\n\t\t\treturn None\n\t\n\t\n\tdef _remove_original(self):\n\t\tif self.original_id is None:\n\t\t\t# Created new menu\n\t\t\tpass\n\t\telif self.original_type == MenuEditor.TYPE_INTERNAL:\n\t\t\ttry:\n\t\t\t\tdel self.app.current.menus[self.original_id]\n\t\t\texcept: pass\n\t\telif self.original_type == MenuEditor.TYPE_GLOBAL:\n\t\t\ttry:\n\t\t\t\tpath = os.path.join(get_menus_path(), \"%s.menu\" % (self.original_id,))\n\t\t\t\tlog.debug(\"Removing %s\", path)\n\t\t\t\tos.unlink(path)\n\t\t\texcept: pass\n\t\n\t\n\tdef _generate_menudata(self):\n\t\t\"\"\"\n\t\tGenerates MenuData instance from items in list\n\t\t\"\"\"\n\t\tmodel = self.builder.get_object(\"tvItems\").get_model()\n\t\tdata = MenuData(*[ i[0].item for i in model ])\n\t\ti = 1\n\t\tfor item in data:\n\t\t\titem.id = \"item%s\" % (i,)\n\t\t\ti += 1\n\t\treturn data\n\t\t\n\t\n\tdef _save_to_profile(self, id):\n\t\t\"\"\"\n\t\tStores menu in loaded profile. Doesn't actually save anything, that's\n\t\tfor main app window thing.\n\t\t\"\"\"\n\t\tself.app.current.menus[id] = self._generate_menudata()\n\t\tlog.debug(\"Stored menu ID %s\", id)\n\t\tself.app.on_profile_modified()\n\t\tif self.callback:\n\t\t\tself.callback(id)\n\t\n\t\n\tdef _save_to_file(self, id):\n\t\t\"\"\"\n\t\tStores menu in json file\n\t\t\"\"\"\n\t\tid = \"%s.menu\" % (id,)\n\t\tpath = os.path.join(get_menus_path(), id)\n\t\tdata = self._generate_menudata()\n\t\tjstr = Encoder(sort_keys=True, indent=4).encode(data)\n\t\topen(path, \"w\").write(jstr)\n\t\tlog.debug(\"Wrote menu file %s\", path)\n\t\tif self.callback:\n\t\t\tself.callback(id)\n\t\n\t\n\tdef setup_menu_icon(self, editor):\n\t\tcontainer = self.builder.get_object(\"menu_icon\")\n\t\teditor.add_widget(_(\"Icon\"), container)\n\t\n\t\n\tdef update_menu_icon(self):\n\t\tlblItemIconName = self.builder.get_object(\"lblItemIconName\")\n\t\tif self.selected_icon is None:\n\t\t\tlblItemIconName.set_label(_(\"(no icon)\"))\n\t\t\tself.menu_icon.set_visible(False)\n\t\telse:\n\t\t\tlblItemIconName.set_label(self.selected_icon)\n\t\t\ttry:\n\t\t\t\tfilename, trash = find_icon(self.selected_icon)\n\t\t\t\tself.menu_icon.set_filename(filename)\n\t\t\t\tself.menu_icon.set_visible(True)\n\t\t\texcept Exception as e:\n\t\t\t\tlog.error(e)\n\t\t\t\tlog.error(traceback.format_exc())\n\t\t\t\tself.menu_icon.set_visible(False)\n\t\n\t\n\tdef on_icon_choosen(self, name):\n\t\tself.selected_icon = name\n\t\tself.update_menu_icon()\n\t\n\t\n\tdef on_btChangeItemIcon_clicked(self, *a):\n\t\tc = IconChooser(self.app, self.on_icon_choosen)\n\t\tc.show(self.window)\n\t\n\t\n\tdef on_btClearItemIcon_clicked(self, *a):\n\t\tself.selected_icon = None\n\t\tself.update_menu_icon()\n"
  },
  {
    "path": "scc/gui/modeshift_editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor\n\nAllows to edit button or trigger action.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.gui.controller_widget import ControllerButton\nfrom scc.gui.controller_widget import STICKS, PADS\nfrom scc.gui.dwsnc import headerbar\nfrom scc.gui.editor import Editor\nfrom scc.actions import Action, NoAction, RangeOP, RingAction\nfrom scc.constants import SCButtons, HapticPos, TRIGGER_MAX\nfrom scc.modifiers import ModeModifier, DoubleclickModifier\nfrom scc.modifiers import FeedbackModifier, HoldModifier\nfrom scc.profile import Profile\nfrom scc.macros import Macro\nfrom scc.tools import nameof\n\nfrom gi.repository import Gtk, Gdk, GLib\nimport os, logging\nlog = logging.getLogger(\"ModeshiftEditor\")\n\n\nclass ModeshiftEditor(Editor):\n\tGLADE = \"modeshift_editor.glade\"\n\tBUTTONS = (\t# in order as displayed in combobox\n\t\t(SCButtons.A,\t\t\t_('A') ),\n\t\t(SCButtons.B,\t\t\t_('B') ),\n\t\t(SCButtons.X,\t\t\t_('X') ),\n\t\t(SCButtons.Y,\t\t\t_('Y') ),\n\t\t(None, None),\n\t\t(SCButtons.BACK,\t\t_('Back (select)') ),\n\t\t(SCButtons.C,\t\t\t_('Center') ),\n\t\t(SCButtons.START,\t\t_('Start') ),\n\t\t(None, None),\n\t\t(SCButtons.LGRIP,\t\t_('Left Grip') ),\n\t\t(SCButtons.RGRIP,\t\t_('Right Grip') ),\n\t\t(SCButtons.LB,\t\t\t_('Left Bumper') ),\n\t\t(SCButtons.RB,\t\t\t_('Right Bumper') ),\n\t\t(None, None),\n\t\t(SCButtons.LT,\t\t\t_('Left Trigger (full)') ),\n\t\t(SCButtons.RT,\t\t\t_('Right Trigger (full)') ),\n\t\t(\"Soft LT\",\t\t\t\t_('Left Trigger (soft)') ),\n\t\t(\"Soft RT\",\t\t\t\t_('Right Trigger (soft)') ),\n\t\t(None, None),\n\t\t(SCButtons.STICKPRESS,\t_('Stick Pressed') ),\n\t\t(SCButtons.LPAD,\t\t_('Left Pad Pressed') ),\n\t\t(SCButtons.RPAD,\t\t_('Right Pad Pressed') ),\n\t\t(SCButtons.LPADTOUCH,\t_('Left Pad Touched') ),\n\t\t(SCButtons.RPADTOUCH,\t_('Right Pad Touched') ),\n\t)\n\t\n\tdef __init__(self, app, callback):\n\t\tEditor.__init__(self)\n\t\tself.app = app\n\t\tself.id = None\n\t\tself.mode = Action.AC_BUTTON\n\t\tself.ac_callback = callback\n\t\tself.soft_level = -1\n\t\tself.current_page = 0\n\t\tself.actions = ( [], [], [] )\n\t\tself.nomods = [ NoAction(), NoAction(), NoAction() ]\n\t\tself.setup_widgets()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\t\n\t\tcbButtonChooser = self.builder.get_object(\"cbButtonChooser\")\n\t\tcbButtonChooser.set_row_separator_func( lambda model, iter : model.get_value(iter, 0) is None )\n\t\t\n\t\tb = lambda a : self.builder.get_object(a)\n\t\tself.action_widgets = (\n\t\t\t# Order goes: Grid, 1st Action Button, Clear Button\n\t\t\t# 1st group, 'pressed'\n\t\t\t( b('grActions'),\t\tb('btDefault'),\t\tb('btClearDefault') ),\n\t\t\t# 2nd group, 'hold'\n\t\t\t( b('grHold'),\t\t\tb('btHold'),\t\tb('btClearHold') ),\n\t\t\t# 2nd group, 'double-click'\n\t\t\t( b('grDoubleClick'),\tb('btDoubleClick'),\tb('btClearDoubleClick') ),\n\t\t)\n\t\t\n\t\theaderbar(self.builder.get_object(\"header\"))\n\t\n\t\n\tdef on_Dialog_destroy(self, *a):\n\t\tself.remove_added_widget()\n\t\n\t\n\tdef _fill_button_chooser(self, *a):\n\t\tcbButtonChooser = self.builder.get_object(\"cbButtonChooser\")\n\t\tmodel = cbButtonChooser.get_model()\n\t\tmodel.clear()\n\t\tfor item, text in self.BUTTONS:\n\t\t\tif any([ x[0] == item for x in self.actions[self.current_page] ]):\n\t\t\t\t# Skip already added buttons\n\t\t\t\tcontinue\n\t\t\tif type(item) is str:\n\t\t\t\t# Special case for soft pull items\n\t\t\t\tbutton = getattr(SCButtons, item.split(\" \")[-1])\n\t\t\t\tif any([ (isinstance(x, RangeOP) and x.what == button)\n\t\t\t\t\t\t\tfor x in self.actions[self.current_page] ]):\n\t\t\t\t\t# Skip already added soft pulls\n\t\t\t\t\tcontinue\n\t\t\t\n\t\t\tif item == SCButtons.STICKPRESS:\n\t\t\t\tif self.id == nameof(SCButtons.LPAD):\n\t\t\t\t\t# Controller cannot handle pressing stick and lpad at once\n\t\t\t\t\tcontinue\n\t\t\tmodel.append(( None if item is None else nameof(item), text ))\n\t\tcbButtonChooser.set_active(0)\n\t\n\t\n\tdef _add_action(self, index, what, action):\n\t\tcbButtonChooser = self.builder.get_object(\"cbButtonChooser\")\n\t\tadjSoftLevel = self.builder.get_object(\"adjSoftLevel\")\n\t\trvSoftLevel = self.builder.get_object(\"rvSoftLevel\")\n\t\tgrActions = self.action_widgets[index][0]\n\t\tmodel = cbButtonChooser.get_model()\n\t\t\n\t\tfor row in model:\n\t\t\titem = model.get_value(row.iter, 0)\n\t\t\tif item:\n\t\t\t\tif item == nameof(what):\n\t\t\t\t\tmodel.remove(row.iter)\n\t\t\t\t\tbreak\n\t\t\t\tif isinstance(what, RangeOP) and item.startswith(\"Soft\"):\n\t\t\t\t\tif nameof(what.what) == item.split(\" \")[-1]:\n\t\t\t\t\t\tmodel.remove(row.iter)\n\t\t\t\t\t\tbreak\n\t\ttry:\n\t\t\twhile model.get_value(model[0].iter, 0) is None:\n\t\t\t\tmodel.remove(model[0].iter)\n\t\t\tcbButtonChooser.set_active(0)\n\t\texcept: pass\n\t\t\n\t\ti = len(self.actions[index]) + 1\n\t\tl = Gtk.Label()\n\t\tif isinstance(what, RangeOP) and what.op == \">=\":\n\t\t\tif self.soft_level == -1:\n\t\t\t\t# First Range added, load level from it\n\t\t\t\tself.soft_level = what.value if what.value > 0.0 else 0.75\n\t\t\t\tadjSoftLevel.set_value(self.soft_level)\n\t\t\t\trvSoftLevel.set_reveal_child(True)\n\t\t\t\twhat.value = -1\n\t\t\tif what.value == -1:\n\t\t\t\t# Range with value taken from \"Soft Pull Level\" slider\n\t\t\t\tl.set_markup(\"<b>%s (soft pull)</b>\" % (nameof(what.what),))\n\t\t\telse:\n\t\t\t\t# Any other range\n\t\t\t\tl.set_markup(\"<b>%s</b>\" % (nameof(what),))\n\t\telse:\n\t\t\tl.set_markup(\"<b>%s</b>\" % (nameof(what),))\n\t\tl.set_xalign(0.0)\n\t\tb = Gtk.Button.new_with_label(action.describe(self.mode))\n\t\tb.set_property(\"hexpand\", True)\n\t\tb.connect('clicked', self.on_actionb_clicked, index, what)\n\t\tclearb = Gtk.Button()\n\t\tclearb.set_image(Gtk.Image.new_from_stock(\"gtk-delete\", Gtk.IconSize.SMALL_TOOLBAR))\n\t\tclearb.set_relief(Gtk.ReliefStyle.NONE)\n\t\tclearb.connect('clicked', self.on_clearb_clicked, index, what)\n\t\tgrActions.attach(l,\t\t\t0, i, 1, 1)\n\t\tgrActions.attach(b,\t\t\t1, i, 1, 1)\n\t\tgrActions.attach(clearb,\t2, i, 1, 1)\n\t\t\n\t\tself.actions[index].append([ what, action, l, b, clearb ])\n\t\tgrActions.show_all()\n\t\n\t\n\tdef on_clearb_clicked(self, trash, index, button):\n\t\tgrActions = self.action_widgets[index][0]\n\t\tcbButtonChooser = self.builder.get_object(\"cbButtonChooser\")\n\t\tmodel = cbButtonChooser.get_model()\n\t\t# Remove requested action from the list\n\t\tfor i in range(0, len(self.actions[index])):\n\t\t\tif self.actions[index][i][0] == button:\n\t\t\t\tbutton, action, l, b, clearb = self.actions[index][i]\n\t\t\t\tfor w in (l, b, clearb): grActions.remove(w)\n\t\t\t\tdel self.actions[index][i]\n\t\t\t\tbreak\n\t\t# Move everything after that action one position up\n\t\t# - remove it\n\t\tfor j in range(i, len(self.actions[index])):\n\t\t\tbutton, action, l, b, clearb = self.actions[index][j]\n\t\t\tfor w in (l, b, clearb): grActions.remove(w)\n\t\t# - add it again\n\t\tfor j in range(i, len(self.actions[index])):\n\t\t\tbutton, action, l, b, clearb = self.actions[index][j]\n\t\t\tgrActions.attach(l,\t\t\t0, j + 1, 1, 1)\n\t\t\tgrActions.attach(b,\t\t\t1, j + 1, 1, 1)\n\t\t\tgrActions.attach(clearb,\t2, j + 1, 1, 1)\n\t\t# Regenereate combobox with removed button added back to it\n\t\t# - Store acive item from in combobox\n\t\tactive, i, index = None, 0, -1\n\t\ttry:\n\t\t\tactive = model.get_value(cbButtonChooser.get_active_iter(), 0)\n\t\texcept: pass\n\t\t# Clear entire combobox\n\t\tmodel.clear()\n\t\t# Fill it again\n\t\tfor button, text in self.BUTTONS:\n\t\t\tmodel.append(( None if button is None else nameof(button), text ))\n\t\t\tif button is not None:\n\t\t\t\tif nameof(button) == active:\n\t\t\t\t\tindex = i\n\t\t\ti += 1\n\t\t# Reselect formely active item\n\t\tif index >= 0:\n\t\t\tcbButtonChooser.set_active(index)\n\t\n\t\n\tdef _choose_editor(self, action, cb):\n\t\tfrom scc.gui.ring_editor import RingEditor\t# Cannot be imported @ top\n\t\tif isinstance(action, Macro):\n\t\t\tfrom scc.gui.macro_editor import MacroEditor\t# Cannot be imported @ top\n\t\t\te = MacroEditor(self.app, cb)\n\t\t\te.set_title(_(\"Edit Macro\"))\n\t\telif RingEditor.is_ring_action(action):\n\t\t\te = RingEditor(self.app, cb)\n\t\telse:\n\t\t\tfrom scc.gui.action_editor import ActionEditor\t# Cannot be imported @ top\n\t\t\te = ActionEditor(self.app, cb)\n\t\t\te.set_title(_(\"Edit Action\"))\n\t\t\te.hide_modeshift()\n\t\treturn e\n\t\n\t\n\tdef on_actionb_clicked(self, trash, index, clicked_button):\n\t\tfor i in self.actions[index]:\n\t\t\tbutton, action, l, b, clearb = i\n\t\t\tif button == clicked_button:\n\t\t\t\tdef on_chosen(id, action):\n\t\t\t\t\tb.set_label(action.describe(self.mode))\n\t\t\t\t\ti[1] = action\n\t\t\t\t\n\t\t\t\tae = self._choose_editor(action, on_chosen)\n\t\t\t\tae.set_input(self.id, action, mode = self.mode)\n\t\t\t\tae.show(self.window)\n\t\t\t\treturn\n\t\n\t\n\tdef on_ntbMore_switch_page(self, ntb, box, index):\n\t\tself.current_page = index\n\t\tself._fill_button_chooser()\n\t\tself.builder.get_object(\"cbButtonChooser\").set_sensitive(box.get_sensitive())\n\t\tself.builder.get_object(\"btAddAction\").set_sensitive(box.get_sensitive())\n\t\n\t\n\tdef on_nomodbt_clicked(self, button, *a):\n\t\tactionButton = self.action_widgets[self.current_page][1]\n\t\t\n\t\tdef on_chosen(id, action):\n\t\t\tactionButton.set_label(action.describe(self.mode))\n\t\t\tself.nomods[self.current_page] = action\n\t\t\n\t\tae = self._choose_editor(self.nomods[self.current_page], on_chosen)\n\t\tae.set_input(self.id, self.nomods[self.current_page], mode = self.mode)\n\t\tae.show(self.window)\n\t\n\t\n\tdef on_nomodclear_clicked(self, button, *a):\n\t\tself.nomods[self.current_page] = NoAction()\n\t\tactionButton = self.action_widgets[self.current_page][1]\n\t\tactionButton.set_label(self.nomods[self.current_page].describe(self.mode))\n\t\n\t\n\tdef on_btAddAction_clicked(self, *a):\n\t\tcbButtonChooser = self.builder.get_object(\"cbButtonChooser\")\n\t\titem = cbButtonChooser.get_model().get_value(cbButtonChooser.get_active_iter(), 0)\n\t\tif item.startswith(\"Soft\"):\n\t\t\tb = getattr(SCButtons, item.split(\" \")[-1])\n\t\t\trng = RangeOP(b, \">=\", -1)\n\t\t\tself._add_action(self.current_page, rng, NoAction())\n\t\telse:\n\t\t\tb = getattr(SCButtons, item)\n\t\t\tself._add_action(self.current_page, b, NoAction())\n\t\n\t\n\tdef on_sclSoftLevel_format_value(self, scale, value):\n\t\treturn  \"%s%%\" % (int(value * 100.0),)\n\t\n\t\n\tdef on_btClear_clicked(self, *a):\n\t\t\"\"\" Handler for clear button \"\"\"\n\t\taction = NoAction()\n\t\tif self.ac_callback is not None:\n\t\t\tself.ac_callback(self.id, action)\n\t\tself.close()\n\t\n\t\n\tdef on_btCustomActionEditor_clicked(self, *a):\n\t\t\"\"\" Handler for 'Custom Editor' button \"\"\"\n\t\tfrom scc.gui.action_editor import ActionEditor\t# Can't be imported on top\n\t\te = ActionEditor(self.app, self.ac_callback)\n\t\te.set_input(self.id, self._make_action(), mode = self.mode)\n\t\te.hide_action_buttons()\n\t\te.hide_advanced_settings()\n\t\te.set_title(self.window.get_title())\n\t\te.force_page(e.load_component(\"custom\"), True)\n\t\tself.send_added_widget(e)\n\t\tself.close()\n\t\te.show(self.get_transient_for())\n\t\n\t\n\tdef on_cbHoldFeedback_toggled(self, cb, *a):\n\t\trvHoldFeedbackAmplitude = self.builder.get_object(\"rvHoldFeedbackAmplitude\")\n\t\trvHoldFeedbackAmplitude.set_reveal_child(cb.get_active())\n\t\n\t\n\tdef on_btOK_clicked(self, *a):\n\t\t\"\"\" Handler for OK button \"\"\"\n\t\tif self.ac_callback is not None:\n\t\t\tself.ac_callback(self.id, self._make_action())\n\t\tself.close()\n\t\n\t\n\tdef _make_action(self):\n\t\t\"\"\" Generates and returns Action instance \"\"\"\n\t\tcbHoldFeedback = self.builder.get_object(\"cbHoldFeedback\")\n\t\tsclHoldFeedback = self.builder.get_object(\"sclHoldFeedback\")\n\t\tnormalaction = self._save_modemod(0)\n\t\tholdaction = self._save_modemod(1)\n\t\tdblaction = self._save_modemod(2)\n\t\tif dblaction:\n\t\t\taction = DoubleclickModifier(dblaction, normalaction)\n\t\t\taction.holdaction = holdaction\n\t\telif holdaction:\n\t\t\taction = HoldModifier(holdaction, normalaction)\n\t\telse:\n\t\t\taction = normalaction\n\t\taction.timeout = self.builder.get_object(\"adjTime\").get_value()\n\t\t\n\t\tif cbHoldFeedback.get_active():\n\t\t\taction = FeedbackModifier(HapticPos.BOTH,\n\t\t\t\t\t\tsclHoldFeedback.get_value(), action)\n\t\t\n\t\treturn action\n\t\n\t\n\tdef _save_modemod(self, index):\n\t\t\"\"\" Generates ModeModifier from page in Notebook \"\"\"\n\t\tadjSoftLevel = self.builder.get_object(\"adjSoftLevel\")\n\t\tpars = []\n\t\tfor item, action, l, b, clearb in self.actions[index]:\n\t\t\tif isinstance(item, RangeOP) and item.value == -1:\n\t\t\t\t# Value taken from slider\n\t\t\t\titem.value = adjSoftLevel.get_value()\n\t\t\tpars += [ item, action ]\n\t\tif self.nomods[index]:\n\t\t\tpars += [ self.nomods[index] ]\n\t\taction = ModeModifier(*pars)\n\t\tif len(pars) == 0:\n\t\t\t# No action is actually set\n\t\t\taction = NoAction()\n\t\telif len(pars) == 1:\n\t\t\t# Only default action left\n\t\t\taction = self.nomods[index]\n\t\treturn action\n\t\n\t\n\tdef _load_modemod(self, index, action):\n\t\tfor key in action.mods:\n\t\t\tself._add_action(index, key, action.mods[key])\n\t\n\t\n\tdef _set_nomod_button(self, index, action):\n\t\tif isinstance(action, ModeModifier):\n\t\t\tself._load_modemod(index, action)\n\t\t\tself.nomods[index] = action.default\n\t\telse:\n\t\t\tself.nomods[index] = action\n\t\tactionButton = self.action_widgets[index][1]\n\t\tactionButton.set_label(self.nomods[index].describe(self.mode))\n\t\n\t\n\tdef allow_first_page(self):\n\t\t\"\"\" For compatibility with action editor. Does nothing \"\"\"\n\t\tpass\n\t\n\t\n\tdef set_input(self, id, action, mode=None):\n\t\tbtDefault = self.builder.get_object(\"btDefault\")\n\t\tlblPressAlone = self.builder.get_object(\"lblPressAlone\")\n\t\tcbHoldFeedback = self.builder.get_object(\"cbHoldFeedback\")\n\t\tsclHoldFeedback = self.builder.get_object(\"sclHoldFeedback\")\n\t\t\n\t\tself.id = id\n\t\tself._fill_button_chooser()\n\t\t\n\t\tif id in STICKS:\n\t\t\tlblPressAlone.set_label(_(\"(no button pressed)\"))\n\t\t\tself.mode = mode = mode or Action.AC_STICK\n\t\telif id in PADS:\n\t\t\tlblPressAlone.set_label(_(\"(no button pressed)\"))\n\t\t\tself.mode = mode = mode or Action.AC_PAD\n\t\telse:\n\t\t\tlblPressAlone.set_label(_(\"(pressed alone)\"))\n\t\t\tself.mode = mode = mode or Action.AC_BUTTON\n\t\t\n\t\tself.set_title(\"Modeshift for %s\" % (nameof(id) if id in SCButtons.__members__.values() else str(id),))\n\t\t\n\t\tif isinstance(action, FeedbackModifier):\n\t\t\tcbHoldFeedback.set_active(True)\n\t\t\tsclHoldFeedback.set_value(action.haptic.get_amplitude())\n\t\t\taction = action.action\n\t\telse:\n\t\t\tcbHoldFeedback.set_active(False)\n\t\t\tsclHoldFeedback.set_value(512)\n\t\t\n\t\tif isinstance(action, ModeModifier):\n\t\t\tself._load_modemod(0, action)\n\t\t\tself._set_nomod_button(0, action.default)\n\t\t\tself._set_nomod_button(1, NoAction())\n\t\t\tself._set_nomod_button(2, NoAction())\n\t\telif isinstance(action, DoubleclickModifier):\t# includes HoldModifier\n\t\t\tself._set_nomod_button(0, action.normalaction)\n\t\t\tself._set_nomod_button(1, action.holdaction)\n\t\t\tself._set_nomod_button(2, action.action)\n\t\tself.builder.get_object(\"adjTime\").set_value(action.timeout)\n\t\t\n\t\tif mode == Action.AC_OSK:\n\t\t\t# This is kinda bad, but allowing Custom Editor\n\t\t\t# from OSK editor is in TODO\n\t\t\tself.builder.get_object(\"btCustomActionEditor\").set_visible(False)\n\t\tif mode != Action.AC_BUTTON:\n\t\t\tfor w in (\"vbHold\", \"vbDoubleClick\", \"lblHold\", \"lblDoubleClick\"):\n\t\t\t\tself.builder.get_object(w).set_sensitive(False)\n"
  },
  {
    "path": "scc/gui/osd_mode.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Mode Mapper\n\nVery special case of mapper used when main application is launched in \"odd mode\".\nThat means it's drawn in OSD layer, cannot be clicked and cannot react to\nkeyboard. This mapper emulates input events on it using GTK methods.\n\nMouse movement (but not buttons) are passed to uinput as usuall.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom gi.repository import Gtk, Gdk, GLib\n\nfrom scc.gui.gdk_to_key import KEY_TO_GDK, KEY_TO_KEYCODE\nfrom scc.gui.daemon_manager import ControllerManager\nfrom scc.osd.slave_mapper import SlaveMapper\nfrom scc.constants import SCButtons\nfrom scc.uinput import Keys, Scans\n\nimport os, logging\nlog = logging.getLogger(\"OSDModMapper\")\n\n\nclass OSDModeMapper(SlaveMapper):\n\tdef __init__(self, app, profile):\n\t\tSlaveMapper.__init__(self, profile, None, keyboard=\"osd\", mouse=\"osd\")\n\t\tself.app = app\n\t\tself.set_special_actions_handler(self)\n\t\tself.target_window = None\n\t\n\tdef on_sa_restart(self, *a):\n\t\t\"\"\" restart / exit handler \"\"\"\n\t\tself.app.quit()\n\t\n\t\n\tdef set_target_window(self, w):\n\t\tself.target_window = w\n\t\n\t\n\tdef create_keyboard(self, name):\n\t\treturn OSDModeKeyboard(self)\n\t\n\t\n\tdef create_mouse(self, name):\n\t\treturn OSDModeMouse(self)\n\n\nclass OSDModeKeyboard(object):\n\t\"\"\" Emulates uinput keyboard emulator \"\"\"\n\t\n\tdef __init__(self, mapper):\n\t\tself.mapper = mapper\n\t\tself.display = Gdk.Display.get_default()\n\t\tself.manager = self.display.get_device_manager()\n\t\tself.device = [ x for x in\n\t\t\tself.manager.list_devices(Gdk.DeviceType.MASTER)\n\t\t\tif x.get_source() == Gdk.InputSource.KEYBOARD\n\t\t][0]\t\n\t\n\tdef pressEvent(self, keys):\n\t\tfor k in keys:\n\t\t\tevent = Gdk.Event.new(Gdk.EventType.KEY_PRESS)\n\t\t\tevent.time = Gtk.get_current_event_time()\n\t\t\tevent.hardware_keycode = KEY_TO_KEYCODE[k]\n\t\t\tevent.keyval = KEY_TO_GDK[k]\n\t\t\tevent.window = self.mapper.target_window\n\t\t\tevent.set_device(self.device)\n\t\t\tGtk.main_do_event(event)\n\t\n\t\n\tdef releaseEvent(self, keys=[]):\n\t\tfor k in keys:\n\t\t\tevent = Gdk.Event.new(Gdk.EventType.KEY_RELEASE)\n\t\t\tevent.time = Gtk.get_current_event_time()\n\t\t\tevent.hardware_keycode = KEY_TO_KEYCODE[k]\n\t\t\tevent.keyval = KEY_TO_GDK[k]\n\t\t\tevent.window = self.mapper.target_window\n\t\t\tevent.set_device(self.device)\n\t\t\tGtk.main_do_event(event)\n\n\nclass OSDModeMouse(object):\n\t\"\"\" Emulates uinput keyboard emulator too \"\"\"\n\t\n\tdef __init__(self, mapper):\n\t\tself.mapper = mapper\n\t\tself.display = Gdk.Display.get_default()\n\t\tself.manager = self.display.get_device_manager()\n\t\tself.device = [ x for x in\n\t\t\tself.manager.list_devices(Gdk.DeviceType.MASTER)\n\t\t\tif x.get_source() == Gdk.InputSource.MOUSE\n\t\t][0]\n\t\n\t\n\tdef synEvent(self, *a):\n\t\tpass\n\t\n\t\n\tdef keyEvent(self, key, val):\n\t\ttp = Gdk.EventType.BUTTON_PRESS if val else Gdk.EventType.BUTTON_RELEASE\n\t\tevent = Gdk.Event.new(tp)\n\t\tevent.button = int(key) - Keys.BTN_LEFT + 1\n\t\twindow, event.x, event.y = Gdk.Window.at_pointer()\n\t\tscreen, x, y, mask = Gdk.Display.get_default().get_pointer()\n\t\tevent.x_root, event.y_root = x, y\n\t\t\n\t\tgtk_window = None\n\t\tfor w in Gtk.Window.list_toplevels():\n\t\t\tif w.get_window():\n\t\t\t\tif window.get_toplevel().get_xid() == w.get_window().get_xid():\n\t\t\t\t\tgtk_window = w\n\t\t\t\t\tbreak\n\t\tif gtk_window:\n\t\t\tif gtk_window.get_type_hint() == Gdk.WindowTypeHint.COMBO:\n\t\t\t\t# Special case, clicking on combo does nothing, so\n\t\t\t\t# pressing \"space\" is emulated instead.\n\t\t\t\tif not val:\n\t\t\t\t\treturn\n\t\t\t\tevent = Gdk.Event.new(Gdk.EventType.KEY_PRESS)\n\t\t\t\tevent.time = Gtk.get_current_event_time()\n\t\t\t\tevent.hardware_keycode = 65\n\t\t\t\tevent.keyval = Gdk.KEY_space\n\t\t\t\tevent.window = self.mapper.target_window\n\t\tevent.time = Gtk.get_current_event_time()\n\t\tevent.window = window\n\t\tevent.set_device(self.device)\n\t\tGtk.main_do_event(event)\n\n\nclass OSDModeMappings(object):\n\t\n\tICONS = {\n\t\t'imgOsdmodeAct'    : SCButtons.A,\n\t\t'imgOsdmodeClose'  : SCButtons.B,\n\t\t'imgOsdmodeExit'   : SCButtons.C,\n\t\t'imgOsdmodeSave'   : SCButtons.Y,\n\t\t'imgOsdmodeOK'     : SCButtons.Y,\n\t}\n\t\n\tMAIN_WINDOW_BUTTONS = { \"vbOsdmodeExit\", \"vbOsdmodeSave\" }\n\tOTHER_WINDOW_BUTTONS = { \"vbOsdmodeExit\", \"vbOsdmodeAct\", \"vbOsdmodeClose\", \"vbOsdmodeOK\" }\n\t\n\tdef __init__(self, app, mapper, window):\n\t\tself.app = app\n\t\tself.mapper = mapper\n\t\tself.window = window\n\t\tself.parent = app.window\n\t\tself.first_window = None\n\t\tGLib.timeout_add(10, self.move_around)\n\t\tself.app.window.connect(\"focus-in-event\", self.on_main_window_focus_in_event)\n\t\tself.app.window.connect(\"focus-out-event\", self.on_main_window_focus_out_event)\n\t\tself.on_main_window_focus_in_event()\n\t\n\t\n\tdef set_controller(self, c):\n\t\tconfig = c.load_gui_config(self.app.imagepath or {})\n\t\tfor name in OSDModeMappings.ICONS:\n\t\t\tw = self.app.builder.get_object(name)\n\t\t\ticon, trash = c.get_button_icon(config, OSDModeMappings.ICONS[name])\n\t\t\tw.set_from_file(icon)\n\t\n\t\n\tdef on_main_window_focus_in_event(self, *a):\n\t\tfor x in self.OTHER_WINDOW_BUTTONS:\n\t\t\tself.app.builder.get_object(x).set_visible(False)\n\t\tfor x in self.MAIN_WINDOW_BUTTONS:\n\t\t\tself.app.builder.get_object(x).set_visible(True)\n\t\n\t\n\tdef on_main_window_focus_out_event(self, *a):\n\t\tfor x in self.MAIN_WINDOW_BUTTONS:\n\t\t\tself.app.builder.get_object(x).set_visible(False)\n\t\tfor x in self.OTHER_WINDOW_BUTTONS:\n\t\t\tself.app.builder.get_object(x).set_visible(True)\n\t\n\t\n\tdef get_target_position(self):\n\t\tpos = self.first_window.get_position()\n\t\tsize = self.first_window.get_geometry()\n\t\tmy_size = self.window.get_window().get_geometry()\n\t\ttx = (pos.x + 0.5 * (size.width - my_size.width))\n\t\tty = pos.y + size.height + 100\n\t\treturn tx, ty\n\t\n\t\n\tdef show(self):\n\t\tself.window.show()\n\t\tself.window.get_window().set_override_redirect(True)\n\t\n\t\n\tdef move_around(self, *a):\n\t\tif self.first_window is None:\n\t\t\tactive = self.window.get_window().get_screen().get_active_window()\n\t\t\tif active is None:\n\t\t\t\treturn\n\t\t\telse:\n\t\t\t\tself.first_window = active\n\t\t\n\t\ttx, ty = self.get_target_position()\n\t\tself.window.get_window().move(tx, ty)\n\t\treturn True\n\n\ndef direction(x):\n\tif x >= 1:\n\t\treturn 1\n\telif x <= -1:\n\t\treturn -1\n\treturn 0\n"
  },
  {
    "path": "scc/gui/osk_binding_editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - On Screen Keyboard Binding Editor\n\nEdits '.scc-osd.keyboard.sccprofile', profile used by on screen keyboard\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gdk\nfrom scc.constants import SCButtons, STICK\nfrom scc.paths import get_profiles_path\nfrom scc.tools import find_profile\nfrom scc.profile import Profile\nfrom scc.actions import Action\nfrom scc.gui.binding_editor import BindingEditor\nfrom scc.gui.controller_widget import TRIGGERS, STICKS\nfrom scc.gui.parser import GuiActionParser\nfrom scc.gui.editor import Editor\nfrom scc.osd.keyboard import Keyboard as OSDKeyboard\n\nimport os, logging\nlog = logging.getLogger(\"OSKEdit\")\n\n\nclass OSKBindingEditor(Editor, BindingEditor):\n\tGLADE = \"osk_binding_editor.glade\"\n\t\n\tdef __init__(self, app):\n\t\tBindingEditor.__init__(self, app)\n\t\tself.app = app\n\t\tself.gladepath = app.gladepath\n\t\tself.imagepath = app.imagepath\n\t\tself.current = Profile(GuiActionParser())\n\t\tself.current.load(find_profile(OSDKeyboard.OSK_PROF_NAME))\n\t\tself.setup_widgets()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\tself.create_binding_buttons(use_icons=False, enable_press=False)\n\t\n\t\n\tdef show_editor(self, id):\n\t\tif id in STICKS:\n\t\t\tae = self.choose_editor(self.current.stick,\n\t\t\t\t_(\"Stick\"))\n\t\t\tae.set_input(STICK, self.current.stick, mode=Action.AC_OSK)\n\t\t\tae.show(self.window)\n\t\telif id in SCButtons.__members__.values():\n\t\t\ttitle = _(\"%s Button\") % (id.name,)\n\t\t\tae = self.choose_editor(self.current.buttons[id], title)\n\t\t\tae.set_input(id, self.current.buttons[id], mode=Action.AC_OSK)\n\t\t\tae.show(self.window)\n\t\telif id in TRIGGERS:\n\t\t\tae = self.choose_editor(self.current.triggers[id],\n\t\t\t\t_(\"%s Trigger\") % (id,))\n\t\t\tae.set_input(id, self.current.triggers[id], mode=Action.AC_OSK)\n\t\t\tae.show(self.window)\n\t\n\t\n\tdef on_action_chosen(self, id, action, mark_changed=True):\n\t\tself.set_action(self.current, id, action)\n\t\tself.save_profile()\n\t\n\t\n\tdef save_profile(self, *a):\n\t\t\"\"\"\n\t\tSaves osk profile from 'profile' object into 'giofile'.\n\t\tCalls on_profile_saved when done\n\t\t\"\"\"\n\t\tself.current.save(os.path.join(get_profiles_path(),\n\t\t\t\tOSDKeyboard.OSK_PROF_NAME + \".sccprofile\"))\n\t\t# OSK reloads profile when daemon reports configuration change\n\t\tself.app.dm.reconfigure()\n"
  },
  {
    "path": "scc/gui/parser.py",
    "content": "from __future__ import unicode_literals\nfrom scc.parser import ActionParser, ParseError\nfrom scc.actions import Action\nfrom scc.tools import _\n\nimport logging\nlog = logging.getLogger(\"gui.parse\")\n\nclass InvalidAction(Action):\n\tdef __init__(self, string, error):\n\t\tself.string = string\n\t\tself.error = error\n\t\tself.name = None\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Invalid Action '%s'>\" % (self.string,)\n\t\n\t__repr__ = __str__\n\t\n\t\n\tdef to_string(self, *a):\n\t\treturn self.string\n\t\n\t\n\tdef describe(self, *a):\n\t\treturn _(\"(invalid)\")\n\n\nclass GuiActionParser(ActionParser):\n\t\"\"\"\n\tActionParser that stores original string and\n\treturns InvalidAction instance when parsing fails\n\t\"\"\"\n\t\n\tdef restart(self, string):\n\t\tself.string = string\n\t\treturn ActionParser.restart(self, string)\n\n\t\n\tdef parse(self):\n\t\t\"\"\"\n\t\tReturns parsed action or None if action cannot be parsed.\n\t\t\"\"\"\n\t\ttry:\n\t\t\ta = ActionParser.parse(self)\n\t\t\ta.string = self.string\n\t\t\treturn a\n\t\texcept ParseError as e:\n\t\t\tlog.error(\"Failed to parse '%s'\", self.string)\n\t\t\tlog.error(e)\n\t\t\treturn InvalidAction(self.string, e)\n"
  },
  {
    "path": "scc/gui/profile_switcher.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - ProfileSwitcher\n\nSet of widgets designed to allow user to select profile, placed in one Gtk.Box:\n [ Icon | Combo box with profile selection       (ch) | (S) ]\n\n... where (S) is Save button that can be shown on demand and (ch) is change\nindicator drawn in combobox.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gio, GLib, GObject\nfrom scc.gui.userdata_manager import UserDataManager\nfrom scc.paths import get_controller_icons_path, get_default_controller_icons_path\nfrom scc.tools import find_profile, find_controller_icon\n\nimport os, random, logging\nlog = logging.getLogger(\"PS\")\n\nclass ProfileSwitcher(Gtk.EventBox, UserDataManager):\n\t\"\"\"\n\tList of signals:\n\t\tchanged (name, giofile)\n\t\t\tEmited when value of selection combobox is changed\n\t\tnew-clicked (name)\n\t\t\tEmited when user selects 'new profile' option.\n\t\t\t'name' is of currently selected profile.\n\t\tright-clicked ()\n\t\t\tEmited whenm user right-clicks anything\n\t\tsave-clicked ()\n\t\t\tEmited when user clicks on save button\n\t\tswitch-to-clicked ()\n\t\t\tEmited when user clicks on \"switch to this controller\" button\n\t\tunknown-profile (name)\n\t\t\tEmited when daemon reports unknown profile for controller.\n\t\t\t'name' is name of reported profile.\n\t\"\"\"\n\t\n\t__gsignals__ = {\n\t\t\t\"changed\"\t\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object, object)),\n\t\t\t\"new-clicked\"\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t\t\t\"right-clicked\"\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t\"save-clicked\"\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t\"switch-to-clicked\"\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t\"unknown-profile\"\t\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t}\n\t\n\tSEND_TIMEOUT = 100\t# How many ms should switcher wait before sending event\n\t\t\t\t\t\t# about profile being switched\n\t\n\tdef __init__(self, imagepath, config):\n\t\tGtk.EventBox.__init__(self)\n\t\tUserDataManager.__init__(self)\n\t\tself.imagepath = imagepath\n\t\tself.config = config\n\t\tself._allow_new = False\n\t\tself._first_time = True\n\t\tself._current = None\n\t\tself._recursing = False\n\t\tself._timer = None\t# Used to prevent sending too many request\n\t\t\t\t\t\t\t# when user scrolls throught combobox\n\t\tself._signal = None\n\t\tself._controller = None\n\t\tself.setup_widgets()\n\t\n\t\n\tdef setup_widgets(self):\n\t\t# Create\n\t\tself._icon = Gtk.Image()\n\t\tself._model = Gtk.ListStore(str, object, str)\n\t\tself._combo = Gtk.ComboBox.new_with_model(self._model)\n\t\tself._box = Gtk.Box(Gtk.Orientation.HORIZONTAL, 0)\n\t\tself._savebutton = None\n\t\tself._switch_to_button = None\n\t\t\n\t\t# Setup\n\t\trend1 = Gtk.CellRendererText()\n\t\trend2 = Gtk.CellRendererText()\n\t\tself._box.set_spacing(12)\n\t\tself._combo.pack_start(rend1, True)\n\t\tself._combo.pack_start(rend2, False)\n\t\tself._combo.add_attribute(rend1, \"text\", 0)\n\t\tself._combo.add_attribute(rend2, \"text\", 2)\n\t\tself._combo.set_row_separator_func(\n\t\t\tlambda model, iter : model.get_value(iter, 1) is None and model.get_value(iter, 0) == \"-\" )\n\t\tself.update_icon()\n\t\t\n\t\t# Signals\n\t\tself._combo.connect('changed', self.on_combo_changed)\n\t\tself.connect(\"button_press_event\", self.on_button_press)\n\t\t\n\t\t# Pack\n\t\tself._box.pack_start(self._icon, False, True, 0)\n\t\tself._box.pack_start(self._combo, True, True, 0)\n\t\tself.add(self._box)\n\t\n\t\n\tdef set_profile(self, name, create=False):\n\t\t\"\"\"\n\t\tSelects specified profile in UI.\n\t\tReturns True on success or False if profile is not in combobox.\n\t\t\n\t\tIf 'create' is set to True, creates new combobox item if needed.\n\t\t\"\"\"\n\t\tif name is None:\n\t\t\treturn\n\t\t\n\t\t\n\t\tif name.endswith(\".mod\"): name = name[0:-4]\n\t\tif name.endswith(\".sccprofile\"): name = name[0:-11]\n\t\tif \"/\" in name : name = os.path.split(name)[-1]\n\t\t\n\t\tself._current = name\n\t\tactive = self._combo.get_active_iter()\n\t\tgiofile = None\n\t\tfor row in self._model:\n\t\t\tif self._model.get_value(row.iter, 1) is not None:\n\t\t\t\tif name == self._model.get_value(row.iter, 0):\n\t\t\t\t\tgiofile = self._model.get_value(row.iter, 1)\n\t\t\t\t\tif active == row.iter:\n\t\t\t\t\t\t# Already selected\n\t\t\t\t\t\tbreak\n\t\t\t\t\tself._combo.set_active_iter(row.iter)\n\t\t\t\t\tbreak\n\t\tif giofile is None and create:\n\t\t\tpath = find_profile(name)\n\t\t\tif path:\n\t\t\t\tgiofile = Gio.File.new_for_path(path)\n\t\t\t\tself._model.insert(0, (name, giofile, None))\n\t\t\t\tself._combo.set_active(0)\n\t\t\n\t\treturn giofile != None\n\t\n\t\n\tdef set_allow_new(self, allow):\n\t\t\"\"\"\n\t\tEnables or disables creating new profile from this ProfileSwitcher.\n\t\tShould be called before set_profile_list.\n\t\t\"\"\"\n\t\tself._allow_new = allow\n\t\n\t\n\tdef set_allow_switch(self, allow):\n\t\t\"\"\"\n\t\tEnables or disables profile switching for this ProfileSwitcher.\n\t\tWhen disabled, only save button is be usable.\n\t\t\"\"\"\n\t\tself._combo.set_sensitive(allow)\n\t\n\t\n\tdef set_profile_list(self, lst):\n\t\t\"\"\"\n\t\tFills combobox with given list of available profiles.\n\t\t'lst' is expected to be iterable of GIO.File's.\n\t\t\"\"\"\n\t\tself._model.clear()\n\t\ti, current_index = 0, 0\n\t\tfor f in sorted(lst, key=lambda f: f.get_basename()):\n\t\t\tname = f.get_basename()\n\t\t\tif type(name) is not str:\n\t\t\t\tname = name.decode(\"utf-8\")\n\t\t\tif name.endswith(\".mod\"):\n\t\t\t\tcontinue\n\t\t\tif name.startswith(\".\"):\n\t\t\t\tcontinue\n\t\t\tif name.endswith(\".sccprofile\"):\n\t\t\t\tname = name[0:-11]\n\t\t\tif name == self._current:\n\t\t\t\tcurrent_index = i\n\t\t\tself._model.append((name, f, None))\n\t\t\ti += 1\n\t\tif self._allow_new:\n\t\t\tself._model.append((\"-\", None, None))\n\t\t\tself._model.append((_(\"New profile...\"), None, None))\n\t\t\n\t\tif self._combo.get_active_iter() is None:\n\t\t\tself._combo.set_active(current_index)\n\t\n\t\n\tdef get_profile_list(self):\n\t\t\"\"\" Returns profiles in combobox as iterable of Gio.File's \"\"\"\n\t\treturn ( x[1] for x in self._model if x[1] is not None )\n\t\n\t\n\tdef get_profile_name(self):\n\t\t\"\"\" Returns name of currently selected profile \"\"\"\n\t\treturn self._model.get_value(self._combo.get_active_iter(), 0)\n\t\n\t\n\tdef refresh_profile_path(self, name):\n\t\t\"\"\"\n\t\tCalled from main window after profile file is deleted.\n\t\tMay either change path to profile in default_profiles directory,\n\t\tor remove entry entirely.\n\t\t\"\"\"\n\t\tprev = None\n\t\tnew_path = find_profile(name)\n\t\t# Find row with named profile\n\t\tfor row in self._model:\n\t\t\tif row[0] == name:\n\t\t\t\tactive = self._combo.get_active_iter()\n\t\t\t\tif new_path is None:\n\t\t\t\t\t# Profile was completly removed\n\t\t\t\t\tif self._model.get_value(active, 0) == name:\n\t\t\t\t\t\t# If removed profile happends to be active one (what's\n\t\t\t\t\t\t# almost always), switch to previous profile in list\n\t\t\t\t\t\tself._model.remove(row.iter)\n\t\t\t\t\t\tif prev is None:\n\t\t\t\t\t\t\t# ... unless removed profile was 1st in list. Switch\n\t\t\t\t\t\t\t# to next in that case\n\t\t\t\t\t\t\tself._combo.set_active_iter(self._model[0].iter)\n\t\t\t\t\t\telse:\n\t\t\t\t\t\t\tself._combo.set_active_iter(prev.iter)\n\t\t\t\t\telse:\n\t\t\t\t\t\tself._model.remove(row.iter)\n\t\t\t\telse:\n\t\t\t\t\tgiofile = Gio.File.new_for_path(new_path)\n\t\t\t\t\tself._model.set_value(row.iter, 1, giofile)\n\t\t\t\t\tif self._model.get_value(active, 0) == name:\n\t\t\t\t\t\t# Active profile was changed\n\t\t\t\t\t\tself.emit('changed', name, giofile)\n\t\t\t\treturn\n\t\t\tprev = row\n\t\n\t\n\tdef on_combo_changed(self, cb):\n\t\tif self._recursing : return\n\t\t\n\t\tdef run_later():\n\t\t\tname = self._model.get_value(cb.get_active_iter(), 0)\n\t\t\tgiofile = self._model.get_value(cb.get_active_iter(), 1)\n\t\t\tGLib.source_remove(self._timer)\n\t\t\tself._timer = None\n\t\t\t\n\t\t\tif giofile is None:\n\t\t\t\t# 'New profile selected'\n\t\t\t\tself._recursing = True\n\t\t\t\tif self._current is None:\n\t\t\t\t\tcb.set_active(0)\n\t\t\t\telse:\n\t\t\t\t\tself.set_profile(self._current)\n\t\t\t\tself._recursing = False\n\t\t\t\t\n\t\t\t\tself.emit('new-clicked', self.get_profile_name())\n\t\t\telse:\n\t\t\t\tself._current = name\n\t\t\t\tself.emit('changed', name, giofile)\n\t\t\n\t\tif self._timer is not None:\n\t\t\tGLib.source_remove(self._timer)\n\t\tself._timer = GLib.timeout_add(ProfileSwitcher.SEND_TIMEOUT, run_later)\n\t\n\t\n\tdef on_button_press(self, trash, event):\n\t\tif event.button == 3:\n\t\t\tself.emit('right-clicked')\t\n\t\n\t\n\tdef on_savebutton_clicked(self, *a):\n\t\tself.emit('save-clicked')\n\t\n\t\n\tdef on_switch_to_clicked(self, *a):\n\t\tself.emit('switch-to-clicked')\n\t\n\t\n\tdef on_daemon_dead(self, *a):\n\t\t\"\"\" Called from App when connection to daemon is lost \"\"\"\n\t\tself._first_time = True\n\t\n\t\n\tdef on_profile_changed(self, c, profile):\n\t\t\"\"\" Called when controller profile is changed from daemon \"\"\"\n\t\tif not self.set_profile(profile, True):\n\t\t\tif self._first_time:\n\t\t\t\tdef later():\n\t\t\t\t\t# Cannot be executed right away, as profile-changed is\n\t\t\t\t\t# emitted before DaemonManager finishes initiaalisation\n\t\t\t\t\tself.emit('unknown-profile', profile)\n\t\t\t\tGLib.idle_add(later)\n\t\tself._first_time = False\n\t\n\t\n\tdef set_profile_modified(self, has_changes, is_template=False):\n\t\t\"\"\"\n\t\tCalled to signalize that profile has changes to save in UI\n\t\tby displaying \"changed\" next to profile name and showing Save button.\n\t\t\n\t\tReturns giofile for currently selected profile. If profile is set as\n\t\tchanged, giofile is automatically changed to 'original/filename.mod',\n\t\tso application can save changes without overwriting original wile.\n\t\t\"\"\"\n\t\tif has_changes:\n\t\t\tif not self._savebutton:\n\t\t\t\t# Save button has to be created\n\t\t\t\tself._savebutton = ButtonInRevealer(\n\t\t\t\t\t\"gtk-save\", _(\"Save changes\"),\n\t\t\t\t\tself.on_savebutton_clicked)\n\t\t\t\tself._box.pack_start(self._savebutton, False, True, 0)\n\t\t\t\tself.show_all()\n\t\t\tself._savebutton.set_reveal_child(True)\n\t\t\titer = self._combo.get_active_iter()\n\t\t\tif is_template:\n\t\t\t\tself._model.set_value(iter, 2, _(\"(changed template)\"))\n\t\t\telse:\n\t\t\t\tself._model.set_value(iter, 2, _(\"(changed)\"))\n\t\telse:\n\t\t\tif self._savebutton:\n\t\t\t\t# Nothing to hide if there is no revealer\n\t\t\t\tself._savebutton.set_reveal_child(False)\n\t\t\tfor i in self._model:\n\t\t\t\ti[2] = None\n\t\t\tif is_template:\n\t\t\t\titer = self._combo.get_active_iter()\n\t\t\t\tself._model.set_value(iter, 2, _(\"(template)\"))\n\t\n\t\n\tdef set_switch_to_enabled(self, enabled):\n\t\t\"\"\"\n\t\tShows or hides 'switch-to' button\n\t\t\"\"\"\n\t\tif enabled:\n\t\t\tif not self._switch_to_button:\n\t\t\t\t# Save button has to be created\n\t\t\t\tself._switch_to_button = ButtonInRevealer(\n\t\t\t\t\t\"gtk-edit\", _(\"Edit mappings of this controller\"),\n\t\t\t\t\tself.on_switch_to_clicked)\n\t\t\t\tself._box.pack_start(self._switch_to_button, False, True, 0)\n\t\t\t\tself.show_all()\n\t\t\tself._switch_to_button.set_reveal_child(True)\n\t\telse:\n\t\t\tif self._switch_to_button:\n\t\t\t\t# Nothing to hide if there is no revealer\n\t\t\t\tself._switch_to_button.set_reveal_child(False)\n\t\n\t\n\tdef get_file(self):\n\t\t\"\"\" Returns set profile as GIO file or None if there is no any \"\"\"\n\t\treturn None\n\t\n\t\n\tdef set_controller(self, c):\n\t\tif self._signal:\n\t\t\tself._controller.disconnect(self._signal)\n\t\t\tself._signal = None\n\t\tself._controller = c\n\t\tif c:\n\t\t\tname = self.config.get_controller_config(c.get_id())[\"name\"]\n\t\t\tself._icon.set_tooltip_text(name)\n\t\t\tself._signal = c.connect('profile-changed', self.on_profile_changed)\n\t\telse:\n\t\t\tself._icon.set_tooltip_text(_(\"Profile\"))\n\t\tself.update_icon()\n\t\n\t\n\tdef get_controller(self):\n\t\t\"\"\" Returns controller set by set_controller function \"\"\"\n\t\treturn self._controller\n\t\n\t\n\tdef update_icon(self):\n\t\t\"\"\" Changes displayed icon to whatever is currently set in config \"\"\"\n\t\t# Called internally and from ControllerSettings\n\t\tif not self._controller:\n\t\t\tself._icon.set_from_file(os.path.join(self.imagepath, \"controller-icon.svg\"))\n\t\t\treturn\n\t\t\n\t\tid = self._controller.get_id()\n\t\tcfg = self.config.get_controller_config(id)\n\t\tif cfg[\"icon\"]:\n\t\t\ticon = find_controller_icon(cfg[\"icon\"])\n\t\t\tself._icon.set_from_file(icon)\n\t\telse:\n\t\t\tlog.debug(\"There is no icon for controller %s, auto assinging one\", id)\n\t\t\tpaths = [ get_default_controller_icons_path(), get_controller_icons_path() ]\n\t\t\t\n\t\t\tdef cb(icons):\n\t\t\t\tif id != self._controller.get_id():\n\t\t\t\t\t# Controller was changed before callback was called\n\t\t\t\t\treturn\n\t\t\t\ticon = None\n\t\t\t\tused_icons = { \n\t\t\t\t\tself.config['controllers'][x]['icon']\n\t\t\t\t\tfor x in self.config['controllers']\n\t\t\t\t\tif 'icon' in self.config['controllers'][x]\n\t\t\t\t}\n\t\t\t\ttp = \"%s-\" % (self._controller.get_type(),)\n\t\t\t\ticons = sorted(( os.path.split(x.get_path())[-1] for x in icons ))\n\t\t\t\tlog.debug(\"Searching for icon type: %s\", tp.strip(\"-\"))\n\t\t\t\tfor i in icons:\n\t\t\t\t\tif i not in used_icons and i.startswith(tp):\n\t\t\t\t\t\t# Unused icon found\n\t\t\t\t\t\ticon = i\n\t\t\t\t\t\tbreak\n\t\t\t\telse:\n\t\t\t\t\t# All icons are already used, assign anything\n\t\t\t\t\ticon = random.choice(icons)\n\t\t\t\tlog.debug(\"Auto-assigned icon %s for controller %s\", icon, id)\n\t\t\t\tcfg = self.config.get_controller_config(id)\n\t\t\t\tcfg[\"icon\"] = icon\n\t\t\t\tself.config.save()\n\t\t\t\tGLib.idle_add(self.update_icon)\n\t\t\t\n\t\t\tself.load_user_data(paths, \"*.svg\", None, cb)\n\n\nclass ButtonInRevealer(Gtk.Revealer):\n\t\n\tdef __init__(self, button_name, tooltip, callback):\n\t\tGtk.Revealer.__init__(self)\n\t\tself.button = Gtk.Button.new_from_icon_name(button_name, Gtk.IconSize.SMALL_TOOLBAR)\n\t\tself.button.connect('clicked', callback)\n\t\tself.button.set_tooltip_text(tooltip)\n\t\tself.set_reveal_child(False)\n\t\tself.set_transition_type(Gtk.RevealerTransitionType.SLIDE_LEFT)\n\t\tself.add(self.button)\n"
  },
  {
    "path": "scc/gui/ribar.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - RIBar\n\nInfobar wrapped in Revealer, looks better than sounds.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom gi.repository import Gtk, GLib, GObject, Pango\nimport os\n\n\nclass RIBar(Gtk.Revealer):\n\t\"\"\"\n\tInfobar wrapped in Revealer\n\t\n\tSignals:\n\t\tEverything from Gtk.Revealer, plus:\n\t\tclose()\n\t\t\temitted when the user dismisses the info bar\n\t\tresponse(response_id)\n\t\t\tEmitted when an action widget (button) is clicked\n\t\"\"\"\n\t__gsignals__ = {\n\t\t\t\"response\"\t: (GObject.SignalFlags.RUN_FIRST, None, (int,)),\n\t\t\t\"close\"\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t}\n\t\n\t### Initialization\n\tdef __init__(self, label, message_type=Gtk.MessageType.INFO,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tinfobar=None, *buttons):\n\t\t\"\"\"\n\t\t... where label can be Gtk.Widget or str and buttons are tuples\n\t\tof (Gtk.Button, response_id)\n\t\t\"\"\"\n\t\t# Init\n\t\tGtk.Revealer.__init__(self)\n\t\tself._infobar = infobar or Gtk.InfoBar()\n\t\tself._values = {}\n\t\tself._label = None\n\t\t# Icon\n\t\tif infobar is None:\n\t\t\ticon_name = \"dialog-information\"\n\t\t\tif message_type == Gtk.MessageType.ERROR:\n\t\t\t\ticon_name = \"dialog-error\"\n\t\t\telif message_type == Gtk.MessageType.WARNING:\n\t\t\t\ticon_name = \"dialog-warning\"\n\t\t\ticon = Gtk.Image()\n\t\t\ticon.set_from_icon_name(icon_name, Gtk.IconSize.DIALOG)\n\t\t\tself._infobar.get_content_area().pack_start(icon, False, False, 1)\n\t\t\t# Label\n\t\t\tif isinstance(label, Gtk.Widget):\n\t\t\t\tself._infobar.get_content_area().pack_start(label, True, True, 0)\n\t\t\t\tself._label = label\n\t\t\telse:\n\t\t\t\tself._label = Gtk.Label()\n\t\t\t\tself._label.set_size_request(300, -1)\n\t\t\t\tself._label.set_markup(label)\n\t\t\t\tself._label.set_alignment(0, 0.5)\n\t\t\t\tself._label.set_line_wrap(True)\n\t\t\t\tself._infobar.get_content_area().add(self._label)\n\t\t# Buttons\n\t\tfor button, response_id in buttons:\n\t\t\tself.add_button(button, response_id)\n\t\t# Signals\n\t\tself._infobar.connect(\"close\", self._cb_close)\n\t\tself._infobar.connect(\"response\", self._cb_response)\n\t\t# Settings\n\t\tself._infobar.set_message_type(message_type)\n\t\tif hasattr(self._infobar, \"set_show_close_button\"):\n\t\t\t# GTK >3.8\n\t\t\tself._infobar.set_show_close_button(True)\n\t\telse:\n\t\t\tself.add_button(Gtk.Button(\"X\"), 0)\n\t\tself.set_reveal_child(False)\n\t\t# Packing\n\t\tself.add(self._infobar)\n\t\tself.show_all()\n\t\n\tdef _cb_close(self, ib):\n\t\tself.emit(\"close\")\n\t\n\tdef _cb_response(self, ib, response_id):\n\t\tself.emit(\"response\", response_id)\n\t\n\tdef disable_close_button(self):\n\t\tif hasattr(self._infobar, \"set_show_close_button\"):\n\t\t\tself._infobar.set_show_close_button(False)\n\t\n\tdef add_widget(self, widget, expand=False, fill=True):\n\t\tself._infobar.get_content_area().pack_start(widget, expand, fill, 1)\n\t\twidget.show()\n\t\n\tdef add_button(self, button, response_id):\n\t\tself._infobar.add_action_widget(button, response_id)\n\t\tself._infobar.show_all()\n\t\n\tdef get_label(self):\n\t\t\"\"\" Returns label widget \"\"\"\n\t\treturn self._label\n\t\n\tdef close_on_close(self):\n\t\t\"\"\"\n\t\tSetups revealer so it will be automaticaly closed, removed and\n\t\tdestroyed when user clicks to any button, including 'X'\n\t\t\"\"\"\n\t\tself.connect(\"close\", self.close)\n\t\tself.connect(\"response\", self.close)\n\t\n\tdef close(self, *a):\n\t\t\"\"\"\n\t\tCloses revealer (with animation), removes it from parent and\n\t\tcalls destroy()\n\t\t\"\"\"\n\t\tself.set_reveal_child(False)\n\t\tGLib.timeout_add(self.get_transition_duration() + 50, self._cb_destroy)\n\t\n\tdef _cb_destroy(self, *a):\n\t\t\"\"\" Callback used by _cb_close method \"\"\"\n\t\tif not self.get_parent() is None:\n\t\t\tself.get_parent().remove(self)\n\t\tself.destroy()\n\t\n\tdef set_value(self, key, value):\n\t\t\"\"\" Stores some metadata \"\"\"\n\t\tself._values[key] = value\n\t\n\tdef get_value(self, key):\n\t\t\"\"\" Retrieves some metadata \"\"\"\n\t\treturn self._values[key]\n\t\n\tdef __getitem__(self, key):\n\t\t\"\"\" Shortcut to get_value \"\"\"\n\t\treturn self._values[key]\n\t\n\tdef __setitem__(self, key, value):\n\t\t\"\"\" Shortcut to set_value \"\"\"\n\t\tself.set_value(key, value)\n\t\n\t@staticmethod\n\tdef build_button(label, icon_name=None, icon_widget=None, use_stock=False):\n\t\t\"\"\" Builds button situable for action area \"\"\"\n\t\tb = Gtk.Button.new_from_stock(label) if use_stock \\\n\t\t\telse Gtk.Button.new_with_label(label)\n\t\tb.set_use_underline(True)\n\t\tif not icon_name is None:\n\t\t\ticon_widget = Gtk.Image()\n\t\t\ticon_widget.set_from_icon_name(icon_name, Gtk.IconSize.BUTTON)\n\t\tif not icon_widget is None:\n\t\t\tb.set_image(icon_widget)\n\t\t\tb.set_always_show_image(True)\n\t\treturn b\n"
  },
  {
    "path": "scc/gui/ring_editor.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Action Editor\n\nAllows to edit button or trigger action.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.gui.controller_widget import ControllerButton\nfrom scc.gui.controller_widget import STICKS, PADS\nfrom scc.gui.editor import Editor, ComboSetter\nfrom scc.gui.dwsnc import headerbar\nfrom scc.modifiers import ModeModifier, DoubleclickModifier, HoldModifier\nfrom scc.actions import Action, RingAction, NoAction, MultiAction\nfrom scc.constants import SCButtons\nfrom scc.profile import Profile\n\nfrom gi.repository import Gtk, Gdk, GLib\nimport os, logging\nlog = logging.getLogger(\"RingEditor\")\n\nclass RingEditor(Editor, ComboSetter):\n\tGLADE = \"ring_editor.glade\"\n\t\n\tdef __init__(self, app, callback):\n\t\tEditor.__init__(self)\n\t\tself.app = app\n\t\tself.id = None\n\t\tself.mode = Action.AC_BUTTON\n\t\tself.ac_callback = callback\n\t\tself.radius = 0.5\n\t\tself.actions = [ NoAction(), NoAction() ]\n\t\tself.setup_widgets()\n\t\n\t\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\tb = lambda a : self.builder.get_object(a)\n\t\tself.action_widgets = (\n\t\t\t# Order goes: Action Button, Clear Button\n\t\t\t( b('btInner'),\t\tb('btClearInner') ),\n\t\t\t( b('btOuter'),\t\tb('btClearOuter') )\n\t\t)\n\t\theaderbar(self.builder.get_object(\"header\"))\n\t\n\t\n\t@staticmethod\n\tdef is_ring_action(obj):\n\t\t\"\"\"\n\t\tReturns True if object is instance of RingAction or object\n\t\tis MultiAction with RingAction as 2nd item\n\t\t\"\"\"\n\t\tif isinstance(obj, RingAction):\n\t\t\treturn True\n\t\tif isinstance(obj, MultiAction):\n\t\t\tif len(obj.actions) >= 2:\n\t\t\t\tif isinstance(obj.actions[1], RingAction):\n\t\t\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef on_adjRadius_value_changed(self, scale, *a):\n\t\tself.radius = scale.get_value()\n\t\n\t\n\tdef on_sclRadius_format_value(self, scale, value):\n\t\treturn \"%s%%\" % (int(value * 100),)\n\t\n\t\n\tdef on_Dialog_destroy(self, *a):\n\t\tself.remove_added_widget()\n\t\n\t\n\tdef on_btClearRadius_clicked(self, *a):\n\t\tself.radius = 0.5\n\t\tself._update()\n\t\n\t\n\tdef on_cbMode_changed(self, cb):\n\t\tlblRadius = self.builder.get_object(\"lblRadius\")\n\t\tlblInner = self.builder.get_object(\"lblInner\")\n\t\tlblOuter = self.builder.get_object(\"lblOuter\")\n\t\tkey = cb.get_model().get_value(cb.get_active_iter(), 0)\n\t\tif key == \"inner\":\n\t\t\tlblRadius.set_label(_(\"Inner Ring Radius\"))\n\t\t\tlblInner.set_label(_(\"Inner Ring\"))\n\t\t\tlblOuter.set_label(_(\"Entire Pad\"))\n\t\telif key == \"outer\":\n\t\t\tlblRadius.set_label(_(\"Outer Ring Starts at\"))\n\t\t\tlblInner.set_label(_(\"Entire Pad\"))\n\t\t\tlblOuter.set_label(_(\"Outer Ring\"))\n\t\telif key == \"two\":\n\t\t\tlblRadius.set_label(_(\"Inner Ring Radius\"))\n\t\t\tlblInner.set_label(_(\"Inner Ring\"))\n\t\t\tlblOuter.set_label(_(\"Outer Ring\"))\n\t\n\t\n\tdef _choose_editor(self, action, cb):\n\t\tif isinstance(action, ModeModifier):\n\t\t\tfrom scc.gui.modeshift_editor import ModeshiftEditor\t# Cannot be imported @ top\n\t\t\te = ModeshiftEditor(self.app, cb)\n\t\t\te.set_title(_(\"Edit Action\"))\n\t\telse:\n\t\t\tfrom scc.gui.action_editor import ActionEditor\t# Cannot be imported @ top\n\t\t\te = ActionEditor(self.app, cb)\n\t\t\te.set_title(_(\"Edit Action\"))\n\t\t\te.hide_macro()\n\t\t\te.hide_ring()\n\t\treturn e\n\t\n\t\n\tdef on_actionb_clicked(self, clicked_button):\n\t\tfor i in range(0, len(self.action_widgets)):\n\t\t\tbutton, clearb = self.action_widgets[i]\n\t\t\tif button == clicked_button:\n\t\t\t\tdef on_chosen(id, action):\n\t\t\t\t\tself.actions[i] = action\n\t\t\t\t\tself._update()\n\t\t\t\t\n\t\t\t\tae = self._choose_editor(self.actions[i], on_chosen)\n\t\t\t\tae.set_input(self.id, self.actions[i])\n\t\t\t\tae.show(self.window)\n\t\t\t\treturn\n\t\n\t\n\tdef on_clearb_clicked(self, clicked_button):\n\t\tfor i in range(0, len(self.action_widgets)):\n\t\t\tbutton, clearb = self.action_widgets[i]\n\t\t\tif clearb == clicked_button:\n\t\t\t\tself.actions[i] = NoAction()\n\t\t\t\tself._update()\n\t\t\t\treturn\n\t\n\t\n\tdef on_btClear_clicked(self, *a):\n\t\t\"\"\" Handler for clear button \"\"\"\n\t\taction = NoAction()\n\t\tif self.ac_callback is not None:\n\t\t\tself.ac_callback(self.id, action)\n\t\tself.close()\n\t\n\t\n\tdef on_btCustomActionEditor_clicked(self, *a):\n\t\t\"\"\" Handler for 'Custom Editor' button \"\"\"\n\t\tfrom scc.gui.action_editor import ActionEditor\t# Can't be imported on top\n\t\te = ActionEditor(self.app, self.ac_callback)\n\t\te.set_input(self.id, self._make_action(), mode = self.mode)\n\t\te.hide_action_buttons()\n\t\te.hide_advanced_settings()\n\t\te.set_title(_(\"Custom Action\"))\n\t\te.force_page(e.load_component(\"custom\"), True)\n\t\tself.send_added_widget(e)\n\t\tself.close()\n\t\te.show(self.get_transient_for())\n\t\n\t\n\tdef on_btOK_clicked(self, *a):\n\t\t\"\"\" Handler for OK button \"\"\"\n\t\tif self.ac_callback is not None:\n\t\t\tself.ac_callback(self.id, self._make_action())\n\t\tself.close()\n\t\n\t\n\tdef _make_action(self):\n\t\t\"\"\" Generates and returns Action instance \"\"\"\n\t\tcbMode = self.builder.get_object(\"cbMode\")\n\t\tkey = cbMode.get_model().get_value(cbMode.get_active_iter(), 0)\n\t\tif key == \"inner\":\n\t\t\treturn MultiAction(\n\t\t\t\tself.actions[1],\n\t\t\t\tRingAction(self.radius, self.actions[0], NoAction())\n\t\t\t)\n\t\telif key == \"outer\":\n\t\t\treturn MultiAction(\n\t\t\t\tself.actions[0],\n\t\t\t\tRingAction(self.radius, NoAction(), self.actions[1])\n\t\t\t)\n\t\telse:\n\t\t\treturn RingAction(self.radius, *self.actions)\n\t\n\t\n\tdef _update(self):\n\t\tfor i in range(0, len(self.action_widgets)):\n\t\t\tbutton, clearb = self.action_widgets[i]\n\t\t\tbutton.set_label(self.actions[i].describe(Action.AC_BUTTON))\n\t\tself.builder.get_object(\"sclRadius\").set_value(self.radius)\n\t\n\t\n\tdef allow_first_page(self):\n\t\t\"\"\" For compatibility with action editor. Does nothing \"\"\"\n\t\tpass\n\t\n\t\n\tdef set_input(self, id, action, mode=None):\n\t\tbtDefault = self.builder.get_object(\"btDefault\")\n\t\tlblPressAlone = self.builder.get_object(\"lblPressAlone\")\n\t\tcbMode = self.builder.get_object(\"cbMode\")\n\t\tself.id = id\n\t\t\n\t\tif isinstance(action, RingAction):\n\t\t\tself.radius = action.radius\n\t\t\tself.actions = [ action.inner, action.outer ]\n\t\t\tself.set_cb(cbMode, \"two\")\n\t\telif RingEditor.is_ring_action(action):\n\t\t\t# Goes here only if action is MultiAciton with RingAction as 2nd item\n\t\t\tring = action.actions[1]\n\t\t\tself.radius = ring.radius\n\t\t\tif ring.inner:\n\t\t\t\tself.actions = [ ring.inner, action.actions[0] ]\n\t\t\t\tself.set_cb(cbMode, \"inner\")\n\t\t\telse:\n\t\t\t\tself.actions = [ action.actions[0], ring.outer ]\n\t\t\t\tself.set_cb(cbMode, \"outer\")\n\t\tself._update()\n"
  },
  {
    "path": "scc/gui/simple_chooser.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Simple Chooser\n\nUsed by Action Editor to display window with just one Component\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GLib\nfrom scc.gui.dwsnc import headerbar\nfrom scc.gui.ae import AEComponent\nfrom scc.gui.editor import Editor\nimport logging, os, types, importlib\nlog = logging.getLogger(\"SimpleChooser\")\n\nclass SimpleChooser(Editor):\n\tGLADE = \"simple_chooser.glade\"\n\t\n\tdef __init__(self, app, component_name, callback):\n\t\tself.app = app\n\t\tself._action = None\n\t\tself.component = None\n\t\tself.callback = callback\n\t\tself.setup_widgets()\n\t\tself.load_component(component_name)\n\t\n\t\n\tdef setup_widgets(self):\n\t\tEditor.setup_widgets(self)\n\t\theaderbar(self.builder.get_object(\"header\"))\n\t\n\t\n\tdef load_component(self, component_name):\n\t\tmod = importlib.import_module(\"scc.gui.ae.%s\" % (component_name,))\n\t\tfor x in dir(mod):\n\t\t\tcls = getattr(mod, x)\n\t\t\tif isinstance(cls, type) and issubclass(cls, AEComponent):\n\t\t\t\tif cls.NAME == component_name:\n\t\t\t\t\tself.component = cls(self.app, self)\n\t\t\t\t\tbreak\n\t\tif self.component is None:\n\t\t\traise ValueError(\"Unknown component '%s'\" % (component_name,))\n\t\tself.component.load()\n\t\tif component_name == \"buttons\":\n\t\t\tself.component.hide_toggle()\n\t\tself.window.add(self.component.get_widget())\n\t\n\t\n\tdef display_action(self, mode, action):\n\t\tself._action = action\n\t\tself.component.set_action(mode, action)\n\t\n\t\n\tdef set_action(self, action):\n\t\tself.callback(action)\n\t\tself.close()\n\t\tself.window.destroy()\n\t\n\t\n\tdef hide_axes(self):\n\t\t\"\"\" Prevents user from selecting axes \"\"\"\n\t\tself.component.hide_axes()\n\t\n\t\n\tdef hide_mouse(self):\n\t\t\"\"\" Prevents user from selecting mouse-related stuff \"\"\"\n\t\tself.component.hide_mouse()\n"
  },
  {
    "path": "scc/gui/statusicon.py",
    "content": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\"\"\"\nSyncthing-GTK - StatusIcon\n\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport locale\nimport os\nimport sys\nimport logging\n\nfrom gi.repository import GObject\nfrom gi.repository import GLib\nfrom gi.repository import Gtk\n\nfrom scc.gui.dwsnc import IS_UNITY, IS_GNOME\nfrom scc.tools import _ # gettext function\n\nlog = logging.getLogger(\"StatusIcon\")\n\n# Taken from Syncthing-GTK, but got all KDE stuff removed, since it doesn't\n# work in latest KDE anymore.\n\n\n#                | MATE      | Unity      | Cinnamon   | Cairo-Dock (classic) | Cairo-Dock (modern) |\n#----------------+-----------+------------+------------+----------------------+---------------------+\n# StatusIconAppI | none      | excellent  | none       | none                 | excellent           |\n# StatusIconGTK3 | excellent | none       | very good¹ | very good¹           | none                |\n#\n# Notes:\n#  - StatusIconAppIndicator does not implement any fallback (but the original libappindicator did)\n#  - Markers:\n#     ¹ Icon cropped\n\n\nclass StatusIcon(GObject.GObject):\n\t\"\"\"\n\tBase class for all status icon backends\n\t\"\"\"\n\tTRAY_TITLE     = _(\"SC-Controller\")\n\t\n\t__gsignals__ = {\n\t\t\"clicked\": (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t}\n\t\n\t__gproperties__ = {\n\t\t\"active\": (\n\t\t\tGObject.TYPE_BOOLEAN,\n\t\t\t\"is the icon user-visible?\",\n\t\t\t\"does the icon back-end think that anything is might be shown to the user?\",\n\t\t\tTrue,\n\t\t\tGObject.PARAM_READWRITE if hasattr(GObject, \"PARAM_READWRITE\") else GObject.ParamFlags.READWRITE\n\t\t)\t\t\n\t}\n\t\n\tdef __init__(self, icon_path, popupmenu, force=False):\n\t\tGObject.GObject.__init__(self)\n\t\tself.__icon_path = os.path.normpath(os.path.abspath(icon_path))\n\t\tself.__popupmenu = popupmenu\n\t\tself.__active    = True\n\t\tself.__visible   = False\n\t\tself.__hidden    = False\n\t\tself.__icon      = \"scc-unknown\"\n\t\tself.__text      = \"\"\n\t\tself.__force     = force\n\t\n\tdef get_active(self):\n\t\t\"\"\"\n\t\tReturn whether there is at least a chance that the icon might be shown to the user\n\t\t\n\t\tIf this returns `False` then the icon will definetely not be shown, but if it returns `True` it doesn't have to\n\t\tbe visible...\n\t\t\n\t\t<em>Note:</em> This value is not directly influenced by calling `hide()` and `show()`.\n\t\t\n\t\t@return {bool}\n\t\t\"\"\"\n\t\treturn self.get_property(\"active\")\n\t\n\tdef set(self, icon=None, text=None):\n\t\t\"\"\"\n\t\tSet the status icon image and descriptive text\n\t\t\n\t\tIf either of these are `None` their previous value will be used.\n\t\t\n\t\t@param {String} icon\n\t\t       The name of the icon to show (i.e. `si-syncthing-idle`)\n\t\t@param {String} text\n\t\t       Some text that indicates what the application is currently doing (generally this be used for the tooltip)\n\t\t\"\"\"\n\t\tif not icon.endswith(\"-0\"): # si-syncthing-0\n\t\t\t# Ignore first syncing icon state to prevent the icon from flickering\n\t\t\t# into the main notification bar during initialization\n\t\t\tself.__visible = True\n\t\t\n\t\tif self.__hidden:\n\t\t\tself._set_visible(False)\n\t\telse:\n\t\t\tself._set_visible(self.__visible)\n\t\n\tdef hide(self):\n\t\t\"\"\"\n\t\tHide the icon\n\t\t\n\t\tThis method tries its best to ensure the icon is hidden, but there are no guarantees as to how use well its\n\t\tgoing to work.\n\t\t\"\"\"\n\t\tself.__hidden = True\n\t\tself._set_visible(False)\n\t\n\tdef show(self):\n\t\t\"\"\"\n\t\tShow a previously hidden icon\n\t\t\n\t\tThis method tries its best to ensure the icon is hidden, but there are no guarantees as to how use well its\n\t\tgoing to work.\n\t\t\"\"\"\n\t\tself.__hidden = False\n\t\tself._set_visible(self.__visible)\n\t\n\tdef is_clickable(self):\n\t\t\"\"\" Basically, returns False is appindicator is used \"\"\"\n\t\treturn True\n\t\n\tdef _is_forced(self):\n\t\treturn self.__force\n\t\n\tdef _on_click(self, *a):\n\t\tself.emit(\"clicked\")\n\t\n\tdef _get_icon(self, icon=None):\n\t\t\"\"\"\n\t\t@internal\n\t\t\n\t\tUse `set()` instead.\n\t\t\"\"\"\n\t\tif icon:\n\t\t\tself.__icon = icon\n\t\treturn self.__icon\n\t\n\tdef _get_text(self, text=None):\n\t\t\"\"\"\n\t\t@internal\n\t\t\n\t\tUse `set()` instead.\n\t\t\"\"\"\n\t\tif text:\n\t\t\tself.__text = text\n\t\treturn self.__text\n\t\n\tdef _get_popupmenu(self):\n\t\t\"\"\"\n\t\t@internal\n\t\t\"\"\"\n\t\treturn self.__popupmenu\n\t\n\tdef _set_visible(self, visible):\n\t\t\"\"\"\n\t\t@internal\n\t\t\"\"\"\n\t\tpass\n\n\tdef do_get_property(self, property):\n\t\tif property.name == \"active\":\n\t\t\treturn self.__active\n\t\telse:\n\t\t\traise AttributeError(\"Unknown property %s\" % property.name)\n\t\n\tdef do_set_property(self, property, value):\n\t\tif property.name == \"active\":\n\t\t\tself.__active = value\n\t\telse:\n\t\t\traise AttributeError(\"unknown property %s\" % property.name)\n\n\nclass StatusIconDummy(StatusIcon):\n\t\"\"\"\n\tDummy status icon implementation that does nothing\n\t\"\"\"\n\tdef __init__(self, *args, **kwargs):\n\t\tStatusIcon.__init__(self, *args, **kwargs)\n\t\t\n\t\t# Pretty unlikely that this will be visible...\n\t\tself.set_property(\"active\", False)\n\t\n\tdef set(self, icon=None, text=None):\n\t\tStatusIcon.set(self, icon, text)\n\t\t\n\t\tself._get_icon(icon)\n\t\tself._get_text(text)\n\n\nclass StatusIconGTK3(StatusIcon):\n\t\"\"\"\n\tGtk.StatusIcon based status icon backend\n\t\"\"\"\n\tdef __init__(self, *args, **kwargs):\n\t\tStatusIcon.__init__(self, *args, **kwargs)\n\t\t\n\t\tif not self._is_forced():\n\t\t\tif IS_UNITY:\n\t\t\t\t# Unity fakes SysTray support but actually hides all icons...\n\t\t\t\traise NotImplementedError\n\t\t\tif IS_GNOME:\n\t\t\t\t# Gnome got broken as well\n\t\t\t\traise NotImplementedError\n\t\t\n\t\tself._tray = Gtk.StatusIcon()\n\t\t\n\t\tself._tray.connect(\"activate\", self._on_click)\n\t\tself._tray.connect(\"popup-menu\", self._on_rclick)\n\t\tself._tray.connect(\"notify::embedded\", self._on_embedded_change)\n\t\t\n\t\tself._tray.set_visible(True)\n\t\tself._tray.set_name(\"sc-controller\")\n\t\tself._tray.set_title(self.TRAY_TITLE)\n\t\t\n\t\t# self._tray.is_embedded() must be called asynchronously\n\t\t# See: http://stackoverflow.com/a/6365904/277882\n\t\tGLib.idle_add(self._on_embedded_change)\n\t\n\tdef destroy(self):\n\t\tself.hide()\n\t\tself._tray = None\n\t\n\tdef set(self, icon=None, text=None):\n\t\tStatusIcon.set(self, icon, text)\n\t\t\n\t\tself._tray.set_from_icon_name(self._get_icon(icon))\n\t\tself._tray.set_tooltip_text(self._get_text(text))\n\t\n\tdef _on_embedded_change(self, *args):\n\t\t# Without an icon update at this point GTK might consider the icon embedded and visible even through\n\t\t# it can't actually be seen...\n\t\tself._tray.set_from_file(self._get_icon())\n\t\t\n\t\t# An invisible tray icon will never be embedded but it also should not be replaced\n\t\t# by a fallback icon\n\t\tis_embedded = self._tray.is_embedded() or not self._tray.get_visible()\n\t\tif is_embedded != self.get_property(\"active\"):\n\t\t\tself.set_property(\"active\", is_embedded)\n\t\n\tdef _on_rclick(self, si, button, time):\n\t\tself._get_popupmenu().popup(None, None, None, None, button, time)\n\t\n\tdef _set_visible(self, active):\n\t\tStatusIcon._set_visible(self, active)\n\t\t\n\t\tself._tray.set_visible(active)\n\n\nclass StatusIconDBus(StatusIcon):\n\tpass\n\n\nclass StatusIconAppIndicator(StatusIconDBus):\n\t\"\"\"\n\tUnity's AppIndicator3.Indicator based status icon backend\n\t\"\"\"\n\tdef __init__(self, *args, **kwargs):\n\t\tStatusIcon.__init__(self, *args, **kwargs)\n\t\t\n\t\ttry:\n\t\t\tfrom gi.repository import AppIndicator3 as appindicator\n\t\t\t\n\t\t\tself._status_active  = appindicator.IndicatorStatus.ACTIVE\n\t\t\tself._status_passive = appindicator.IndicatorStatus.PASSIVE\n\t\texcept ImportError:\n\t\t\tlog.warning(\"Failed to import AppIndicator3\")\n\t\t\traise NotImplementedError\n\t\t\n\t\tcategory = appindicator.IndicatorCategory.APPLICATION_STATUS\n\t\t# Whatever icon is set here will be used as a tooltip icon during the entire time to icon is shown\n\t\tself._tray = appindicator.Indicator.new(\"sc-controller\", self._get_icon(), category)\n\t\tself._tray.set_menu(self._get_popupmenu())\n\t\tself._tray.set_title(self.TRAY_TITLE)\n\t\n\tdef _set_visible(self, active):\n\t\tStatusIcon._set_visible(self, active)\n\t\t\n\t\tself._tray.set_status(self._status_active if active else self._status_passive)\n\t\n\tdef is_clickable(self):\n\t\treturn False\n\t\n\tdef destroy(self):\n\t\tself.hide()\n\t\tself._tray = None\n\t\n\tdef set(self, icon=None, text=None):\n\t\tStatusIcon.set(self, icon, text)\n\t\t\n\t\tself._tray.set_icon_full(self._get_icon(icon), self._get_text(text))\n\n\nclass StatusIconProxy(StatusIcon):\n\t\n\tdef __init__(self, *args, **kwargs):\n\t\tStatusIcon.__init__(self, *args, **kwargs)\n\t\t\n\t\tself._arguments  = (args, kwargs)\n\t\tself._status_fb  = None\n\t\tself._status_gtk = None\n\t\tself.set(\"scc-unknown\", \"\")\n\t\t\n\t\t# Do not ever force-show indicators when they do not think they'll work\n\t\tif \"force\" in self._arguments[1]:\n\t\t\tdel self._arguments[1][\"force\"]\n\t\t\n\t\ttry:\n\t\t\t# Try loading GTK native status icon\n\t\t\tself._status_gtk = StatusIconGTK3(*args, **kwargs)\n\t\t\tself._status_gtk.connect(\"clicked\",        self._on_click)\n\t\t\tself._status_gtk.connect(\"notify::active\", self._on_notify_active_gtk)\n\t\t\tself._on_notify_active_gtk()\n\t\t\t\n\t\t\tlog.info(\"Using backend StatusIconGTK3 (primary)\")\n\t\texcept NotImplementedError:\n\t\t\t# Directly load fallback implementation\n\t\t\tself._load_fallback()\n\t\n\tdef _on_click(self, *args):\n\t\tself.emit(\"clicked\")\n\t\n\tdef _on_notify_active_gtk(self, *args):\n\t\tif self._status_fb:\n\t\t\t# Hide fallback icon if GTK icon is active and vice-versa\n\t\t\tif self._status_gtk.get_active():\n\t\t\t\tself._status_fb.hide()\n\t\t\telse:\n\t\t\t\tself._status_fb.show()\n\t\telif not self._status_gtk.get_active():\n\t\t\t# Load fallback implementation\n\t\t\tself._load_fallback()\n\t\n\tdef _on_notify_active_fb(self, *args):\n\t\tactive = False\n\t\tif self._status_gtk and self._status_gtk.get_active():\n\t\t\tactive = True\n\t\tif self._status_fb and self._status_fb.get_active():\n\t\t\tactive = True\n\t\tself.set_property(\"active\", active)\n\t\n\tdef _load_fallback(self):\n\t\tstatus_icon_backends = [StatusIconAppIndicator, StatusIconDummy]\n\t\t\n\t\tif not self._status_fb:\n\t\t\tfor StatusIconBackend in status_icon_backends:\n\t\t\t\ttry:\n\t\t\t\t\tself._status_fb = StatusIconBackend(*self._arguments[0], **self._arguments[1])\n\t\t\t\t\tself._status_fb.connect(b\"clicked\",        self._on_click)\n\t\t\t\t\tself._status_fb.connect(b\"notify::active\", self._on_notify_active_fb)\n\t\t\t\t\tself._on_notify_active_fb()\n\t\t\t\t\t\n\t\t\t\t\tlog.warning(\"StatusIcon: Using backend %s (fallback)\" % StatusIconBackend.__name__)\n\t\t\t\t\tbreak\n\t\t\t\texcept NotImplementedError:\n\t\t\t\t\tcontinue\n\t\t\n\t\t\t# At least the dummy backend should have been loaded at this point...\n\t\t\tassert self._status_fb\n\t\t\n\t\t# Update fallback icon\n\t\tself.set(self._icon, self._text)\n\t\n\tdef is_clickable(self):\n\t\tif self._status_gtk:\n\t\t\treturn self._status_gtk.is_clickable()\n\t\tif self._status_fb:\n\t\t\treturn self._status_fb.is_clickable()\n\t\treturn False\n\t\n\tdef set(self, icon=None, text=None):\n\t\tself._icon = icon\n\t\tself._text = text\n\t\t\n\t\tif self._status_gtk:\n\t\t\tself._status_gtk.set(icon, text)\n\t\tif self._status_fb:\n\t\t\tself._status_fb.set(icon, text)\n\t\n\tdef hide(self):\n\t\tif self._status_gtk:\n\t\t\tself._status_gtk.hide()\n\t\tif self._status_fb:\n\t\t\tself._status_fb.hide()\n\t\n\tdef destroy(self):\n\t\tif self._status_gtk:\n\t\t\tself._status_gtk.destroy()\n\t\tif self._status_fb:\n\t\t\tself._status_fb.destroy()\n\t\n\tdef show(self):\n\t\tif self._status_gtk:\n\t\t\tself._status_gtk.show()\n\t\tif self._status_fb:\n\t\t\tself._status_fb.show()\n\ndef get_status_icon(*args, **kwargs):\n\t# Try selecting backend based on environment variable\n\tif \"STATUS_BACKEND\" in os.environ:\n\t\tkwargs[\"force\"] = True\n\t\t\n\t\tstatus_icon_backend_name = \"StatusIcon%s\" % (os.environ.get(\"STATUS_BACKEND\"))\n\t\tif status_icon_backend_name in globals():\n\t\t\ttry:\n\t\t\t\tstatus_icon = globals()[status_icon_backend_name](*args, **kwargs)\n\t\t\t\tlog.info(\"StatusIcon: Using requested backend %s\" % (status_icon_backend_name))\n\t\t\t\treturn status_icon\n\t\t\texcept NotImplementedError:\n\t\t\t\tlog.error(\"StatusIcon: Requested backend %s is not supported\" % (status_icon_backend_name))\n\t\telse:\n\t\t\tlog.error(\"StatusIcon: Requested backend %s does not exist\" % (status_icon_backend_name))\n\t\t\n\t\treturn StatusIconDummy(*args, **kwargs)\n\t\n\t# Use proxy backend to determine the correct backend while the application is running\n\treturn StatusIconProxy(*args, **kwargs)\n\n"
  },
  {
    "path": "scc/gui/svg_widget.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Background\n\nChanges SVG on the fly and uptates that magnificent image on background with it.\nAlso supports clicking on areas defined in SVG image.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gdk, GObject, GdkPixbuf, Rsvg\nfrom xml.etree import ElementTree as ET\nfrom math import sin, cos, pi as PI\nfrom collections import OrderedDict\nimport os, sys, re, logging\nimport importlib\n\n#sys.modules.pop('xml.etree.ElementTree', None)\n#sys.modules['_elementtree'] = None\n#ET = importlib.import_module('xml.etree.ElementTree')\n\nlog = logging.getLogger(\"Background\")\nET.register_namespace('', \"http://www.w3.org/2000/svg\")\n\n\nclass SVGWidget(Gtk.EventBox):\n\tFILENAME = \"background.svg\"\n\tCACHE_SIZE = 50\n\t\n\t__gsignals__ = {\n\t\t\t# Raised when mouse is over defined area\n\t\t\t\"hover\"\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t\t\t# Raised when mouse leaves all defined areas\n\t\t\t\"leave\"\t: (GObject.SignalFlags.RUN_FIRST, None, ()),\n\t\t\t# Raised user clicks on defined area\n\t\t\t\"click\"\t: (GObject.SignalFlags.RUN_FIRST, None, (object,)),\n\t}\n\t\n\t\n\tdef __init__(self, filename, init_hilighted=True):\n\t\tGtk.EventBox.__init__(self)\n\t\tself.cache = OrderedDict()\n\t\tself.areas = []\n\t\t\n\t\tself.connect(\"motion-notify-event\", self.on_mouse_moved)\n\t\tself.connect(\"button-press-event\", self.on_mouse_click)\n\t\tself.set_events(Gdk.EventMask.POINTER_MOTION_MASK | Gdk.EventMask.BUTTON_PRESS_MASK)\n\t\t\n\t\tself.size_override = None\n\t\tself.image_width = 1\n\t\tself.image_height = 1\n\t\tself.set_image(filename)\n\t\tself.image = Gtk.Image()\n\t\tif init_hilighted:\n\t\t\tself.hilight({})\n\t\tself.add(self.image)\n\t\tself.show_all()\n\t\n\t\n\tdef set_image(self, filename):\n\t\tself.current_svg = open(filename, \"r\").read()\n\t\tself.cache = OrderedDict()\n\t\tself.areas = []\n\t\tself.parse_image()\n\t\n\t\n\tdef parse_image(self):\n\t\t\"\"\"\n\t\tGoes trought SVG image, searches for all rects named\n\t\t'AREA_SOMETHING' and generates area list from it.\n\t\tThis area list is later used to determine over which button is mouse\n\t\thovering.\n\t\t\"\"\"\n\t\ttree = ET.fromstring(self.current_svg.encode(\"utf-8\"))\n\t\tSVGWidget.find_areas(tree, None, self.areas)\n\t\tself.image_width =  float(tree.attrib[\"width\"])\n\t\tself.image_height = float(tree.attrib[\"height\"])\n\t\n\t\n\tdef resize(self, width, height):\n\t\t\"\"\"\n\t\tOverrides image size.\n\t\tDoesn't keep aspect ratio and causes cache to be flushed,\n\t\tso this may be slow and nasty.\n\t\t\"\"\"\n\t\tself.size_override = width, height\n\t\tself.cache = OrderedDict()\n\t\n\t\n\tdef on_mouse_click(self, trash, event):\n\t\tarea = self.on_mouse_moved(trash, event)\n\t\tif area is not None:\n\t\t\tself.emit('click', area)\n\t\n\t\n\tdef on_mouse_moved(self, trash, event):\n\t\t\"\"\"\n\t\tNot actual signal handler, just called from App.\n\t\t\"\"\"\n\t\tx_offset = (self.get_allocation().width - self.image_width) / 2\n\t\tx = event.x - x_offset\n\t\ty = event.y\n\t\tfor a in self.areas:\n\t\t\tif a.contains(x, y):\n\t\t\t\tself.emit('hover', a.name)\n\t\t\t\treturn a.name\n\t\tself.emit('leave')\n\t\treturn None\n\t\n\t\n\tdef get_area(self, id):\n\t\tfor a in self.areas:\n\t\t\tif a.name == id:\n\t\t\t\treturn a\n\t\treturn None\n\t\n\t\n\tdef get_all_by_prefix(self, prefix):\n\t\t\"\"\"\n\t\tSearchs for areas using specific prefix.\n\t\tFor prefix \"AREA_\", returns self.areas arrray. For anything else,\n\t\tre-parses current image and searchs recursivelly for anything that matches, so it\n\t\tmay be good idea to not call this too often.\n\t\t\"\"\"\n\t\tif prefix == \"AREA_\":\n\t\t\treturn self.areas\n\t\tlst = []\n\t\ttree = ET.fromstring(self.current_svg.encode(\"utf-8\"))\n\t\tSVGWidget.find_areas(tree, None, lst, prefix=prefix)\n\t\treturn lst\n\t\n\t\n\tdef get_area_position(self, area_id):\n\t\t\"\"\"\n\t\tComputes and returns area position on image as (x, y, width, height).\n\t\tRaises ValueError if such area is not found.\n\t\t\"\"\"\n\t\t# TODO: Maybe cache this?\n\t\ta = self.get_area(area_id)\n\t\tif a:\n\t\t\treturn a.x, a.y, a.w, a.h\n\t\traise ValueError(\"Area '%s' not found\" % (area_id, ))\n\t\n\t\n\t@staticmethod\n\tdef find_areas(xml, parent_transform, areas, get_colors=False, prefix=\"AREA_\"):\n\t\t\"\"\"\n\t\tRecursively searches throught XML for anything with ID of 'AREA_SOMETHING'\n\t\t\"\"\"\n\t\tfor child in xml:\n\t\t\tchild_transform = SVGEditor.matrixmul(\n\t\t\t\tparent_transform or SVGEditor.IDENTITY,\n\t\t\t\tSVGEditor.parse_transform(child))\n\t\t\tif str(child.attrib.get('id')).startswith(prefix):\n\t\t\t\t# log.debug(\"Found SVG area %s\", child.attrib['id'][5:])\n\t\t\t\ta = Area(child, child_transform)\n\t\t\t\tif get_colors:\n\t\t\t\t\ta.color = None\n\t\t\t\t\tif 'style' in child.attrib:\n\t\t\t\t\t\tstyle = { y[0] : y[1] for y in [ x.split(\":\", 1) for x in child.attrib['style'].split(\";\") ] }\n\t\t\t\t\t\tif 'fill' in style:\n\t\t\t\t\t\t\ta.color = SVGWidget.color_to_float(style['fill'])\n\t\t\t\tareas.append(a)\n\t\t\telse:\n\t\t\t\tSVGWidget.find_areas(child, child_transform, areas, get_colors=get_colors, prefix=prefix)\n\t\n\t\n\tdef get_rect_area(self, element):\n\t\t\"\"\"\n\t\tReturns x, y, width and height of rect element relative to document root.\n\t\telement can be specified by it's id.\n\t\t\"\"\"\n\t\tif type(element) == str:\n\t\t\ttree = ET.fromstring(self.current_svg.encode(\"utf-8\"))\n\t\t\t#SVGEditor.update_parents(tree)\n\t\t\telement = SVGEditor.get_element(tree, element)\n\t\twidth, height = 0, 0\n\t\tx, y = SVGEditor.get_translation(element, absolute=True)\n\t\tif 'width' in element.attrib:  width = float(element.attrib['width'])\n\t\tif 'height' in element.attrib: height = float(element.attrib['height'])\n\t\t\n\t\treturn x, y, width, height\n\t\n\t\n\t@staticmethod\n\tdef color_to_float(colorstr):\n\t\t\"\"\"\n\t\tParses color expressed as RRGGBB (as in config) and returns\n\t\tthree floats of r, g, b, a (range 0 to 1)\n\t\t\"\"\"\n\t\tb, color = Gdk.Color.parse(\"#\" + colorstr.strip(\"#\"))\n\t\tif b:\n\t\t\treturn color.red_float, color.green_float, color.blue_float, 1\n\t\treturn 1, 0, 1, 1\t# uggly purple\n\t\n\t\n\tdef hilight(self, buttons):\n\t\t\"\"\" Hilights specified button, if same ID is found in svg \"\"\"\n\t\tcache_id = \"|\".join([ \"%s:%s\" % (x, buttons[x]) for x in buttons ])\n\t\tif not cache_id in self.cache:\n\t\t\t# Ok, this is close to madness, but probably better than drawing\n\t\t\t# 200 images by hand;\n\t\t\tif len(buttons) == 0:\n\t\t\t\t# Quick way out - changes are not needed\n\t\t\t\ttmp = self.current_svg.encode('utf-8') if type(self.current_svg) == str else self.current_svg\n\t\t\t\tsvg = Rsvg.Handle.new_from_data(tmp)\n\t\t\telse:\n\t\t\t\t# 1st, parse source as XML\n\t\t\t\ttree = ET.fromstring(self.current_svg)\n\t\t\t\t# 2nd, change colors of some elements\n\t\t\t\tfor button in buttons:\n\t\t\t\t\tel = SVGEditor.find_by_id(tree, button)\n\t\t\t\t\tif el is not None:\n\t\t\t\t\t\tSVGEditor.recolor(el, buttons[button])\n\t\t\t\t\t\n\t\t\t\t# 3rd, turn it back into XML string......\n\t\t\t\txml = ET.tostring(tree)\n\t\t\t\t\n\t\t\t\t# ... and now, parse that as XML again......\n\t\t\t\tsvg = Rsvg.Handle.new_from_data(xml)\n\t\t\twhile len(self.cache) >= self.CACHE_SIZE:\n\t\t\t\tself.cache.popitem(False)\n\t\t\tif self.size_override:\n\t\t\t\tw, h = self.size_override\n\t\t\t\tself.cache[cache_id] = svg.get_pixbuf().scale_simple(\n\t\t\t\t\t\tw, h, GdkPixbuf.InterpType.BILINEAR)\n\t\t\telse:\n\t\t\t\tself.cache[cache_id] = svg.get_pixbuf()\n\t\t\n\t\tself.image.set_from_pixbuf(self.cache[cache_id])\n\t\n\t\n\tdef get_pixbuf(self):\n\t\t\"\"\" Returns pixbuf of current image \"\"\"\n\t\treturn self.image.get_pixbuf()\n\t\n\t\n\tdef edit(self):\n\t\t\"\"\" Returns new Editor instance bound to this widget \"\"\"\n\t\treturn SVGEditor(self)\n\n\nclass Area:\n\tSPECIAL_CASES = ( \"LSTICK\", \"RSTICK\", \"DPAD\", \"ABS\", \"MOUSE\",\n\t\t\"MINUSHALF\", \"PLUSHALF\", \"KEY\" )\n\t\n\t\"\"\" Basicaly just rectangle with name \"\"\"\n\tdef __init__(self, element, transform):\n\t\tself.name = element.attrib['id'].split(\"_\")[1]\n\t\tif self.name in Area.SPECIAL_CASES:\n\t\t\tself.name = \"_\".join(element.attrib['id'].split(\"_\")[1:3])\n\t\tself.x, self.y = SVGEditor.get_translation(transform)\n\t\tself.w = float(element.attrib.get('width', 0))\n\t\tself.h = float(element.attrib.get('height', 0))\n\t\n\t\n\tdef contains(self, x, y):\n\t\treturn (x >= self.x and y >= self.y \n\t\t\tand x <= self.x + self.w and y <= self.y + self.h)\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Area %s,%s %sx%s>\" % (self.x, self.y, self.w, self.h)\n\n\nclass SVGEditor(object):\n\t\"\"\"\n\tAllows some basic edit operations by parsing SVG into dom tree and doing\n\tunholly mess on that.\n\t\n\tConstructed by SVGWidget.edit(), updates original SVGWidget when commit()\n\tis called.\n\t\"\"\"\n\tRE_PARSE_TRANSFORM = re.compile(r\"([a-z]+)\\(([-0-9\\.,]+)\\)(.*)\")\n\tIDENTITY = ( (1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0) )\n\t\n\tdef __init__(self, svgw):\n\t\tif type(svgw) == str:\n\t\t\tself._svgw = None\n\t\t\tself._tree = ET.fromstring(svgw)\n\t\t#elif type(svgw) == unicode:\n\t\t#\tself._svgw = None\n\t\t#\tself._tree = ET.fromstring(svgw.encode(\"utf-8\"))\n\t\telse:\n\t\t\tself._svgw = svgw\n\t\t\tif type(svgw.current_svg) == bytes:\n\t\t\t\tself._tree = ET.fromstring(svgw.current_svg.decode(\"utf-8\"))\n\t\t\telse:\n\t\t\t\tself._tree = ET.fromstring(svgw.current_svg)\n\t\n\t\n\tdef commit(self):\n\t\t\"\"\"\n\t\tSends modified SVG back to original SVGWidget instance.\n\t\t\n\t\tReturn self.\n\t\t\"\"\"\n\t\tself._svgw.current_svg = ET.tostring(self._tree)\n\t\tself._svgw.cache = OrderedDict()\n\t\tself._svgw.hilight({})\n\t\t\n\t\treturn self\n\t\n\t\n\tdef to_string(self):\n\t\t\"\"\" Returns modivied SVG as string \"\"\"\n\t\treturn ET.tostring(self._tree)\n\t\n\t\n\t@staticmethod\n\tdef _deep_copy(element):\n\t\t\"\"\" Creates deep copy of XML element \"\"\"\n\t\te = element.copy()\n\t\tfor ch in element:\n\t\t\tcopy = SVGEditor._deep_copy(ch)\n\t\t\te.remove(ch)\n\t\t\te.append(copy)\n\t\t\tcopy.parent = e\n\t\treturn e\n\t\n\t\n\tdef clone_element(self, id):\n\t\t\"\"\"\n\t\tGrabs element with specified ID, duplicates it and returns created\n\t\telement. Returned element may get invalidated when commit() is called.\n\t\t\n\t\tReturns None if element cannot be found\n\t\t\"\"\"\n\t\t#SVGEditor.update_parents(self)\n\t\te = SVGEditor.get_element(self, id)\n\t\tif e is not None:\n\t\t\tcopy = SVGEditor._deep_copy(e)\n\t\t\tparent = e.find(\"..\")\n\t\t\tif parent: parent.append(copy)\n\t\t\t#e.parent.append(copy)\n\t\t\t#copy.parent = e.parent\n\t\t\treturn copy\n\t\treturn None\n\t\n\t\n\tdef remove_element(self, e):\n\t\t\"\"\"\n\t\tRemoves element with specified ID, or, if element is passed,\n\t\tremoved that element. If  'id' is None, does nothing.\n\t\t\n\t\tReturns self.\n\t\t\"\"\"\n\t\t\n\t\tif type(e) == str:\n\t\t\te = SVGEditor.get_element(self, e)\n\t\tif e is not None:\n\t\t\te.parent.remove(e)\n\t\treturn self\n\t\n\t\n\tdef keep(self, *ids):\n\t\t\"\"\"\n\t\tRemoves all elements but ones with ID specified.\n\t\tKeeps child elements as well.\n\t\t\n\t\tReturns self.\n\t\t\"\"\"\n\t\t\n\t\tdef recursive(element):\n\t\t\tfor child in list(element):\n\t\t\t\tif (child.tag.endswith(\"metadata\") \n\t\t\t\t\t\tor child.tag.endswith(\"defs\")\n\t\t\t\t\t\tor child.tag.endswith(\"defs\")\n\t\t\t\t\t\tor child.tag.endswith(\"namedview\")\n\t\t\t\t\t):\n\t\t\t\t\trecursive(child)\n\t\t\t\telif child.attrib.get('id') not in ids:\n\t\t\t\t\telement.remove(child)\n\t\t\n\t\t\n\t\trecursive(self._tree)\n\t\treturn self\n\t\n\t\n\t@staticmethod\n\tdef update_parents(tree):\n\t\t\"\"\"\n\t\tEnsures that parent fields of all tree elements are are set.\n\t\t\"\"\"\n\t\tif isinstance(tree, SVGEditor):\n\t\t\ttree = tree._tree\n\t\tdef add_parent(parent):\n\t\t\tfor child in parent:\n\t\t\t\tchild.parent = parent\n\t\t\t\tadd_parent(child)\n\t\tadd_parent(tree)\n\t\tif not hasattr(tree, \"parent\"):\n\t\t\ttree.parent = None\n\t\n\t\n\t@staticmethod\n\tdef get_element(tree, id):\n\t\t\"\"\"\n\t\tRecursively searches throught XML until element with specified ID is found.\n\t\t\n\t\tReturns element or None, if there is not any.\n\t\t\"\"\"\n\t\tif isinstance(tree, SVGEditor):\n\t\t\ttree = tree._tree\n\t\t\n\t\treturn SVGEditor.find_by_id(tree, id)\n\t\n\t\n\t@staticmethod\n\tdef find_by_id(tree, id):\n\t\t\"\"\"\n\t\tRecursively searches throught XML until element with specified ID is found.\n\t\t\n\t\tReturns element or None, if there is not any.\n\t\t\"\"\"\n\t\tfor child in tree:\n\t\t\tif 'id' in child.attrib:\n\t\t\t\tif child.attrib['id'] == id:\n\t\t\t\t\treturn child\n\t\t\tr = SVGEditor.find_by_id(child, id)\n\t\t\tif r is not None:\n\t\t\t\treturn r\n\t\treturn None\t\n\t\n\t\n\t@staticmethod\n\tdef find_by_tag(tree, tag):\n\t\t\"\"\"\n\t\tRecursively searches throught XML until element with specified tag is found.\n\t\t\n\t\tReturns element or None, if there is not any.\n\t\t\"\"\"\n\t\tfor child in tree:\n\t\t\tif child.tag.endswith(tag):\n\t\t\t\treturn child\n\t\t\tr = SVGEditor.find_by_tag(child, tag)\n\t\t\tif r is not None:\n\t\t\t\treturn r\n\t\treturn None\t\n\t\n\t\n\t@staticmethod\n\tdef recolor(element, color):\n\t\t\"\"\"\n\t\tChanges background color of element.\n\t\tIf element is group, descends into first element with fill set.\n\t\t\n\t\tReturns True on success, False if element cannot be recolored.\n\t\t\"\"\"\n\t\tif element.tag.endswith(\"path\") or element.tag.endswith(\"rect\") or element.tag.endswith(\"circle\") or element.tag.endswith(\"ellipse\") or element.tag.endswith(\"text\"):\n\t\t\tif 'style' in element.attrib:\n\t\t\t\tstyle = { y[0] : y[1] for y in [ x.split(\":\", 1) for x in element.attrib['style'].split(\";\") ] }\n\t\t\t\tif 'fill' in style:\n\t\t\t\t\tif len(color.strip(\"#\")) == 8:\n\t\t\t\t\t\tstyle['fill'] = \"#%s\" % (color[-6:],)\n\t\t\t\t\t\talpha = float(int(color.strip(\"#\")[0:2], 16)) / 255.0\n\t\t\t\t\t\tstyle['fill-opacity'] = style['opacity'] = str(alpha)\n\t\t\t\t\telse:\n\t\t\t\t\t\tstyle['fill'] = color\n\t\t\t\t\t\tstyle['fill-opacity'] = style['opacity'] = \"1\"\n\t\t\t\t\telement.attrib['style'] = \";\".join([ \"%s:%s\" % (x, style[x]) for x in style ])\n\t\t\t\t\treturn True\n\t\telif element.tag.endswith(\"g\"):\n\t\t\t# Group, needs to find RECT, CIRCLE or PATH, whatever comes first\n\t\t\tfor child in element:\n\t\t\t\tSVGEditor.recolor(child, color)\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\t@staticmethod\n\tdef _recolor(tree, s_from, s_to):\n\t\t\"\"\" Recursive part of recolor_strokes and recolor_background \"\"\"\n\t\tfor child in tree:\n\t\t\tif 'style' in child.attrib:\n\t\t\t\tif s_from in child.attrib['style']:\n\t\t\t\t\tchild.attrib['style'] = child.attrib['style'].replace(s_from, s_to)\n\t\t\tSVGEditor._recolor(child, s_from, s_to)\n\t\n\t\n\tdef recolor_background(self, change_from, change_to):\n\t\t\"\"\"\n\t\tRecursively travels entire DOM tree and changes every matching\n\t\tbackground color into specified color.\n\t\t\n\t\tReturns self.\n\t\t\"\"\"\n\t\ts_from = \"fill:#%s\" % (change_from,)\n\t\ts_to   = \"fill:#%s\" % (change_to,)\n\t\tSVGEditor._recolor(self._tree, s_from, s_to)\n\t\treturn self\n\t\n\t\n\tdef recolor_strokes(self, change_from, change_to):\n\t\t\"\"\"\n\t\tRecursively travels entire DOM tree and changes every matching\n\t\tline (stroke) color into specified color.\n\t\t\n\t\tReturns self.\n\t\t\"\"\"\n\t\ts_from = \"stroke:#%s\" % (change_from,)\n\t\ts_to   = \"stroke:#%s\" % (change_to,)\n\t\tSVGEditor._recolor(self._tree, s_from, s_to)\n\t\treturn self\n\t\n\t\n\t@staticmethod\n\tdef matrixmul(X, Y, *a):\n\t\tif len(a) > 0:\n\t\t\treturn SVGEditor.matrixmul(SVGEditor.matrixmul(X, Y), a[0], *a[1:])\n\t\treturn [[ sum(a*b for a,b in zip(x,y)) for y in zip(*Y) ] for x in X ]\n\t\n\t\n\t@staticmethod\n\tdef scale(xml, sx, sy=None):\n\t\t\"\"\"\n\t\tChanges element scale.\n\t\tCreates or updates 'transform' attribute.\n\t\t\"\"\"\n\t\tsy = sy or sx\n\t\tSVGEditor.set_transform(xml, SVGEditor.matrixmul(\n\t\t\tSVGEditor.parse_transform(xml),\n\t\t\t[ [ sx, 0.0, 0.0 ], [ 0.0, sy, 0.0 ], [ 0.0, 0.0, 1.0 ] ],\n\t\t))\n\t\n\t\n\t@staticmethod\n\tdef rotate(xml, a, x, y):\n\t\t\"\"\"\n\t\tChanges element rotation.\n\t\tCreates or updates 'transform' attribute.\n\t\t\"\"\"\n\t\ta = a * PI / 180.0\n\t\tSVGEditor.set_transform(xml, SVGEditor.matrixmul(\n\t\t\tSVGEditor.parse_transform(xml),\n\t\t\t[ [ 1.0, 0.0, x ], [ 0.0, 1.0, y ], [ 0.0, 0.0, 1.0 ] ],\n\t\t\t[ [ cos(a), -sin(a), 0 ], [ sin(a), cos(a), 0 ], [ 0.0, 0.0, 1.0 ] ],\n\t\t\t[ [ 1.0, 0.0, -x ], [ 0.0, 1.0, -y ], [ 0.0, 0.0, 1.0 ] ],\n\t\t))\n\t\n\t\n\t@staticmethod\n\tdef translate(xml, x, y):\n\t\t\"\"\"\n\t\tChanges element translation.\n\t\tCreates or updates 'transform' attribute.\n\t\t\"\"\"\n\t\tSVGEditor.set_transform(xml, SVGEditor.matrixmul(\n\t\t\tSVGEditor.parse_transform(xml),\n\t\t\t[ [ 1.0, 0.0, x ], [ 0.0, 1.0, y ], [ 0.0, 0.0, 1.0 ] ],\n\t\t))\n\t\n\t\n\t@staticmethod\n\tdef set_transform(xml, matrix):\n\t\t\"\"\"\n\t\tSets element transformation matrix\n\t\t\"\"\"\n\t\txml.attrib['transform'] = \"matrix(%s,%s,%s,%s,%s,%s)\" % (\n\t\t\tmatrix[0][0], matrix[1][0], matrix[0][1],\n\t\t\tmatrix[1][1], matrix[0][2], matrix[1][2],\n\t\t)\n\t\n\t\n\t@staticmethod\n\tdef get_translation(elm_or_matrix, absolute=False):\n\t\tif isinstance(elm_or_matrix, ET.Element):\n\t\t\telm = elm_or_matrix\n\t\t\tmatrix = SVGEditor.parse_transform(elm)\n\t\t\tparent = elm.find(\"..\")\n\t\t\twhile parent is not None:\n\t\t\t\tmatrix = SVGEditor.matrixmul(matrix, SVGEditor.parse_transform(parent))\n\t\t\t\tparent = parent.find(\"..\")\n\t\telse:\n\t\t\tmatrix = elm_or_matrix\n\n\t\treturn matrix[0][2], matrix[1][2]\n\t\n\t\n\t@staticmethod\n\tdef get_size(elm):\n\t\twidth, height = 1, 1\n\t\tif 'width' in elm.attrib:\n\t\t\twidth = float(elm.attrib['width'])\n\t\tif 'height' in elm.attrib:\n\t\t\theight = float(elm.attrib['height'])\n\t\treturn width, height\n\t\n\t\n\t@staticmethod\n\tdef parse_transform(xml):\n\t\t\"\"\"\n\t\tReturns element transform data in transformation matrix,\n\t\t\"\"\"\n\t\tmatrix = SVGEditor.IDENTITY\n\t\tif 'x' in xml.attrib or 'y' in xml.attrib:\n\t\t\tx = float(xml.attrib.get('x', 0.0))\n\t\t\ty = float(xml.attrib.get('y', 0.0))\n\t\t\t# Assuming matrix is identity matrix here\n\t\t\tmatrix = ((1.0, 0.0, x), (0.0, 1.0, y), (0.0, 0.0, 1.0))\n\t\tif 'transform' in xml.attrib:\n\t\t\ttransform = xml.attrib['transform']\n\t\t\tmatch = SVGEditor.RE_PARSE_TRANSFORM.match(transform.strip())\n\t\t\twhile match:\n\t\t\t\top, values, transform = match.groups()\n\t\t\t\tif op == \"translate\":\n\t\t\t\t\ttranslation = [ float(x) for x in values.split(\",\")[0:2] ]\n\t\t\t\t\twhile len(translation) < 2: translation.append(0.0)\n\t\t\t\t\tx, y = translation\n\t\t\t\t\tmatrix = SVGEditor.matrixmul(matrix, ((1.0, 0.0, x), (0.0, 1.0, y), (0.0, 0.0, 1.0)))\n\t\t\t\telif op == \"rotate\":\n\t\t\t\t\trotation = [ float(x) for x in values.split(\",\")[0:3] ]\n\t\t\t\t\twhile len(rotation) < 3: rotation.append(0.0)\n\t\t\t\t\ta, x, y = rotation\n\t\t\t\t\ta = a * PI / 180.0\n\t\t\t\t\tmatrix = SVGEditor.matrixmul(\n\t\t\t\t\t\tmatrix,\n\t\t\t\t\t\t[ [ 1.0, 0.0, x ], [ 0.0, 1.0, y ], [ 0.0, 0.0, 1.0 ] ],\n\t\t\t\t\t\t[ [ cos(a), -sin(a), 0 ], [ sin(a), cos(a), 0 ], [ 0.0, 0.0, 1.0 ] ],\n\t\t\t\t\t\t[ [ 1.0, 0.0, -x ], [ 0.0, 1.0, -y ], [ 0.0, 0.0, 1.0 ] ],\n\t\t\t\t\t)\n\t\t\t\telif op == \"scale\":\n\t\t\t\t\tscale = tuple([ float(x) for x in values.split(\",\")[0:2] ])\n\t\t\t\t\tif len(scale) == 1:\n\t\t\t\t\t\tsx, sy = scale[0], scale[0]\n\t\t\t\t\telse:\n\t\t\t\t\t\tsx, sy = scale\n\t\t\t\t\tmatrix = SVGEditor.matrixmul(matrix, ((sx, 0.0, 0.0), (0.0, sy, 0.0), (0.0, 0.0, 1.0)))\n\t\t\t\telif op == \"matrix\":\n\t\t\t\t\tm = [ float(x) for x in values.split(\",\") ][0:6]\n\t\t\t\t\twhile len(m) < 6: m.append(0.0)\n\t\t\t\t\ta,b,c,d,e,f = m\n\t\t\t\t\tmatrix = SVGEditor.matrixmul(matrix,\n\t\t\t\t\t\t[ [ a, c, e], [b, d, f], [0, 0, 1] ]\n\t\t\t\t\t)\n\t\t\t\t\n\t\t\t\tmatch = SVGEditor.RE_PARSE_TRANSFORM.match(transform.strip())\n\t\t\n\t\treturn matrix\n\t\n\t\n\t@staticmethod\n\tdef set_text(xml, text):\n\t\thas_valid_children = False\n\t\tfor child in xml:\n\t\t\tif child.tag.endswith(\"text\") or child.tag.endswith(\"tspan\"):\n\t\t\t\thas_valid_children = True\n\t\t\t\tSVGEditor.set_text(child, text)\n\t\tif not has_valid_children:\n\t\t\txml.text = text\n\t\n\t\n\tdef set_labels(self, labels):\n\t\t\"\"\"\n\t\tReplaces text on every element named LABEL_something with coresponding\n\t\tvalue from 'labels' dict.\n\t\t\n\t\tReturns self.\n\t\t\"\"\"\n\t\tdef walk(xml):\n\t\t\tfor child in xml:\n\t\t\t\tif 'id' in child.attrib:\n\t\t\t\t\tif child.attrib['id'].startswith(\"LABEL_\"):\n\t\t\t\t\t\tid = child.attrib['id'][6:]\n\t\t\t\t\t\tif id in labels:\n\t\t\t\t\t\t\tSVGEditor.set_text(child, labels[id])\n\t\t\t\twalk(child)\n\t\t\n\t\twalk(self._tree)\n\t\treturn self\n\t\n\t\n\t@staticmethod\n\tdef add_element(parent, e, **attributes):\n\t\t\"\"\"\n\t\tCreates new element as child of specified parent or, if 1st argument\n\t\tis ET.Element, adds that element.\n\t\t\n\t\tReturns created or passed element.\n\t\t\"\"\"\n\t\tif not isinstance(e, ET.Element):\n\t\t\tattributes = { k : str(attributes[k]) for k in attributes }\n\t\t\te = ET.Element(e, attributes)\n\t\tparent.append(e)\n\t\treturn e\n\t\n\t\n\t@staticmethod\n\tdef load_from_file(filename):\n\t\ttree = ET.fromstring(open(filename, \"r\").read())\n\t\treturn SVGEditor.find_by_tag(tree, \"g\")\n"
  },
  {
    "path": "scc/gui/userdata_manager.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Profile Manager\n\nSimple class that manages stuff related to creating, loading, listing (...) of\nuser-editable data - that are profiles, menus and controller-icons.\n\nMain App class interits from this.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, Gio, GLib\nfrom scc.paths import get_menuicons_path, get_default_menuicons_path\nfrom scc.paths import get_profiles_path, get_default_profiles_path\nfrom scc.paths import get_menus_path, get_default_menus_path\nfrom scc.profile import Profile\nfrom scc.gui.parser import GuiActionParser\n\nimport os, logging\nlog = logging.getLogger(\"UDataManager\")\n\nclass UserDataManager(object):\n\t\n\tdef __init__(self):\n\t\tprofiles_path = get_profiles_path()\n\t\tif not os.path.exists(profiles_path):\n\t\t\tlog.info(\"Creting profile directory '%s'\" % (profiles_path,))\n\t\t\tos.makedirs(profiles_path)\n\t\tmenus_path = get_menus_path()\n\t\tif not os.path.exists(menus_path):\n\t\t\tlog.info(\"Creting menu directory '%s'\" % (menus_path,))\n\t\t\tos.makedirs(menus_path)\n\t\n\t\n\tdef load_profile(self, giofile):\n\t\t\"\"\"\n\t\tLoads profile from 'giofile' into 'profile' object\n\t\tCalls on_profiles_loaded when done\n\t\t\"\"\"\n\t\t# This may get asynchronous later, but that load runs under 1ms...\n\t\tprofile = Profile(GuiActionParser())\n\t\tprofile.load(giofile.get_path())\n\t\tself.on_profile_loaded(profile, giofile)\n\t\n\t\n\tdef save_profile(self, giofile, profile):\n\t\t\"\"\"\n\t\tSaves profile from 'profile' object into 'giofile'.\n\t\tCalls on_profile_saved when done\n\t\t\"\"\"\n\t\t# 1st check, if file is not in /usr/share.\n\t\t# When user tries to save over built-in profile in /usr/share,\n\t\t# new file with same name is created in ~/.config/scc/profiles and profile\n\t\t# is shaved into it.\n\t\t\n\t\tif giofile.get_path().startswith(get_default_profiles_path()):\n\t\t\treturn self._save_profile_local(giofile, profile)\n\t\t\n\t\tprofile.save(giofile.get_path())\n\t\tself.on_profile_saved(giofile)\n\t\n\t\n\tdef _save_profile_local(self, giofile, profile):\n\t\tfilename = os.path.split(giofile.get_path())[-1]\n\t\tlocalpath = os.path.join(get_profiles_path(), filename)\n\t\tgiofile = Gio.File.new_for_path(localpath)\n\t\tself.save_profile(giofile, profile)\n\t\n\t\n\tdef load_profile_list(self, category=None):\n\t\tpaths = [ get_default_profiles_path(), get_profiles_path() ]\n\t\tself.load_user_data(paths, \"*.sccprofile\", category, self.on_profiles_loaded)\n\t\n\t\n\tdef load_menu_list(self, category=None):\n\t\tpaths = [ get_default_menus_path(), get_menus_path() ]\n\t\tself.load_user_data(paths, \"*.menu\", category, self.on_menus_loaded)\n\t\n\t\n\tdef load_menu_icons(self, category=None):\n\t\tpaths = [ get_default_menuicons_path(), get_menuicons_path() ]\n\t\tself.load_user_data(paths, \"*.png\", category, self.on_menuicons_loaded)\n\t\n\t\n\tdef load_user_data(self, paths, pattern, category, callback):\n\t\t\"\"\"\n\t\tLoads data such as of profiles. Uses GLib to do it on background.\n\t\t\"\"\"\n\t\tif category:\n\t\t\tpaths = [ os.path.join(p, category) for p in paths ]\n\t\t\n\t\t# First list is for default stuff, then for user-defined\n\t\t# Number is increased when list is loaded until it reaches 2\n\t\tdata = [ None ] * len(paths)\n\t\t\n\t\tfor i in range(0, len(paths)):\n\t\t\tf = Gio.File.new_for_path(paths[i])\n\t\t\tf.enumerate_children_async(\n\t\t\t\tpattern,\n\t\t\t\tGio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,\n\t\t\t\t1, None, self._on_user_data_loaded,\n\t\t\t\tdata, i, callback\n\t\t\t)\n\t\n\t\n\tdef _on_user_data_loaded(self, pdir, res, data, i, callback):\n\t\t\"\"\"\n\t\tCalled when enumerate_children_async gets lists of files.\n\t\tUsually called twice for default (system) and user directory.\n\t\t\"\"\"\n\t\ttry:\n\t\t\tdata[i] = pdir, pdir.enumerate_children_finish(res)\n\t\texcept Exception as e:\n\t\t\t# Usually when directory doesn't exists\n\t\t\tlog.warning(\"enumerate_children_finish for %s failed: %s\",  pdir.get_path(), e)\n\t\t\tdata[i] = None, []\n\t\tif not None in data:\n\t\t\tfiles = {}\n\t\t\ttry:\n\t\t\t\tfor pdir, enumerator in data:\n\t\t\t\t\tif pdir is not None:\n\t\t\t\t\t\tfor finfo in enumerator:\n\t\t\t\t\t\t\tname = finfo.get_name()\n\t\t\t\t\t\t\tif name and not name.endswith(\"~\"):\n\t\t\t\t\t\t\t\tfiles[name] = pdir.get_child(name)\n\t\t\texcept Exception as e:\n\t\t\t\t# https://github.com/kozec/sc-controller/issues/50\n\t\t\t\tlog.warning(\"enumerate_children_async failed: %s\", e)\n\t\t\t\tfiles = self._sync_load([ pdir for pdir, enumerator in data\n\t\t\t\t\t\t\t\t\t\t\tif pdir is not None])\n\t\t\tif len(files) < 1:\n\t\t\t\t# https://github.com/kozec/sc-controller/issues/327\n\t\t\t\tlog.warning(\"enumerate_children_async returned no files\")\n\t\t\t\tfiles = self._sync_load([ pdir for pdir, enumerator in data\n\t\t\t\t\t\t\t\t\t\t\tif pdir is not None])\n\t\t\t\n\t\t\tcallback(files.values())\n\t\n\t\n\tdef _sync_load(self, pdirs):\n\t\t\"\"\"\n\t\tSynchronous (= UI lagging) fallback method for those (hopefully) rare\n\t\tcases when enumerate_children_finish returns nonsense.\n\t\t\"\"\"\n\t\tfiles = {}\n\t\tfor pdir in pdirs:\n\t\t\tfor name in os.listdir(pdir.get_path()):\n\t\t\t\tfiles[name] = pdir.get_child(name)\n\t\treturn files\n\t\n\t\n\tdef on_menus_loaded(self, menus): # Overriden by subclass\n\t\tpass\n\t\n\t\n\tdef on_profiles_loaded(self, profiles): # Overriden by subclass\n\t\tpass\n\t\n\t\n\tdef on_menuicons_loaded(self, icons): # Overriden by subclass\n\t\tpass\n\t\n\t\n\tdef on_profile_saved(self, giofile): # Overriden in App\n\t\tpass\n\t\n\t\n\tdef on_profile_loaded(self, profile, giofile): # Overriden in App\n\t\tpass\n"
  },
  {
    "path": "scc/lib/__init__.py",
    "content": "#!/usr/bin/env python2\n\nfrom .enum import Enum, IntEnum, unique\n"
  },
  {
    "path": "scc/lib/daemon.py",
    "content": "#!/usr/bin/env python2\n\n\"\"\"Generic linux daemon base class\"\"\"\n\n# Adapted from http://www.jejik.com/files/examples/daemon3x.py\n# thanks to the original author\n\nimport sys\nimport os\nimport time\nimport atexit\nimport signal\nimport syslog\n\nclass Daemon(object):\n\t\"\"\"A generic daemon class.\n\n\tUsage: subclass the daemon class and override the run() method.\"\"\"\n\n\tdef __init__(self, pidfile):\n\t\tself.pidfile = pidfile\n\n\tdef daemonize(self):\n\t\t\"\"\"Deamonize class. UNIX double fork mechanism.\"\"\"\n\n\t\ttry:\n\t\t\tpid = os.fork()\n\t\t\tif pid > 0:\n\t\t\t\t# exit first parent\n\t\t\t\tsys.exit(0)\n\t\texcept OSError as err:\n\t\t\tsys.stderr.write('fork #1 failed: {0}\\n'.format(err))\n\t\t\tsys.exit(1)\n\n\t\t# decouple from parent environment\n\t\tos.chdir('/')\n\t\tos.setsid()\n\t\tos.umask(0)\n\n\t\t# do second fork\n\t\ttry:\n\t\t\tpid = os.fork()\n\t\t\tif pid > 0:\n\n\t\t\t\t# exit from second parent\n\t\t\t\tsys.exit(0)\n\t\texcept OSError as err:\n\t\t\tsys.stderr.write('fork #2 failed: {0}\\n'.format(err))\n\t\t\tsys.exit(1)\n\n\t\t# redirect standard file descriptors\n\t\tsys.stdout.flush()\n\t\tsys.stderr.flush()\n\t\tstdi = open(os.devnull, 'r')\n\t\tstdo = open(os.devnull, 'a+')\n\t\tstde = open(os.devnull, 'a+')\n\n\t\tos.dup2(stdi.fileno(), sys.stdin.fileno())\n\t\tos.dup2(stdo.fileno(), sys.stdout.fileno())\n\t\tos.dup2(stde.fileno(), sys.stderr.fileno())\n\n\t\t# write pidfile\n\t\tself.write_pid()\n\n\tdef write_pid(self):\n\t\t\"\"\"Write pid file\"\"\"\n\t\tatexit.register(self.delpid)\n\n\t\tpid = str(os.getpid())\n\t\twith open(self.pidfile, 'w+') as fd:\n\t\t\tfd.write(pid + '\\n')\n\n\tdef delpid(self):\n\t\t\"\"\"Delete pid file\"\"\"\n\t\tos.remove(self.pidfile)\n\n\tdef start(self):\n\t\t\"\"\"Start the daemon.\"\"\"\n\n\t\t# Check for a pidfile to see if the daemon already runs\n\t\ttry:\n\t\t\twith open(self.pidfile, 'r') as pidf:\n\t\t\t\tpid = int(pidf.read().strip())\n\t\texcept Exception:\n\t\t\tpid = None\n\n\t\tif pid:\n\t\t\t# Check if PID coresponds to running daemon process and fail if yes\n\t\t\ttry:\n\t\t\t\tassert os.path.exists(\"/proc\")\t# Just in case of BSD...\n\t\t\t\tcmdline = open(\"/proc/%s/cmdline\" % (pid,), \"r\").read().replace(\"\\x00\", \" \").strip()\n\t\t\t\tif sys.argv[0] in cmdline:\n\t\t\t\t\traise Exception(\"already running\")\n\t\t\texcept IOError:\n\t\t\t\t# No such process\n\t\t\t\tpass\n\t\t\texcept:\n\t\t\t\tmessage = \"pidfile {0} already exist. \" + \\\n\t\t\t\t\t\t\"Daemon already running?\\n\"\n\t\t\t\tsys.stderr.write(message.format(self.pidfile))\n\t\t\t\tsys.exit(1)\n\n\t\t\tsys.stderr.write(\"Overwriting stale pidfile\\n\")\n\n\t\t# Start the daemon\n\t\tself.daemonize()\n\t\tsyslog.syslog(syslog.LOG_INFO, '{}: started'.format(os.path.basename(sys.argv[0])))\n\t\tself.on_start()\n\t\twhile True:\n\t\t\ttry:\n\t\t\t\tself.run()\n\t\t\texcept Exception as e: # pylint: disable=W0703\n\t\t\t\tsyslog.syslog(syslog.LOG_ERR, '{}: {!s}'.format(os.path.basename(sys.argv[0]), e))\n\t\t\ttime.sleep(2)\n\n\tdef on_start(self):\n\t\tpass\n\t\n\tdef stop(self, once=False):\n\t\t\"\"\"Stop the daemon.\"\"\"\n\n\t\t# Get the pid from the pidfile\n\t\ttry:\n\t\t\twith open(self.pidfile, 'r') as pidf:\n\t\t\t\tpid = int(pidf.read().strip())\n\t\texcept Exception:\n\t\t\tpid = None\n\n\t\tif not pid:\n\t\t\tmessage = \"pidfile {0} does not exist. \" + \\\n\t\t\t\t\t\"Daemon not running?\\n\"\n\t\t\tsys.stderr.write(message.format(self.pidfile))\n\t\t\treturn # not an error in a restart\n\n\t\t# Try killing the daemon process\n\t\ttry:\n\t\t\tfor x in range(0, 10): # Waits max 1s\n\t\t\t\tos.kill(pid, signal.SIGTERM)\n\t\t\t\tif once: break\n\t\t\t\tfor x in range(50):\n\t\t\t\t\tos.kill(pid, 0)\n\t\t\t\t\ttime.sleep(0.1)\n\t\t\t\ttime.sleep(0.1)\n\t\t\tos.kill(pid, signal.SIGKILL)\n\t\texcept OSError as err:\n\t\t\te = str(err.args)\n\t\t\tif e.find(\"No such process\") > 0:\n\t\t\t\tif os.path.exists(self.pidfile):\n\t\t\t\t\tos.remove(self.pidfile)\n\t\t\telse:\n\t\t\t\tprint(str(err.args))\n\t\t\t\tsys.exit(1)\n\t\tsyslog.syslog(syslog.LOG_INFO, '{}: stopped'.format(os.path.basename(sys.argv[0])))\n\n\tdef restart(self):\n\t\t\"\"\"Restart the daemon.\"\"\"\n\t\tself.stop()\n\t\ttime.sleep(2)\n\t\tself.start()\n\n\tdef run(self):\n\t\t\"\"\"You should override this method when you subclass Daemon.\n\n\t\tIt will be called after the process has been daemonized by\n\t\tstart() or restart().\"\"\"\n"
  },
  {
    "path": "scc/lib/enum.py",
    "content": "\"\"\"Python Enumerations\"\"\"\n\n\"\"\"\nCopyright (c) 2013, Ethan Furman.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n    Redistributions of source code must retain the above\n    copyright notice, this list of conditions and the\n    following disclaimer.\n\n    Redistributions in binary form must reproduce the above\n    copyright notice, this list of conditions and the following\n    disclaimer in the documentation and/or other materials\n    provided with the distribution.\n\n    Neither the name Ethan Furman nor the names of any\n    contributors may be used to endorse or promote products\n    derived from this software without specific prior written\n    permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n\"\"\"\n\nimport sys as _sys\n\n__all__ = ['Enum', 'IntEnum', 'unique']\n\nversion = 1, 1, 3\n\npyver = float('%s.%s' % _sys.version_info[:2])\n\ntry:\n    any\nexcept NameError:\n    def any(iterable):\n        for element in iterable:\n            if element:\n                return True\n        return False\n\ntry:\n    from collections import OrderedDict\nexcept ImportError:\n    OrderedDict = None\n\ntry:\n    basestring\nexcept NameError:\n    # In Python 2 basestring is the ancestor of both str and unicode\n    # in Python 3 it's just str, but was missing in 3.1\n    basestring = str\n\ntry:\n    unicode\nexcept NameError:\n    # In Python 3 unicode no longer exists (it's just str)\n    unicode = str\n\nclass _RouteClassAttributeToGetattr(object):\n    \"\"\"Route attribute access on a class to __getattr__.\n\n    This is a descriptor, used to define attributes that act differently when\n    accessed through an instance and through a class.  Instance access remains\n    normal, but access to an attribute through a class will be routed to the\n    class's __getattr__ method; this is done by raising AttributeError.\n\n    \"\"\"\n    def __init__(self, fget=None):\n        self.fget = fget\n\n    def __get__(self, instance, ownerclass=None):\n        if instance is None:\n            raise AttributeError()\n        return self.fget(instance)\n\n    def __set__(self, instance, value):\n        raise AttributeError(\"can't set attribute\")\n\n    def __delete__(self, instance):\n        raise AttributeError(\"can't delete attribute\")\n\n\ndef _is_descriptor(obj):\n    \"\"\"Returns True if obj is a descriptor, False otherwise.\"\"\"\n    return (\n            hasattr(obj, '__get__') or\n            hasattr(obj, '__set__') or\n            hasattr(obj, '__delete__'))\n\n\ndef _is_dunder(name):\n    \"\"\"Returns True if a __dunder__ name, False otherwise.\"\"\"\n    return (name[:2] == name[-2:] == '__' and\n            name[2:3] != '_' and\n            name[-3:-2] != '_' and\n            len(name) > 4)\n\n\ndef _is_sunder(name):\n    \"\"\"Returns True if a _sunder_ name, False otherwise.\"\"\"\n    return (name[0] == name[-1] == '_' and\n            name[1:2] != '_' and\n            name[-2:-1] != '_' and\n            len(name) > 2)\n\n\ndef _make_class_unpicklable(cls):\n    \"\"\"Make the given class un-picklable.\"\"\"\n    def _break_on_call_reduce(self, protocol=None):\n        raise TypeError('%r cannot be pickled' % self)\n    cls.__reduce_ex__ = _break_on_call_reduce\n    cls.__module__ = '<unknown>'\n\n\nclass _EnumDict(dict):\n    \"\"\"Track enum member order and ensure member names are not reused.\n\n    EnumMeta will use the names found in self._member_names as the\n    enumeration member names.\n\n    \"\"\"\n    def __init__(self):\n        super(_EnumDict, self).__init__()\n        self._member_names = []\n\n    def __setitem__(self, key, value):\n        \"\"\"Changes anything not dundered or not a descriptor.\n\n        If a descriptor is added with the same name as an enum member, the name\n        is removed from _member_names (this may leave a hole in the numerical\n        sequence of values).\n\n        If an enum member name is used twice, an error is raised; duplicate\n        values are not checked for.\n\n        Single underscore (sunder) names are reserved.\n\n        Note:   in 3.x __order__ is simply discarded as a not necessary piece\n                leftover from 2.x\n\n        \"\"\"\n        if pyver >= 3.0 and key == '__order__':\n                return\n        if _is_sunder(key):\n            raise ValueError('_names_ are reserved for future Enum use')\n        elif _is_dunder(key):\n            pass\n        elif key in self._member_names:\n            # descriptor overwriting an enum?\n            raise TypeError('Attempted to reuse key: %r' % key)\n        elif not _is_descriptor(value):\n            if key in self:\n                # enum overwriting a descriptor?\n                raise TypeError('Key already defined as: %r' % self[key])\n            self._member_names.append(key)\n        super(_EnumDict, self).__setitem__(key, value)\n\n\n# Dummy value for Enum as EnumMeta explicity checks for it, but of course until\n# EnumMeta finishes running the first time the Enum class doesn't exist.  This\n# is also why there are checks in EnumMeta like `if Enum is not None`\nEnum = None\n\n\nclass EnumMeta(type):\n    \"\"\"Metaclass for Enum\"\"\"\n    @classmethod\n    def __prepare__(metacls, cls, bases):\n        return _EnumDict()\n\n    def __new__(metacls, cls, bases, classdict):\n        # an Enum class is final once enumeration items have been defined; it\n        # cannot be mixed with other types (int, float, etc.) if it has an\n        # inherited __new__ unless a new __new__ is defined (or the resulting\n        # class will fail).\n        if type(classdict) is dict:\n            original_dict = classdict\n            classdict = _EnumDict()\n            for k, v in original_dict.items():\n                classdict[k] = v\n\n        member_type, first_enum = metacls._get_mixins_(bases)\n        __new__, save_new, use_args = metacls._find_new_(classdict, member_type,\n                                                        first_enum)\n        # save enum items into separate mapping so they don't get baked into\n        # the new class\n        members = dict((k, classdict[k]) for k in classdict._member_names)\n        for name in classdict._member_names:\n            del classdict[name]\n\n        # py2 support for definition order\n        __order__ = classdict.get('__order__')\n        if __order__ is None:\n            if pyver < 3.0:\n                try:\n                    __order__ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])]\n                except TypeError:\n                    __order__ = [name for name in sorted(members.keys())]\n            else:\n                __order__ = classdict._member_names\n        else:\n            del classdict['__order__']\n            if pyver < 3.0:\n                __order__ = __order__.replace(',', ' ').split()\n                aliases = [name for name in members if name not in __order__]\n                __order__ += aliases\n\n        # check for illegal enum names (any others?)\n        invalid_names = set(members) & set(['mro'])\n        if invalid_names:\n            raise ValueError('Invalid enum member name(s): %s' % (\n                ', '.join(invalid_names), ))\n\n        # save attributes from super classes so we know if we can take\n        # the shortcut of storing members in the class dict\n        base_attributes = set([a for b in bases for a in b.__dict__])\n        # create our new Enum type\n        enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict)\n        enum_class._member_names_ = []               # names in random order\n        if OrderedDict is not None:\n            enum_class._member_map_ = OrderedDict()\n        else:\n            enum_class._member_map_ = {}             # name->value map\n        enum_class._member_type_ = member_type\n\n        # Reverse value->name map for hashable values.\n        enum_class._value2member_map_ = {}\n\n        # instantiate them, checking for duplicates as we go\n        # we instantiate first instead of checking for duplicates first in case\n        # a custom __new__ is doing something funky with the values -- such as\n        # auto-numbering ;)\n        if __new__ is None:\n            __new__ = enum_class.__new__\n        for member_name in __order__:\n            value = members[member_name]\n            if not isinstance(value, tuple):\n                args = (value, )\n            else:\n                args = value\n            if member_type is tuple:   # special case for tuple enums\n                args = (args, )     # wrap it one more time\n            if not use_args or not args:\n                enum_member = __new__(enum_class)\n                if not hasattr(enum_member, '_value_'):\n                    enum_member._value_ = value\n            else:\n                enum_member = __new__(enum_class, *args)\n                if not hasattr(enum_member, '_value_'):\n                    enum_member._value_ = member_type(*args)\n            value = enum_member._value_\n            enum_member._name_ = member_name\n            enum_member.__objclass__ = enum_class\n            enum_member.__init__(*args)\n            # If another member with the same value was already defined, the\n            # new member becomes an alias to the existing one.\n            for name, canonical_member in enum_class._member_map_.items():\n                if canonical_member.value == enum_member._value_:\n                    enum_member = canonical_member\n                    break\n            else:\n                # Aliases don't appear in member names (only in __members__).\n                enum_class._member_names_.append(member_name)\n            # performance boost for any member that would not shadow\n            # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr)\n            if member_name not in base_attributes:\n                setattr(enum_class, member_name, enum_member)\n            # now add to _member_map_\n            enum_class._member_map_[member_name] = enum_member\n            try:\n                # This may fail if value is not hashable. We can't add the value\n                # to the map, and by-value lookups for this value will be\n                # linear.\n                enum_class._value2member_map_[value] = enum_member\n            except TypeError:\n                pass\n\n\n        # If a custom type is mixed into the Enum, and it does not know how\n        # to pickle itself, pickle.dumps will succeed but pickle.loads will\n        # fail.  Rather than have the error show up later and possibly far\n        # from the source, sabotage the pickle protocol for this class so\n        # that pickle.dumps also fails.\n        #\n        # However, if the new class implements its own __reduce_ex__, do not\n        # sabotage -- it's on them to make sure it works correctly.  We use\n        # __reduce_ex__ instead of any of the others as it is preferred by\n        # pickle over __reduce__, and it handles all pickle protocols.\n        unpicklable = False\n        if '__reduce_ex__' not in classdict:\n            if member_type is not object:\n                methods = ('__getnewargs_ex__', '__getnewargs__',\n                        '__reduce_ex__', '__reduce__')\n                if not any(m in member_type.__dict__ for m in methods):\n                    _make_class_unpicklable(enum_class)\n                    unpicklable = True\n\n\n        # double check that repr and friends are not the mixin's or various\n        # things break (such as pickle)\n        for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):\n            class_method = getattr(enum_class, name)\n            obj_method = getattr(member_type, name, None)\n            enum_method = getattr(first_enum, name, None)\n            if name not in classdict and class_method is not enum_method:\n                if name == '__reduce_ex__' and unpicklable:\n                    continue\n                setattr(enum_class, name, enum_method)\n\n        # method resolution and int's are not playing nice\n        # Python's less than 2.6 use __cmp__\n\n        if pyver < 2.6:\n\n            if issubclass(enum_class, int):\n                setattr(enum_class, '__cmp__', getattr(int, '__cmp__'))\n\n        elif pyver < 3.0:\n\n            if issubclass(enum_class, int):\n                for method in (\n                        '__le__',\n                        '__lt__',\n                        '__gt__',\n                        '__ge__',\n                        '__eq__',\n                        '__ne__',\n                        '__hash__',\n                        ):\n                    setattr(enum_class, method, getattr(int, method))\n\n        # replace any other __new__ with our own (as long as Enum is not None,\n        # anyway) -- again, this is to support pickle\n        if Enum is not None:\n            # if the user defined their own __new__, save it before it gets\n            # clobbered in case they subclass later\n            if save_new:\n                setattr(enum_class, '__member_new__', enum_class.__dict__['__new__'])\n            setattr(enum_class, '__new__', Enum.__dict__['__new__'])\n        return enum_class\n\n    def __bool__(cls):\n        \"\"\"\n        classes/types should always be True.\n        \"\"\"\n        return True\n\n    def __call__(cls, value, names=None, module=None, type=None, start=1):\n        \"\"\"Either returns an existing member, or creates a new enum class.\n\n        This method is used both when an enum class is given a value to match\n        to an enumeration member (i.e. Color(3)) and for the functional API\n        (i.e. Color = Enum('Color', names='red green blue')).\n\n        When used for the functional API: `module`, if set, will be stored in\n        the new class' __module__ attribute; `type`, if set, will be mixed in\n        as the first base class.\n\n        Note: if `module` is not set this routine will attempt to discover the\n        calling module by walking the frame stack; if this is unsuccessful\n        the resulting class will not be pickleable.\n\n        \"\"\"\n        if names is None:  # simple value lookup\n            return cls.__new__(cls, value)\n        # otherwise, functional API: we're creating a new Enum type\n        return cls._create_(value, names, module=module, type=type, start=start)\n\n    def __contains__(cls, member):\n        return isinstance(member, cls) and member.name in cls._member_map_\n\n    def __delattr__(cls, attr):\n        # nicer error message when someone tries to delete an attribute\n        # (see issue19025).\n        if attr in cls._member_map_:\n            raise AttributeError(\n                    \"%s: cannot delete Enum member.\" % cls.__name__)\n        super(EnumMeta, cls).__delattr__(attr)\n\n    def __dir__(self):\n        return (['__class__', '__doc__', '__members__', '__module__'] +\n                self._member_names_)\n\n    @property\n    def __members__(cls):\n        \"\"\"Returns a mapping of member name->value.\n\n        This mapping lists all enum members, including aliases. Note that this\n        is a copy of the internal mapping.\n\n        \"\"\"\n        return cls._member_map_.copy()\n\n    def __getattr__(cls, name):\n        \"\"\"Return the enum member matching `name`\n\n        We use __getattr__ instead of descriptors or inserting into the enum\n        class' __dict__ in order to support `name` and `value` being both\n        properties for enum members (which live in the class' __dict__) and\n        enum members themselves.\n\n        \"\"\"\n        if _is_dunder(name):\n            raise AttributeError(name)\n        try:\n            return cls._member_map_[name]\n        except KeyError:\n            raise AttributeError(name)\n\n    def __getitem__(cls, name):\n        return cls._member_map_[name]\n\n    def __iter__(cls):\n        return (cls._member_map_[name] for name in cls._member_names_)\n\n    def __reversed__(cls):\n        return (cls._member_map_[name] for name in reversed(cls._member_names_))\n\n    def __len__(cls):\n        return len(cls._member_names_)\n\n    __nonzero__ = __bool__\n\n    def __repr__(cls):\n        return \"<enum %r>\" % cls.__name__\n\n    def __setattr__(cls, name, value):\n        \"\"\"Block attempts to reassign Enum members.\n\n        A simple assignment to the class namespace only changes one of the\n        several possible ways to get an Enum member from the Enum class,\n        resulting in an inconsistent Enumeration.\n\n        \"\"\"\n        member_map = cls.__dict__.get('_member_map_', {})\n        if name in member_map:\n            raise AttributeError('Cannot reassign members.')\n        super(EnumMeta, cls).__setattr__(name, value)\n\n    def _create_(cls, class_name, names=None, module=None, type=None, start=1):\n        \"\"\"Convenience method to create a new Enum class.\n\n        `names` can be:\n\n        * A string containing member names, separated either with spaces or\n          commas.  Values are auto-numbered from 1.\n        * An iterable of member names.  Values are auto-numbered from 1.\n        * An iterable of (member name, value) pairs.\n        * A mapping of member name -> value.\n\n        \"\"\"\n        if pyver < 3.0:\n            # if class_name is unicode, attempt a conversion to ASCII\n            if isinstance(class_name, unicode):\n                try:\n                    class_name = class_name.encode('ascii')\n                except UnicodeEncodeError:\n                    raise TypeError('%r is not representable in ASCII' % class_name)\n        metacls = cls.__class__\n        if type is None:\n            bases = (cls, )\n        else:\n            bases = (type, cls)\n        classdict = metacls.__prepare__(class_name, bases)\n        __order__ = []\n\n        # special processing needed for names?\n        if isinstance(names, basestring):\n            names = names.replace(',', ' ').split()\n        if isinstance(names, (tuple, list)) and isinstance(names[0], basestring):\n            names = [(e, i+start) for (i, e) in enumerate(names)]\n\n        # Here, names is either an iterable of (name, value) or a mapping.\n        item = None  # in case names is empty\n        for item in names:\n            if isinstance(item, basestring):\n                member_name, member_value = item, names[item]\n            else:\n                member_name, member_value = item\n            classdict[member_name] = member_value\n            __order__.append(member_name)\n        # only set __order__ in classdict if name/value was not from a mapping\n        if not isinstance(item, basestring):\n            classdict['__order__'] = ' '.join(__order__)\n        enum_class = metacls.__new__(metacls, class_name, bases, classdict)\n\n        # TODO: replace the frame hack if a blessed way to know the calling\n        # module is ever developed\n        if module is None:\n            try:\n                module = _sys._getframe(2).f_globals['__name__']\n            except (AttributeError, ValueError):\n                pass\n        if module is None:\n            _make_class_unpicklable(enum_class)\n        else:\n            enum_class.__module__ = module\n\n        return enum_class\n\n    @staticmethod\n    def _get_mixins_(bases):\n        \"\"\"Returns the type for creating enum members, and the first inherited\n        enum class.\n\n        bases: the tuple of bases that was given to __new__\n\n        \"\"\"\n        if not bases or Enum is None:\n            return object, Enum\n\n\n        # double check that we are not subclassing a class with existing\n        # enumeration members; while we're at it, see if any other data\n        # type has been mixed in so we can use the correct __new__\n        member_type = first_enum = None\n        for base in bases:\n            if  (base is not Enum and\n                    issubclass(base, Enum) and\n                    base._member_names_):\n                raise TypeError(\"Cannot extend enumerations\")\n        # base is now the last base in bases\n        if not issubclass(base, Enum):\n            raise TypeError(\"new enumerations must be created as \"\n                    \"`ClassName([mixin_type,] enum_type)`\")\n\n        # get correct mix-in type (either mix-in type of Enum subclass, or\n        # first base if last base is Enum)\n        if not issubclass(bases[0], Enum):\n            member_type = bases[0]     # first data type\n            first_enum = bases[-1]  # enum type\n        else:\n            for base in bases[0].__mro__:\n                # most common: (IntEnum, int, Enum, object)\n                # possible:    (<Enum 'AutoIntEnum'>, <Enum 'IntEnum'>,\n                #               <class 'int'>, <Enum 'Enum'>,\n                #               <class 'object'>)\n                if issubclass(base, Enum):\n                    if first_enum is None:\n                        first_enum = base\n                else:\n                    if member_type is None:\n                        member_type = base\n\n        return member_type, first_enum\n\n    if pyver < 3.0:\n        @staticmethod\n        def _find_new_(classdict, member_type, first_enum):\n            \"\"\"Returns the __new__ to be used for creating the enum members.\n\n            classdict: the class dictionary given to __new__\n            member_type: the data type whose __new__ will be used by default\n            first_enum: enumeration to check for an overriding __new__\n\n            \"\"\"\n            # now find the correct __new__, checking to see of one was defined\n            # by the user; also check earlier enum classes in case a __new__ was\n            # saved as __member_new__\n            __new__ = classdict.get('__new__', None)\n            if __new__:\n                return None, True, True      # __new__, save_new, use_args\n\n            N__new__ = getattr(None, '__new__')\n            O__new__ = getattr(object, '__new__')\n            if Enum is None:\n                E__new__ = N__new__\n            else:\n                E__new__ = Enum.__dict__['__new__']\n            # check all possibles for __member_new__ before falling back to\n            # __new__\n            for method in ('__member_new__', '__new__'):\n                for possible in (member_type, first_enum):\n                    try:\n                        target = possible.__dict__[method]\n                    except (AttributeError, KeyError):\n                        target = getattr(possible, method, None)\n                    if target not in [\n                            None,\n                            N__new__,\n                            O__new__,\n                            E__new__,\n                            ]:\n                        if method == '__member_new__':\n                            classdict['__new__'] = target\n                            return None, False, True\n                        if isinstance(target, staticmethod):\n                            target = target.__get__(member_type)\n                        __new__ = target\n                        break\n                if __new__ is not None:\n                    break\n            else:\n                __new__ = object.__new__\n\n            # if a non-object.__new__ is used then whatever value/tuple was\n            # assigned to the enum member name will be passed to __new__ and to the\n            # new enum member's __init__\n            if __new__ is object.__new__:\n                use_args = False\n            else:\n                use_args = True\n\n            return __new__, False, use_args\n    else:\n        @staticmethod\n        def _find_new_(classdict, member_type, first_enum):\n            \"\"\"Returns the __new__ to be used for creating the enum members.\n\n            classdict: the class dictionary given to __new__\n            member_type: the data type whose __new__ will be used by default\n            first_enum: enumeration to check for an overriding __new__\n\n            \"\"\"\n            # now find the correct __new__, checking to see of one was defined\n            # by the user; also check earlier enum classes in case a __new__ was\n            # saved as __member_new__\n            __new__ = classdict.get('__new__', None)\n\n            # should __new__ be saved as __member_new__ later?\n            save_new = __new__ is not None\n\n            if __new__ is None:\n                # check all possibles for __member_new__ before falling back to\n                # __new__\n                for method in ('__member_new__', '__new__'):\n                    for possible in (member_type, first_enum):\n                        target = getattr(possible, method, None)\n                        if target not in (\n                                None,\n                                None.__new__,\n                                object.__new__,\n                                Enum.__new__,\n                                ):\n                            __new__ = target\n                            break\n                    if __new__ is not None:\n                        break\n                else:\n                    __new__ = object.__new__\n\n            # if a non-object.__new__ is used then whatever value/tuple was\n            # assigned to the enum member name will be passed to __new__ and to the\n            # new enum member's __init__\n            if __new__ is object.__new__:\n                use_args = False\n            else:\n                use_args = True\n\n            return __new__, save_new, use_args\n\n\n########################################################\n# In order to support Python 2 and 3 with a single\n# codebase we have to create the Enum methods separately\n# and then use the `type(name, bases, dict)` method to\n# create the class.\n########################################################\ntemp_enum_dict = {}\ntemp_enum_dict['__doc__'] = \"Generic enumeration.\\n\\n    Derive from this class to define new enumerations.\\n\\n\"\n\ndef __new__(cls, value):\n    # all enum instances are actually created during class construction\n    # without calling this method; this method is called by the metaclass'\n    # __call__ (i.e. Color(3) ), and by pickle\n    if type(value) is cls:\n        # For lookups like Color(Color.red)\n        value = value.value\n        #return value\n    # by-value search for a matching enum member\n    # see if it's in the reverse mapping (for hashable values)\n    try:\n        if value in cls._value2member_map_:\n            return cls._value2member_map_[value]\n    except TypeError:\n        # not there, now do long search -- O(n) behavior\n        for member in cls._member_map_.values():\n            if member.value == value:\n                return member\n    raise ValueError(\"%s is not a valid %s\" % (value, cls.__name__))\ntemp_enum_dict['__new__'] = __new__\ndel __new__\n\ndef __repr__(self):\n    return \"<%s.%s: %r>\" % (\n            self.__class__.__name__, self._name_, self._value_)\ntemp_enum_dict['__repr__'] = __repr__\ndel __repr__\n\ndef __str__(self):\n    return \"%s.%s\" % (self.__class__.__name__, self._name_)\ntemp_enum_dict['__str__'] = __str__\ndel __str__\n\nif pyver >= 3.0:\n    def __dir__(self):\n        added_behavior = [\n                m\n                for cls in self.__class__.mro()\n                for m in cls.__dict__\n                if m[0] != '_' and m not in self._member_map_\n                ]\n        return (['__class__', '__doc__', '__module__', ] + added_behavior)\n    temp_enum_dict['__dir__'] = __dir__\n    del __dir__\n\ndef __format__(self, format_spec):\n    # mixed-in Enums should use the mixed-in type's __format__, otherwise\n    # we can get strange results with the Enum name showing up instead of\n    # the value\n\n    # pure Enum branch\n    if self._member_type_ is object:\n        cls = str\n        val = str(self)\n    # mix-in branch\n    else:\n        cls = self._member_type_\n        val = self.value\n    return cls.__format__(val, format_spec)\ntemp_enum_dict['__format__'] = __format__\ndel __format__\n\n\n####################################\n# Python's less than 2.6 use __cmp__\n\nif pyver < 2.6:\n\n    def __cmp__(self, other):\n        if type(other) is self.__class__:\n            if self is other:\n                return 0\n            return -1\n        return NotImplemented\n        raise TypeError(\"unorderable types: %s() and %s()\" % (self.__class__.__name__, other.__class__.__name__))\n    temp_enum_dict['__cmp__'] = __cmp__\n    del __cmp__\n\nelse:\n\n    def __le__(self, other):\n        raise TypeError(\"unorderable types: %s() <= %s()\" % (self.__class__.__name__, other.__class__.__name__))\n    temp_enum_dict['__le__'] = __le__\n    del __le__\n\n    def __lt__(self, other):\n        raise TypeError(\"unorderable types: %s() < %s()\" % (self.__class__.__name__, other.__class__.__name__))\n    temp_enum_dict['__lt__'] = __lt__\n    del __lt__\n\n    def __ge__(self, other):\n        raise TypeError(\"unorderable types: %s() >= %s()\" % (self.__class__.__name__, other.__class__.__name__))\n    temp_enum_dict['__ge__'] = __ge__\n    del __ge__\n\n    def __gt__(self, other):\n        raise TypeError(\"unorderable types: %s() > %s()\" % (self.__class__.__name__, other.__class__.__name__))\n    temp_enum_dict['__gt__'] = __gt__\n    del __gt__\n\n\ndef __eq__(self, other):\n    if type(other) is self.__class__:\n        return self is other\n    return NotImplemented\ntemp_enum_dict['__eq__'] = __eq__\ndel __eq__\n\ndef __ne__(self, other):\n    if type(other) is self.__class__:\n        return self is not other\n    return NotImplemented\ntemp_enum_dict['__ne__'] = __ne__\ndel __ne__\n\ndef __hash__(self):\n    return hash(self._name_)\ntemp_enum_dict['__hash__'] = __hash__\ndel __hash__\n\ndef __reduce_ex__(self, proto):\n    return self.__class__, (self._value_, )\ntemp_enum_dict['__reduce_ex__'] = __reduce_ex__\ndel __reduce_ex__\n\n# _RouteClassAttributeToGetattr is used to provide access to the `name`\n# and `value` properties of enum members while keeping some measure of\n# protection from modification, while still allowing for an enumeration\n# to have members named `name` and `value`.  This works because enumeration\n# members are not set directly on the enum class -- __getattr__ is\n# used to look them up.\n\n@_RouteClassAttributeToGetattr\ndef name(self):\n    return self._name_\ntemp_enum_dict['name'] = name\ndel name\n\n@_RouteClassAttributeToGetattr\ndef value(self):\n    return self._value_\ntemp_enum_dict['value'] = value\ndel value\n\n@classmethod\ndef _convert(cls, name, module, filter, source=None):\n    \"\"\"\n    Create a new Enum subclass that replaces a collection of global constants\n    \"\"\"\n    # convert all constants from source (or module) that pass filter() to\n    # a new Enum called name, and export the enum and its members back to\n    # module;\n    # also, replace the __reduce_ex__ method so unpickling works in\n    # previous Python versions\n    module_globals = vars(_sys.modules[module])\n    if source:\n        source = vars(source)\n    else:\n        source = module_globals\n    members = dict((name, value) for name, value in source.items() if filter(name))\n    cls = cls(name, members, module=module)\n    cls.__reduce_ex__ = _reduce_ex_by_name\n    module_globals.update(cls.__members__)\n    module_globals[name] = cls\n    return cls\ntemp_enum_dict['_convert'] = _convert\ndel _convert\n\nEnum = EnumMeta('Enum', (object, ), temp_enum_dict)\ndel temp_enum_dict\n\n# Enum has now been created\n###########################\n\nclass IntEnum(int, Enum):\n    \"\"\"Enum where members are also (and must be) ints\"\"\"\n\ndef _reduce_ex_by_name(self, proto):\n    return self.name\n\ndef unique(enumeration):\n    \"\"\"Class decorator that ensures only unique members exist in an enumeration.\"\"\"\n    duplicates = []\n    for name, member in enumeration.__members__.items():\n        if name != member.name:\n            duplicates.append((name, member.name))\n    if duplicates:\n        duplicate_names = ', '.join(\n                [\"%s -> %s\" % (alias, name) for (alias, name) in duplicates]\n                )\n        raise ValueError('duplicate names found in %r: %s' %\n                (enumeration, duplicate_names)\n                )\n    return enumeration\n"
  },
  {
    "path": "scc/lib/eudevmonitor.py",
    "content": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\"\"\"\nudevmonitor.py - enumerates and monitors devices using (e)udev\n\nCopyright (C) 2018 by Kozec\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License version 2 as published by\nthe Free Software Foundation\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along\nwith this program; if not, write to the Free Software Foundation, Inc.,\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\"\"\"\n\nfrom collections import namedtuple\nfrom ctypes.util import find_library\nimport os, ctypes, errno\n\nclass Eudev:\n\tLIB_NAME = \"udev\"\n\n\tdef __init__(self):\n\t\tself._ctx = None\n\t\ttry:\n\t\t\tself._lib = ctypes.cdll.LoadLibrary(\"libudev.so\")\n\t\texcept OSError:\n\t\t\tself._lib = ctypes.CDLL(find_library(self.LIB_NAME))\n\t\t\tif self._lib is None:\n\t\t\t\traise ImportError(\"No library named udev\")\n\t\tEudev._setup_lib(self._lib)\n\t\tself._ctx = self._lib.udev_new()\n\t\tif self._ctx is None:\n\t\t\traise OSError(\"Failed to initialize udev context\")\n\n\t@staticmethod\n\tdef _setup_lib(l):\n\t\t\"\"\" Just so it's away from init and can be folded in IDE \"\"\"\n\t\t# udev\n\t\tl.udev_new.restype = ctypes.c_void_p\n\t\tl.udev_unref.argtypes = [ ctypes.c_void_p ]\n\t\t# enumeration\n\t\tl.udev_enumerate_new.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_enumerate_new.restype = ctypes.c_void_p\n\t\tl.udev_enumerate_unref.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_enumerate_scan_devices.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_enumerate_scan_devices.restype = ctypes.c_int\n\t\tl.udev_enumerate_get_list_entry.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_enumerate_get_list_entry.restype = ctypes.c_void_p\n\t\tl.udev_list_entry_get_next.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_list_entry_get_next.restype = ctypes.c_void_p\n\t\tl.udev_list_entry_get_value.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_list_entry_get_value.restype = ctypes.c_char_p\n\t\tl.udev_list_entry_get_name.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_list_entry_get_name.restype = ctypes.c_char_p\n\t\t# monitoring\n\t\tl.udev_monitor_new_from_netlink.argtypes = [ ctypes.c_void_p, ctypes.c_char_p ]\n\t\tl.udev_monitor_new_from_netlink.restype = ctypes.c_void_p\n\t\tl.udev_monitor_unref.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_monitor_enable_receiving.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_monitor_enable_receiving.restype = ctypes.c_int\n\t\tl.udev_monitor_set_receive_buffer_size.argtypes = [ ctypes.c_void_p, ctypes.c_int ]\n\t\tl.udev_monitor_set_receive_buffer_size.restype = ctypes.c_int\n\t\tl.udev_monitor_get_fd.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_monitor_get_fd.restype = ctypes.c_int\n\t\tl.udev_monitor_receive_device.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_monitor_receive_device.restype = ctypes.c_void_p\n\t\tl.udev_monitor_filter_update.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_monitor_filter_update.restype = ctypes.c_int\n\t\tl.udev_monitor_filter_add_match_subsystem_devtype.argtypes = [ ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p ]\n\t\tl.udev_monitor_filter_add_match_subsystem_devtype.restype = ctypes.c_int\n\t\tl.udev_monitor_filter_add_match_tag.argtypes = [ ctypes.c_void_p, ctypes.c_char_p ]\n\t\tl.udev_monitor_filter_add_match_tag.restype = ctypes.c_int\n\t\t# device\n\t\tl.udev_device_get_action.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_action.restype = ctypes.c_char_p\n\t\tl.udev_device_get_devnode.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_devnode.restype = ctypes.c_char_p\n\t\tl.udev_device_get_subsystem.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_subsystem.restype = ctypes.c_char_p\n\t\tl.udev_device_get_devtype.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_devtype.restype = ctypes.c_char_p\n\t\tl.udev_device_get_syspath.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_syspath.restype = ctypes.c_char_p\n\t\tl.udev_device_get_sysname.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_sysname.restype = ctypes.c_char_p\n\t\tl.udev_device_get_is_initialized.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_is_initialized.restype = ctypes.c_int\n\t\tl.udev_device_get_devnum.argtypes = [ ctypes.c_void_p ]\n\t\tl.udev_device_get_devnum.restype = ctypes.c_int\n\t\tl.udev_device_unref.argtypes = [ ctypes.c_void_p ]\n\n\t\tfor name in dir(Enumerator):\n\t\t\tif \"match_\" in name:\n\t\t\t\ttwoargs = getattr(getattr(Enumerator, name), \"twoargs\", False)\n\t\t\t\tfn = getattr(l, \"udev_enumerate_add_\" + name)\n\t\t\t\tif twoargs:\n\t\t\t\t\tfn.argtypes = [ ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p ]\n\t\t\t\telse:\n\t\t\t\t\tfn.argtypes = [ ctypes.c_void_p, ctypes.c_char_p ]\n\t\t\t\tfn.restype = ctypes.c_int\n\n\n\tdef __del__(self):\n\t\tif self._ctx is not None:\n\t\t\tself._lib.udev_unref(self._ctx)\n\t\t\tself._ctx = None\n\n\n\tdef enumerate(self, subclass=None):\n\t\t\"\"\"\n\t\tReturns new Enumerator instance.\n\t\t\"\"\"\n\t\tenumerator = self._lib.udev_enumerate_new(self._ctx)\n\t\tif enumerator is None:\n\t\t\traise OSError(\"Failed to initialize enumerator\")\n\t\tif subclass is not None:\n\t\t\tassert issubclass(subclass, Enumerator)\n\t\tsubclass = subclass or Enumerator\n\t\treturn subclass(self, enumerator)\n\n\n\tdef monitor(self, subclass=None):\n\t\t\"\"\"\n\t\tReturns new Monitor instance.\n\t\t\"\"\"\n\t\tmonitor = self._lib.udev_monitor_new_from_netlink(self._ctx, b\"udev\")\n\t\tif monitor is None:\n\t\t\traise OSError(\"Failed to initialize monitor\")\n\t\tif subclass is not None:\n\t\t\tassert issubclass(subclass, Monitor)\n\t\tsubclass = subclass or Monitor\n\t\treturn subclass(self, monitor)\n\n\ndef twoargs(fn):\n\tfn.twoargs = True\n\treturn fn\n\n\nclass Enumerator:\n\t\"\"\"\n\tIterable object used for enumerating available devices.\n\tYields syspaths (strings).\n\n\tAll match_* methods are returning self for chaining.\n\t\"\"\"\n\tdef __init__(self, eudev, enumerator):\n\t\tself._eudev = eudev\n\t\tself._enumerator = enumerator\n\t\tself._keep_in_mem = []\n\t\tself._enumeration_started = False\n\t\tself._next = None\n\n\n\tdef __del__(self):\n\t\tif self._enumerator is not None:\n\t\t\tself._eudev._lib.udev_enumerate_unref(self._enumerator)\n\t\t\tself._enumerator = None\n\n\n\tdef _add_match(self, whichone, *pars):\n\t\tif self._enumeration_started:\n\t\t\traise RuntimeError(\"Cannot add match after enumeration is started\")\n\t\tfn = getattr(self._eudev._lib, \"udev_enumerate_add_\" + whichone)\n\t\tpars = [ ctypes.c_char_p(p.encode(\"utf-8\") if type(p) is str else p) for p in pars ]\n\t\tself._keep_in_mem += pars\n\t\terr = fn(self._enumerator, *pars)\n\t\tif err < 0:\n\t\t\traise OSError(\"udev_enumerate_add_%s: error %s\" % (whichone, err))\n\t\treturn self\n\n\n\t@twoargs\n\tdef match_sysattr(self, sysattr, value): return self._add_match(\"match_sysattr\", sysattr, value)\n\t@twoargs\n\tdef nomatch_sysattr(self, sysattr, value): return self._add_match(\"nomatch_sysattr\", sysattr, value)\n\t@twoargs\n\tdef match_property(self, property, value): return self._add_match(\"match_property\", property, value)\n\tdef match_subsystem(self, subsystem): return self._add_match(\"match_subsystem\", subsystem)\n\tdef nomatch_subsystem(self, subsystem): return self._add_match(\"nomatch_subsystem\", subsystem)\n\tdef match_sysname(self, sysname): return self._add_match(\"match_sysname\", sysname)\n\tdef match_tag(self, tag): return self._add_match(\"match_tag\", tag)\n\tdef match_is_initialized(self): return self._add_match(\"match_is_initialized\")\n\t# match_parent is not implemented\n\n\n\tdef __iter__(self):\n\t\tif self._enumeration_started:\n\t\t\traise RuntimeError(\"Cannot iterate same Enumerator twice\")\n\t\tself._enumeration_started = True\n\t\terr = self._eudev._lib.udev_enumerate_scan_devices(self._enumerator)\n\t\tif err < 0:\n\t\t\traise OSError(\"udev_enumerate_scan_devices: error %s\" % (err, ))\n\t\tself._next = self._eudev._lib.udev_enumerate_get_list_entry(self._enumerator)\n\t\treturn self\n\n\n\tdef next(self):\n\t\treturn self.__next__()\n\n\n\tdef __next__(self):\n\t#def next(self):\n\t\tif not self._enumeration_started:\n\t\t\tself.__iter__()\t# Starts the enumeration\n\t\tif self._next is None:\n\t\t\traise StopIteration()\n\t\trv = self._eudev._lib.udev_list_entry_get_name(self._next)\n\t\tif rv is None:\n\t\t\traise OSError(\"udev_list_entry_get_name failed\")\n\t\tself._next = self._eudev._lib.udev_list_entry_get_next(self._next)\n\t\treturn str(rv, \"utf-8\")\n\n\nclass Monitor:\n\t\"\"\"\n\tMonitor object recieves device events.\n\treceive_device method blocks until next event is processed, so it can be\n\tused either in dumb loop, or called when select syscall reports descriptor\n\treturned by get_fd has data available.\n\n\tAll match_* methods are returning self for chaining\n\t\"\"\"\n\tDeviceEvent = namedtuple(\"DeviceEvent\", \"action,node,initialized,subsystem,devtype,syspath,devnum\")\n\n\tdef __init__(self, eudev, monitor):\n\t\tself._eudev = eudev\n\t\tself._monitor = monitor\n\t\tself._monitor_started = False\n\t\tself._keep_in_mem = []\n\t\tself._enabled_matches = set()\n\n\n\tdef __del__(self):\n\t\tif self._monitor is not None:\n\t\t\tself._eudev._lib.udev_monitor_unref(self._monitor)\n\t\t\tself._monitor = None\n\n\n\tdef _add_match(self, whichone, *pars):\n\t\tkey = tuple([whichone] + list(pars))\n\t\tif key in self._enabled_matches:\n\t\t\t# Already done\n\t\t\treturn self\n\t\tfn = getattr(self._eudev._lib, \"udev_monitor_filter_add_\" + whichone)\n\t\tpars = [ ctypes.c_char_p(p.encode(\"utf-8\") if type(p) is str else p) for p in pars ]\n\t\tself._keep_in_mem += pars\n\t\terr = fn(self._monitor, *pars)\n\t\tif err < 0:\n\t\t\traise OSError(\"udev_monitor_filter_add_%s: error %s\" % (whichone, errno.errorcode.get(err, err)))\n\t\tself._enabled_matches.add(key)\n\t\tif self._monitor_started:\n\t\t\terr = self._eudev._lib.udev_monitor_filter_update(self._monitor)\n\t\t\tif err < 0:\n\t\t\t\traise OSError(\"udev_monitor_filter_update: error %s\" % (errno.errorcode.get(err, err), ))\n\t\treturn self\n\n\n\tdef match_subsystem_devtype(self, subsystem, devtype=None):\n\t\treturn self._add_match(\"match_subsystem_devtype\", subsystem, devtype)\n\tdef match_subsystem(self, subsystem):\n\t\treturn self._add_match(\"match_subsystem_devtype\", subsystem, None)\n\tdef match_tag(self, tag):\n\t\treturn self._add_match(\"match_tag\", tag)\n\n\tdef is_started(self):\n\t\treturn self._monitor_started\n\n\n\tdef get_fd(self):\n\t\tfileno = self._eudev._lib.udev_monitor_get_fd(self._monitor)\n\t\tif fileno < 0:\n\t\t\traise OSError(\"udev_monitor_get_fd: error %s\" % (errno.errorcode.get(fileno, fileno), ))\n\t\treturn fileno\n\n\n\tdef enable_receiving(self):\n\t\t\"\"\" Returns self for chaining \"\"\"\n\t\tif self._monitor_started:\n\t\t\treturn # Error, but unimportant\n\t\terr = self._eudev._lib.udev_monitor_enable_receiving(self._monitor)\n\t\tif err < 0:\n\t\t\traise OSError(\"udev_monitor_enable_receiving: error %s\" % (errno.errorcode.get(err, err)))\n\t\tself._monitor_started = True\n\t\treturn self\n\n\n\tdef set_receive_buffer_size(self, size):\n\t\t\"\"\" Returns self for chaining \"\"\"\n\t\terr = self._eudev._lib.udev_monitor_set_receive_buffer_size(self._monitor, size)\n\t\tif err < 0:\n\t\t\traise OSError(\"udev_monitor_set_receive_buffer_size: error %s\" % (errno.errorcode.get(err, err)))\n\t\treturn self\n\n\n\tfileno = get_fd\t\t\t\t# python stuff likes this name better\n\tstart = enable_receiving\t# I like this name better\n\n\n\tdef receive_device(self):\n\t\tif not self._monitor_started:\n\t\t\tself.enable_receiving()\n\n\t\tdev = self._eudev._lib.udev_monitor_receive_device(self._monitor)\n\t\tif dev is None:\n\t\t\t# udev_monitor_receive_device is _supposed_ to be blocking.\n\t\t\t# It doesn't looks that way\n\t\t\treturn None\n\n\t\tdevnode = self._eudev._lib.udev_device_get_devnode(dev)\n\t\tdevnode_str = str(devnode, \"utf-8\") if devnode else None\n\n\t\tdevtype = self._eudev._lib.udev_device_get_devtype(dev)\n\t\tdevtype_str = str(devtype, \"utf-8\") if devtype else None\n\n\t\tevent = Monitor.DeviceEvent(\n\t\t\tstr(self._eudev._lib.udev_device_get_action(dev), \"utf-8\"),\n\t\t\tdevnode_str,\n\t\t\tself._eudev._lib.udev_device_get_is_initialized(dev) == 1,\n\t\t\tstr(self._eudev._lib.udev_device_get_subsystem(dev), \"utf-8\"),\n\t\t\tstr(self._eudev._lib.udev_device_get_devtype(dev) or b\"\", \"utf-8\"),\n\t\t\tstr(self._eudev._lib.udev_device_get_syspath(dev) or b\"\", \"utf-8\"),\n\t\t\tself._eudev._lib.udev_device_get_devnum(dev),\n\t\t)\n\n\t\tself._eudev._lib.udev_device_unref(dev)\n\t\treturn event\n\n\nif __name__ == \"__main__\":\n\tudev = Eudev()\n\ten = udev.enumerate().match_subsystem(\"hidraw\")\n\tfor i in en:\n\t\tprint(i)\n\n\tm = udev.monitor().match_subsystem(\"hidraw\").start()\n\twhile True:\n\t\td = m.receive_device()\n\t\tif d:\n\t\t\tprint(os.major(d.devnum), os.minor(d.devnum), d)\n"
  },
  {
    "path": "scc/lib/hidparse.py",
    "content": "\"\"\"\nhidparse - just enough code to parse HID report from hidraw descriptor.\n\nBased on\n  - Pythonic binding for linux's hidraw ioctls\n\t  (https://github.com/vpelletier/python-hidraw)\n  - Winfred Lu's rd-parse.py\n\t  (http://winfred-lu.blogspot.sk/2014/02/usb-hid-report-descriptor-parser-in.html)\n\nLicensed under GPL 2.0\n\"\"\"\nfrom scc.lib.hidparse_data import GlobalItem, MainItem, LocalItem, UsagePage\nfrom scc.lib.hidparse_data import SensorPage, SensorSelector, LightSensor\nfrom scc.lib.hidparse_data import GenericDesktopPage, page_to_enum\nfrom scc.lib.hidparse_data import MotionSensor, OrientationSensor\nfrom scc.lib.hidparse_data import ModifierI2a, HidSensorProperty\nfrom scc.lib.hidparse_data import ItemType, ItemLength, ItemBase\nfrom scc.lib.hidparse_data import SensorEvent, SensorDataField\nfrom scc.lib.hidparse_data import Collection, Unit, UnitType\nfrom scc.lib import ioctl_opt\nfrom scc.lib import IntEnum\nimport ctypes, fcntl, collections, struct\n\n# hid.h\n_HID_MAX_DESCRIPTOR_SIZE = 4096\n\nAXES = [\n\tGenericDesktopPage.X,\n\tGenericDesktopPage.Y,\n\tGenericDesktopPage.Z,\n\tGenericDesktopPage.Rx,\n\tGenericDesktopPage.Ry,\n\tGenericDesktopPage.Rz,\n]\n\n# hidraw.h\nclass _hidraw_report_descriptor(ctypes.Structure):\n\t_fields_ = [\n\t\t('size', ctypes.c_uint),\n\t\t('value', ctypes.c_ubyte * _HID_MAX_DESCRIPTOR_SIZE),\n\t]\n\n\nclass _hidraw_devinfo(ctypes.Structure):\n\t_fields_ = [\n\t\t('bustype', ctypes.c_uint),\n\t\t('vendor', ctypes.c_short),\n\t\t('product', ctypes.c_short),\n\t]\n\n\nclass BusType(IntEnum):\n\tUSB = 0x03\n\tHIL = 0x04\n\tBLUETOOTH = 0x05\n\tVIRTUAL = 0x06\n\n\nclass ReservedItem(object):\n\t_CACHE = {}\n\t\n\tdef __init__(self, value):\n\t\tself.value = value\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<Reserved ID 0x%x>\" % (self.value,)\n\t\n\t__str__ = __repr__\n\t\n\tdef __new__(cls, value):\n\t\tif value not in cls._CACHE:\n\t\t\tcls._CACHE[value] = object.__new__(cls)\n\t\treturn cls._CACHE[value]\n\n\ndef enum_or_reserved(enum, value):\n\ttry:\n\t\treturn enum(value)\n\texcept ValueError:\n\t\treturn ReservedItem(value)\n\n\n_HIDIOCGRDESCSIZE =\tioctl_opt.IOR(ord('H'), 0x01, ctypes.c_int)\n_HIDIOCGRDESC =\t\tioctl_opt.IOR(ord('H'), 0x02, _hidraw_report_descriptor)\n_HIDIOCGRAWINFO =\tioctl_opt.IOR(ord('H'), 0x03, _hidraw_devinfo)\n#_HIDIOCGFEATURE =\tlambda len : ioctl_opt.IORW(ord('H'), 0x07, len)\n_HIDIOCGFEATURE = lambda len: ioctl_opt.IOC(\n\tioctl_opt.IOC_WRITE|ioctl_opt.IOC_READ, ord('H'), 0x07, len)\n\n\ndef _ioctl(devfile, func, arg, mutate_flag=False):\n\tresult = fcntl.ioctl(devfile, func, arg, mutate_flag)\n\tif result < 0:\n\t\traise IOError(result)\n\n\ndef get_device_info(devfile):\n\t\"\"\"\n\tReturns tuple of (bustype, vendor_id, product_id), where bustype is\n\tinstance of BusType enum.\n\t\"\"\"\n\tdevinfo = _hidraw_devinfo()\n\t_ioctl(devfile, _HIDIOCGRAWINFO, devinfo, True)\n\treturn (BusType(devinfo.bustype), devinfo.vendor, devinfo.product)\n\n\ndef get_raw_report_descriptor(devfile):\n\t\"\"\"\n\tReturns raw HID report descriptor as list of bytes.\n\t\"\"\"\n\tdescriptor = _hidraw_report_descriptor()\n\tsize = ctypes.c_uint()\n\t_ioctl(devfile, _HIDIOCGRDESCSIZE, size, True)\n\tdescriptor.size = size\n\t_ioctl(devfile, _HIDIOCGRDESC, descriptor, True)\n\treturn descriptor.value[:size.value]\n\n\n# Convert items to unsigned char, short, or int\ndef _it2u(it):\n\tif len(it) == 2:\t# unsigned char\n\t\tn = it[1]\n\telif len(it) == 3:\t# unsigned short\n\t\tn = int('{:02x}{:02x}'.format(it[2], it[1]), 16)\n\telif len(it) == 5:\t# unsigned int\n\t\tn = int('{:02x}{:02x}{:02x}{:02x}'.format(it[4], it[3], it[2], it[1]), 16)\n\telse:\n\t\tn = 0\n\treturn n\n\n\n# Convert items to signed char, short, or int\ndef _it2s(it):\n\tif len(it) == 2:\t\t\t\t\t # signed char\n\t\tn = it[1]\n\t\tif n & 0x80:\n\t\t\tn -= 0x100\n\telif len(it) == 3:\t\t\t\t   # signed short\n\t\tn = int('{:02x}{:02x}'.format(it[2], it[1]), 16)\n\t\tif n & 0x8000:\n\t\t\tn -= 0x10000\n\telif len(it) == 5:\t\t\t\t   # signed int\n\t\tn = int('{:02x}{:02x}{:02x}{:02x}'.format(it[4], it[3], it[2], it[1]), 16)\n\t\tif n & 0x80000000:\n\t\t\tn -= 0x100000000\n\telse:\n\t\tn = 0\n\treturn n\n\n\ndef parse_item(it, page):\n\tif it[0] != 0xFE:\t\t\t\t\t# normal item\n\t\titag = it[0] & 0xF0\n\t\titype = it[0] & 0xC\n\t\tisize = it[0] & 0x3\n\telse:\t\t\t\t\t\t\t\t# long item\n\t\tisize = it[1]\n\t\titag = it[3] * 256 + it[2]\n\t\traise ValueError(\"Not implemented: long item!!\")\n\t\n\tif itype == 0x00:\t\t\t\t\t# main items\n\t\titem = enum_or_reserved(MainItem, itag)\n\t\tif item == MainItem.Collection:\n\t\t\tcol_type = enum_or_reserved(Collection, it[1])\n\t\t\treturn item, col_type\n\t\telif item in (MainItem.Input, MainItem.Output, MainItem.Feature):\n\t\t\treturn (item,\n\t\t\t\tItemType.Constant if it[1] & 0x1 else ItemType.Data,\n\t\t\t\tItemLength.Variable if it[1] & 0x2 else ItemLength.Array,\n\t\t\t\tItemBase.Relative if it[1] & 0x4 else ItemBase.Absolute,\n\t\t\t)\n\t\telse:\n\t\t\t# EndCollection or reserved\n\t\t\treturn item,\n\telif itype == 0x04:\t\t\t\t\t# global items\n\t\titem = enum_or_reserved(GlobalItem, itag)\n\t\tif item == GlobalItem.UsagePage:\n\t\t\tpage = enum_or_reserved(UsagePage, _it2u(it))\n\t\t\treturn item, page\n\t\telif item == GlobalItem.UnitExponent:\n\t\t\t# exponent\n\t\t\tvalue = it[1] if it[1] < 8 else it[1] - 0x10\n\t\t\treturn item, value\n\t\telif item == GlobalItem.Unit:\n\t\t\tif it[1] == 0:\n\t\t\t\treturn item, UnitType.NoUnit, None\n\t\t\tnibble = it[1] & 0x0F\n\t\t\tunit_type = enum_or_reserved(UnitType, nibble)\n\t\t\tif it[1] & 0xF0 != 0:\n\t\t\t\treturn item, unit_type, Unit.Length\n\t\t\tif len(it) > 2:\n\t\t\t\tif it[2] & 0x0F: return item, unit_type, Unit.Mass\n\t\t\t\tif it[2] & 0xF0: return item, unit_type, Unit.Item\n\t\t\tif len(it) > 3:\n\t\t\t\tif it[3] & 0x0F: return item, unit_type, Unit.Temperature\n\t\t\t\tif it[3] & 0xF0: return item, unit_type, Unit.Current\n\t\t\tif len(it) > 4:\n\t\t\t\tif it[4] & 0x0F: return item, unit_type, Unit.LuminousIntensity\n\t\t\treturn item, unit_type, ReservedItem(it[1])\n\t\telif item in (GlobalItem.LogicalMaximum, GlobalItem.PhysicalMaximum):\n\t\t\t# unsigned values\n\t\t\treturn item, _it2u(it)\n\t\telif item in (GlobalItem.LogicalMinimum, GlobalItem.PhysicalMinimum,\n\t\t\t\t\tGlobalItem.ReportSize):\n\t\t\t# signed values\n\t\t\treturn item, _it2s(it)\n\t\telif item in (GlobalItem.ReportID, GlobalItem.ReportCount):\n\t\t\treturn item, it[1]\n\t\telse:\n\t\t\treturn item\n\telif itype == 0x08:\t\t\t\t\t# local items\n\t\titem = enum_or_reserved(LocalItem, itag)\n\t\tif item == LocalItem.Usage:\n\t\t\tif page is SensorPage and isize == 2:\t# sensor page & usage size is 2\n\t\t\t\tmdf = (it[2] & 0xf0) >> 4\n\t\t\t\tif it[2] & 0xf == 0x02:\n\t\t\t\t\treturn (item, enum_or_reserved(ModifierI2a, mdf),\n\t\t\t\t\t\t\tenum_or_reserved(SensorEvent, it[1]))\n\t\t\t\telif it[2] & 0xf== 0x03:\n\t\t\t\t\treturn (item, enum_or_reserved(ModifierI2a, mdf),\n\t\t\t\t\t\t\tenum_or_reserved(HidSensorProperty, it[1]))\n\t\t\t\telif it[2] & 0xf== 0x04:\n\t\t\t\t\tif it[1] & 0xf0 == 0x50:\n\t\t\t\t\t\treturn (item, enum_or_reserved(ModifierI2a, mdf),\n\t\t\t\t\t\t\t\tenum_or_reserved(MotionSensor, it[1]))\n\t\t\t\t\telif it[1] & 0xf0 == 0x70 or it[1] & 0xf0 == 0x80:\n\t\t\t\t\t\treturn (item, enum_or_reserved(ModifierI2a, mdf),\n\t\t\t\t\t\t\t\tenum_or_reserved(OrientationSensor, it[1]))\n\t\t\t\t\telif it[1] & 0xf0 == 0xD0:\n\t\t\t\t\t\treturn (item, enum_or_reserved(ModifierI2a, mdf),\n\t\t\t\t\t\t\t\tenum_or_reserved(LightSensor, it[1]))\n\t\t\t\telif it[2] & 0xf== 0x05:\n\t\t\t\t\treturn (item, enum_or_reserved(ModifierI2a, mdf),\n\t\t\t\t\t\t\tenum_or_reserved(SensorDataField, it[1]))\n\t\t\t\telif it[2] & 0xf == 0x08:\n\t\t\t\t\treturn (item, enum_or_reserved(ModifierI2a, mdf),\n\t\t\t\t\t\t\tenum_or_reserved(SensorSelector, it[1]))\n\t\t\telif isize == 3:\t\t\t # 4 bytes (usage page: usage id)\n\t\t\t\tuid = it[2] * 256 + it[1]\n\t\t\t\tupg = it[4] * 256 + it[3]\n\t\t\t\tpage = enum_or_reserved(UsagePage, upg)\n\t\t\t\ttry:\n\t\t\t\t\treturn item, page(uid)\n\t\t\t\texcept ValueError:\n\t\t\t\t\treturn item, uid\n\t\t\t\treturn item, page\n\t\t\telse:\n\t\t\t\ttry:\n\t\t\t\t\treturn item, page(it[1])\n\t\t\t\texcept ValueError:\n\t\t\t\t\treturn item, it[1]\n\t\telif item in (LocalItem.UsageMinimum, LocalItem.UsageMaximum,\n\t\t\t\t\tLocalItem.DesignatorMinimum, LocalItem.DesignatorMaximum,\n\t\t\t\t\tLocalItem.StringMinimum, LocalItem.StringMaximum):\n\t\t\treturn item, _it2s(it)\n\t\telse:\n\t\t\treturn item\n\telse:\n\t\treturn ReservedItem(itype)\n\n\ndef _split_hid_items(data):\n\tsize = 0\n\tfor i in range(len(data)):\n\t\tif size != 0:\t\t\t\t\t# skip bytes for the previous item\n\t\t\tsize -= 1\n\t\t\tcontinue\n\t\tsize = data[i] & 0x3\t\t\t # 3 means 4 bytes\n\t\tif size == 3:\n\t\t\tsize = 4\n\t\tif i == 0xFE:\t\t\t\t\t# long item\n\t\t\tsize = data[i+1]\n\t\tyield data[i:i+size+1]\n\n\ndef parse_report_descriptor(data, flat_list=False):\n\t\"\"\"\n\tParses HID report descriptor to list of elements.\n\t\n\tIf flat_list is set to True, only one list is returned.\n\tOtherwise, each collection is stored in its own nested list.\n\t\"\"\"\n\trv, page = [], GenericDesktopPage\n\tstack, col = [], rv\n\tfor it in _split_hid_items(data):\n\t\titem = parse_item(it, page)\n\t\tif not flat_list and item[0] is MainItem.Collection:\n\t\t\titem = [ item ]\n\t\t\tcol.append(item)\n\t\t\tstack.append(col)\n\t\t\tcol = item\n\t\telif not flat_list and item[0] is MainItem.EndCollection:\n\t\t\tcol.append(item)\n\t\t\tcol, stack = stack[0], stack[:-1]\n\t\telif item[0] is GlobalItem.UsagePage:\n\t\t\tpage = item[1]\n\t\t\tif item[1] in page_to_enum:\n\t\t\t\tpage = page_to_enum[item[1]]\n\t\t\telse:\n\t\t\t\tpage = GenericDesktopPage\n\t\t\tcol.append(item)\n\t\telse:\n\t\t\tcol.append(item)\n\t\n\treturn rv\n\n\ndef get_report_descriptor(devfile, flat_list=False):\n\t\"\"\"\n\tReturns parsed HID report descriptor as list of elements.\n\t\n\tIf flat_list is set to True, only one list is returned.\n\tOtherwise, each collection is stored in its own nested list.\n\t\"\"\"\n\tdata = get_raw_report_descriptor(devfile)\n\treturn parse_report_descriptor(data, flat_list)\n\n\nclass Parser(object):\n\t\n\tdef __init__(self, code, offset, count, size):\n\t\tself.code = code\n\t\tself.value = 0\n\t\tself.offset = offset\n\t\tself.byte_offset = offset / 8\n\t\tself.bit_offset = offset % 8\n\t\tself.count = count\n\t\tself.len = count * size\n\t\tif self.len > 64:\n\t\t\traise ValueError(\"Too many bytes in value: %i\" % (self.len, ))\n\t\telif self.len > 32:\n\t\t\tself.byte_len = 8\n\t\t\tself.fmt = \"<Q\"\n\t\telif self.len > 16:\n\t\t\tself.byte_len = 4\n\t\t\tself.fmt = \"<I\"\n\t\telif self.len > 8:\n\t\t\tself.byte_len = 2\n\t\t\tself.fmt = \"<H\"\n\t\telse:\n\t\t\tself.byte_len = 1\n\t\t\tself.fmt = \"<B\"\n\t\tself.additional_bits = offset % 8\n\t\n\t\n\tdef decode(self, data):\n\t\tself.value, = struct.unpack(self.fmt, data[self.byte_offset: self.byte_offset + self.byte_len])\n\t\tself.value >>= self.additional_bits\n\nHIDPARSE_TYPE_AXIS = 1\nHIDPARSE_TYPE_BUTTONS = 2\n\n\nclass HIDButtonParser(Parser):\n\tTYPE = HIDPARSE_TYPE_BUTTONS\n\t\n\tdef __repr__(self):\n\t\treturn \"<HID Buttons @%s len %s value %s>\" % (self.offset, self.len, self.value)\n\n\nclass HIDAxisParser(Parser):\n\tTYPE = HIDPARSE_TYPE_AXIS\n\t\n\tdef __repr__(self):\n\t\treturn \"<HID Axis @%s len %s value %s>\" % (self.offset, self.len, self.value)\n\n\ndef make_parsers(data):\n\tsize, count = 1, 0\n\tkind = None\n\toffset = 0\n\tparsers = []\n\taxis_id, buttons_id = 0, 0\n\tfor x in parse_report_descriptor(data, True):\n\t\t# print(x)\n\t\tif x[0] == GlobalItem.ReportSize:\n\t\t\tsize = x[1]\n\t\telif x[0] == GlobalItem.ReportCount:\n\t\t\tcount = x[1]\n\t\telif x[0] == LocalItem.Usage:\n\t\t\tkind = x[1]\n\t\telif x[0] == MainItem.Input:\n\t\t\tif x[1] == ItemType.Constant:\n\t\t\t\tpass\n\t\t\telif x[1] == ItemType.Data:\n\t\t\t\tif kind in AXES:\n\t\t\t\t\tfor i in range(count):\n\t\t\t\t\t\tparsers.append(HIDAxisParser(axis_id, offset + size * i, 1, size))\n\t\t\t\t\t\taxis_id += 1\n\t\t\t\telse:\n\t\t\t\t\tparsers.append(HIDButtonParser(buttons_id, offset, count, size))\n\t\t\t\t\tbuttons_id += count\n\t\t\toffset += size * count\n\tsize = offset / 8\n\tif offset % 8 > 0: size += 1\n\treturn size, parsers\n"
  },
  {
    "path": "scc/lib/hidparse_data.py",
    "content": "\"\"\"\nhidparse_data - long enums moved from hidparse.py\n\nLicensed under GPL 2.0\n\"\"\"\n\nfrom scc.lib import IntEnum\n\n\nclass MainItem(IntEnum):\n\tInput = 0x80\n\tOutput = 0x90\n\tCollection = 0xA0\n\tFeature = 0xB0\n\tEndCollection = 0xC0\n\n\nclass GlobalItem(IntEnum):\n\tUsagePage = 0x00\n\tLogicalMinimum = 0x10\n\tLogicalMaximum = 0x20\n\tPhysicalMinimum = 0x30\n\tPhysicalMaximum = 0x40\n\tUnitExponent = 0x50\n\tUnit = 0x60\n\tReportSize = 0x70\n\tReportID = 0x80\n\tReportCount = 0x90\n\tPush = 0xA0\n\tPop = 0xB0\n\n\nclass LocalItem(IntEnum):\n\tUsage = 0x00\n\tUsageMinimum = 0x10\n\tUsageMaximum = 0x20\n\tDesignatorIndex = 0x30\n\tDesignatorMinimum = 0x40\n\tDesignatorMaximum = 0x50\n\tStringIndex = 0x70\n\tStringMinimum = 0x80\n\tStringMaximum = 0x90\n\tDelimiter = 0xA0\n\n\nclass Collection(IntEnum):\n\tPhysical = 0x00\n\tApplication = 0x01\n\tLogical = 0x02\n\tReport = 0x03\n\tNamedArray = 0x04\n\tUsageSwitch = 0x05\n\tUsageModifier = 0x06\n\n\nclass GenericDesktopPage(IntEnum):\n\tUndefined = 0x00\n\tPointer = 0x01\n\tMouse = 0x02\n\tReserved = 0x03\n\tJoystick = 0x04\n\tGamePad = 0x05\n\tKeyboard = 0x06\n\tKeypad = 0x07\n\tMultiAxisController = 0x08\n\tTabletPCSystemControls = 0x09\n\tX = 0x30\n\tY = 0x31\n\tZ = 0x32\n\tRx = 0x33\n\tRy = 0x34\n\tRz = 0x35\n\tSlider = 0x36\n\tDial = 0x37\n\tWheel = 0x38\n\tHatswitch = 0x39\n\tCountedBuffer = 0x3A\n\tByteCount = 0x3B\n\tMotionWakeup = 0x3C\n\tStart = 0x3D\n\tSelect = 0x3E\n\tVx = 0x40\n\tVy = 0x41\n\tVz = 0x42\n\tVbrx = 0x43\n\tVbry = 0x44\n\tVbrz = 0x45\n\tVno = 0x46\n\tFeatureNotification = 0x47\n\tResolutionMultiplier = 0x48\n\tSystemControl = 0x80\n\tSystemPowerDown = 0x81\n\tSystemSleep = 0x82\n\tSystemWakeUp = 0x83\n\tSystemContextMenu = 0x84\n\tSystemMainMenu = 0x85\n\tSystemAppMenu = 0x86\n\tSystemMenuHelp = 0x87\n\tSystemMenuExit = 0x88\n\tSystemMenuSelect = 0x89\n\tSystemMenuRight = 0x8A\n\tSystemMenuLeft = 0x8B\n\tSystemMenuUp = 0x8C\n\tSystemMenuDown = 0x8D\n\tSystemColdRestart = 0x8E\n\tSystemWarmRestart = 0x8F\n\tDPadUp = 0x90\n\tDPadDown = 0x91\n\tDPadRight = 0x92\n\tDPadLeft = 0x93\n\tSystemDock = 0xA0\n\tSystemUndock = 0xA1\n\tSystemSetup = 0xA2\n\tSystemBreak = 0xA3\n\tSystemDebuggerBreak = 0xA4\n\tApplicationBreak = 0xA5\n\tApplicationDebuggerBreak = 0xA6\n\tSystemSpeakerMute = 0xA7\n\tSystemHibernate = 0xA8\n\tSystemDisplayInvert = 0xB0\n\tSystemDisplayInternal = 0xB1\n\tSystemDisplayExternal = 0xB2\n\tSystemDisplayBoth = 0xB3\n\tSystemDisplayDual = 0xB4\n\tSystemDisplayToggleIntExt = 0xB5\n\tSystemDisplaySwapPrimarySecondary = 0xB6\n\tSystemDisplayLCDAutoscale = 0xB7\n\n\nclass SimulationControlsPage(IntEnum):\n\tUndefined = 0x00\n\tFlightSimulationDevice = 0x01\n\tAutomobileSimulationDevice = 0x02\n\tTankSimulationDevice = 0x03\n\tSpaceshipSimulationDevice = 0x04\n\tSubmarineSimulationDevice = 0x05\n\tSailingSimulationDevice = 0x06\n\tMotorcycleSimulationDevice = 0x07\n\tSportsSimulationDevice = 0x08\n\tAirplaneSimulationDevice = 0x09\n\tHelicopterSimulationDevice = 0x0A\n\tMagicCarpetSimulationDevice = 0x0B\n\tBicycleSimulationDevice = 0x0C\n\tFlightControlStick = 0x20\n\tFlightStick = 0x21\n\tCyclicControl = 0x22\n\tCyclicTrim = 0x23\n\tFlightYoke = 0x24\n\tTrackControl = 0x25\n\tAileron = 0xB0\n\tAileronTrim = 0xB1\n\tAntiTorqueControl = 0xB2\n\tAutopilotEnable = 0xB3\n\tChaffRelease = 0xB4\n\tCollectiveControl = 0xB5\n\tDiveBrake = 0xB6\n\tElectronicCountermeasures = 0xB7\n\tElevator = 0xB8\n\tElevatorTrim = 0xB9\n\tRudder = 0xBA\n\tThrottle = 0xBB\n\tFlightCommunications = 0xBC\n\tFlareRelease = 0xBD\n\tLandingGear = 0xBE\n\tToeBrake = 0xBF\n\tTrigger = 0xC0\n\tWeaponsArm = 0xC1\n\tWeaponsSelect = 0xC2\n\tWingFlaps = 0xC3\n\tAccelerator = 0xC4\n\tBrake = 0xC5\n\tClutch = 0xC6\n\tShifter = 0xC7\n\tSteering = 0xC8\n\tTurretDirection = 0xC9\n\tBarrelElevation = 0xCA\n\tDivePlane = 0xCB\n\tBallast = 0xCC\n\tBicycleCrank = 0xCD\n\tHandleBars = 0xCE\n\tFrontBrake = 0xCF\n\tRearBrake = 0xD0\n\n\nclass VRControlsPage(IntEnum):\n\tUnidentified = 0x00\n\tBelt = 0x01\n\tBodySuit = 0x02\n\tFlexor = 0x03\n\tGlove = 0x04\n\tHeadTracker = 0x05\n\tHeadMountedDisplay = 0x06\n\tHandTracker = 0x07\n\tOculometer = 0x08\n\tVest = 0x09\n\tAnimatronicDevice = 0x0A\n\tStereoEnable = 0x20\n\tDisplayEnable = 0x21\n\n\nclass SportControlsPage(IntEnum):\n\tUnidentified = 0x00\n\tBaseballBat = 0x01\n\tGolfClub = 0x02\n\tRowingMachine = 0x03\n\tTreadmill = 0x04\n\tOar = 0x30\n\tSlope = 0x31\n\tRate = 0x32\n\tStickSpeed = 0x33\n\tStickFaceAngle = 0x34\n\tStickHeelToe = 0x35\n\tStickFollowThrough = 0x36\n\tStickTempo = 0x37\n\tStickType = 0x38\n\tStickHeight = 0x39\n\tPutter = 0x50\n\tIron1 = 0x51\n\tIron2 = 0x52\n\tIron3 = 0x53\n\tIron4 = 0x54\n\tIron5 = 0x55\n\tIron6 = 0x56\n\tIron7 = 0x57\n\tIron8 = 0x58\n\tIron9 = 0x59\n\tIron10 = 0x5A\n\tIron11 = 0x5B\n\tSandWedge = 0x5C\n\tLoftWedge = 0x5D\n\tPowerWedge = 0x5E\n\tWood1 = 0x5F\n\tWood3 = 0x60\n\tWood5 = 0x61\n\tWood7 = 0x62\n\tWood9 = 0x63\n\n\nclass GameControlsPage(IntEnum):\n\tUndefined = 0x00\n\tGameController3D = 0x01\n\tPinballDevice = 0x02\n\tGunDevice = 0x03\n\tPointofView = 0x20\n\tTurnRightLeft = 0x21\n\tPitchForwardBackward = 0x22\n\tRollRightLeft = 0x23\n\tMoveRightLeft = 0x24\n\tMoveForwardBackward = 0x25\n\tMoveUpDown = 0x26\n\tLeanRightLeft = 0x27\n\tLeanForwardBackward = 0x28\n\tHeightofPOV = 0x29\n\tFlipper = 0x2A\n\tSecondaryFlipper = 0x2B\n\tBump = 0x2C\n\tNewGame = 0x2D\n\tShootBall = 0x2E\n\tPlayer = 0x2F\n\tGunBolt = 0x30\n\tGunClip = 0x31\n\tGunSelector = 0x32\n\tGunSingleShot = 0x33\n\tGunBurst = 0x34\n\tGunAutomatic = 0x35\n\tGunSafety = 0x36\n\tGamepadFireJump = 0x37\n\tGamepadTrigger = 0x39\n\n\nclass GenericDeviceControlsPage(IntEnum):\n\tUnidentified = 0x00\n\tBatteryStrength = 0x20\n\tWirelessChannel = 0x21\n\tWirelessID = 0x22\n\tDiscoverWirelessControl = 0x23\n\tSecurityCodeCharacterEntered = 0x24\n\tSecurityCodeCharacterErased = 0x25\n\tSecurityCodeCleared = 0x26\n\n\nclass KeyboardKeypadPage(IntEnum):\n\tNoEventIndicated = 0x00\n\tErrorRollOver = 0x01\n\tPOSTFail = 0x02\n\tErrorUndefined = 0x03\n\tA = 0x04\n\tB = 0x05\n\tC = 0x06\n\tD = 0x07\n\tE = 0x08\n\tF = 0x09\n\tG = 0x0A\n\tH = 0x0B\n\tI = 0x0C\n\tJ = 0x0D\n\tK = 0x0E\n\tL = 0x0F\n\tM = 0x10\n\tN = 0x11\n\tO = 0x12\n\tP = 0x13\n\tQ = 0x14\n\tR = 0x15\n\tS = 0x16\n\tT = 0x17\n\tU = 0x18\n\tV = 0x19\n\tW = 0x1A\n\tX = 0x1B\n\tY = 0x1C\n\tZ = 0x1D\n\tNum1 = 0x1E\n\tNum2 = 0x1F\n\tNum3 = 0x20\n\tNum4 = 0x21\n\tNum5 = 0x22\n\tNum6 = 0x23\n\tNum7 = 0x24\n\tNum8 = 0x25\n\tNum9 = 0x26\n\tNum0 = 0x27\n\tEnter = 0x28\n\tEscape = 0x29\n\tDelete = 0x2A\n\tTab = 0x2B\n\tSpacebar = 0x2C\n\tMinus = 0x2D\n\tPlus = 0x2E\n\tLBrace = 0x2F\n\tRBrace = 0x30\n\tBackslash = 0x31\n\tNonUsGrave = 0x32\n\tColon = 0x33\n\tDoublequote = 0x34\n\tGrave = 0x35\n\tKeypadComma = 0x36\n\tDot = 0x37\n\tSlash = 0x38\n\tCapsLock = 0x39\n\tF1 = 0x3A\n\tF2 = 0x3B\n\tF3 = 0x3C\n\tF4 = 0x3D\n\tF5 = 0x3E\n\tF6 = 0x3F\n\tF7 = 0x40\n\tF8 = 0x41\n\tF9 = 0x42\n\tF10 = 0x43\n\tF11 = 0x44\n\tF12 = 0x45\n\tPrintScreen = 0x46\n\tScrollLock = 0x47\n\tPause = 0x48\n\tInsert = 0x49\n\tHome = 0x4A\n\tPageUp = 0x4B\n\tDeleteForward = 0x4C\n\tEnd = 0x4D\n\tPageDown = 0x4E\n\tRightArrow = 0x4F\n\tLeftArrow = 0x50\n\tDownArrow = 0x51\n\tUpArrow = 0x52\n\tNumpadLockandClear = 0x53\n\tNumpadDivide = 0x54\n\tNumpadMultiply = 0x55\n\tNumpadMinus = 0x56\n\tNumpadPlus = 0x57\n\tNumpadEnter = 0x58\n\tNumpad1 = 0x59\n\tNumpad2 = 0x5A\n\tNumpad3 = 0x5B\n\tNumpad4 = 0x5C\n\tNumpad5 = 0x5D\n\tNumpad6 = 0x5E\n\tNumpad7 = 0x5F\n\tNumpad8 = 0x60\n\tNumpad9 = 0x61\n\tNumpad0 = 0x62\n\tNumpadDot = 0x63\n\tNonUSBackslash = 0x64\n\tApplication = 0x65\n\tPower = 0x66\n\tEquals = 0x67\n\tF13 = 0x68\n\tF14 = 0x69\n\tF15 = 0x6A\n\tF16 = 0x6B\n\tF17 = 0x6C\n\tF18 = 0x6D\n\tF19 = 0x6E\n\tF20 = 0x6F\n\tF21 = 0x70\n\tF22 = 0x71\n\tF23 = 0x72\n\tF24 = 0x73\n\tExecute = 0x74\n\tHelp = 0x75\n\tMenu = 0x76\n\tSelect = 0x77\n\tStop = 0x78\n\tAgain = 0x79\n\tUndo = 0x7A\n\tCut = 0x7B\n\tCopy = 0x7C\n\tPaste = 0x7D\n\tFind = 0x7E\n\tMute = 0x7F\n\tVolumeUp = 0x80\n\tVolumeDown = 0x81\n\tLockingCapsLock = 0x82\n\tLockingNumLock = 0x83\n\tLockingScrollLock = 0x84\n\tCommaSign = 0x85\n\tEqualSign = 0x86\n\tKeyboardInternational1 = 0x87\n\tKeyboardInternational2 = 0x88\n\tKeyboardInternational3 = 0x89\n\tKeyboardInternational4 = 0x8A\n\tKeyboardInternational5 = 0x8B\n\tKeyboardInternational6 = 0x8C\n\tKeyboardInternational7 = 0x8D\n\tKeyboardInternational8 = 0x8E\n\tKeyboardInternational9 = 0x8F\n\tKeyboardLANG1 = 0x90\n\tKeyboardLANG2 = 0x91\n\tKeyboardLANG3 = 0x92\n\tKeyboardLANG4 = 0x93\n\tKeyboardLANG5 = 0x94\n\tKeyboardLANG6 = 0x95\n\tKeyboardLANG7 = 0x96\n\tKeyboardLANG8 = 0x97\n\tKeyboardLANG9 = 0x98\n\tKeyboardAlternateErase = 0x99\n\tKeyboardSysReqAttention = 0x9A\n\tKeyboardCancel = 0x9B\n\tKeyboardClear = 0x9C\n\tKeyboardPrior = 0x9D\n\tKeyboardReturn = 0x9E\n\tKeyboardSeparator = 0x9F\n\tKeyboardOut = 0xA0\n\tKeyboardOper = 0xA1\n\tKeyboardClearAgain = 0xA2\n\tKeyboardCrSelProps = 0xA3\n\tKeyboardExSel = 0xA4\n\tKeypad00 = 0xB0\n\tKeypad000 = 0xB1\n\tThousandsSeparator = 0xB2\n\tDecimalSeparator = 0xB3\n\tCurrencyUnit = 0xB4\n\tCurrencySubUnit34 = 0xB5\n\tKeypadLeftBracket = 0xB6\n\tKeypadRightBracket = 0xB7\n\tKeypadLeftBrace = 0xB8\n\tKeypadRightBrace = 0xB9\n\tKeypadTab = 0xBA\n\tBackspace = 0xBB\n\tKeypadA = 0xBC\n\tKeypadB = 0xBD\n\tKeypadC = 0xBE\n\tKeypadD = 0xBF\n\tKeypadE = 0xC0\n\tKeypadF = 0xC1\n\tXox = 0xC2\n\tKeyXor = 0xC3\n\tKeyPercent = 0xC4\n\tKeyLessThan = 0xC5\n\tKeyMoreThan = 0xC6\n\tKeyAnd = 0xC7\n\tKeyAndAnd = 0xC8\n\tKeyOr = 0xC9\n\tKeyOrOr = 0xCA\n\tKeyColon = 0xCB\n\tKeyHash = 0xCC\n\tSpace = 0xCD\n\tKeyAt = 0xCE\n\tKeyExclamation = 0xCF\n\tMemoryStore = 0xD0\n\tMemoryRecall = 0xD1\n\tMemoryClear = 0xD2\n\tMemoryAdd = 0xD3\n\tMemorySubtract = 0xD4\n\tMemoryMultiply = 0xD5\n\tMemoryDivide = 0xD6\n\tPlusMinus = 0xD7\n\tClear = 0xD8\n\tClearEntry = 0xD9\n\tBinary = 0xDA\n\tOctal = 0xDB\n\tDecimal = 0xDC\n\tKeypadHexadecimal = 0xDD\n\tKeyboardLeftControl = 0xE0\n\tKeyboardLeftShift = 0xE1\n\tKeyboardLeftAlt = 0xE2\n\tKeyboardLeftGUI = 0xE3\n\tKeyboardRightControl = 0xE4\n\tKeyboardRightShift = 0xE5\n\tKeyboardRightAlt = 0xE6\n\tKeyboardRightGUI = 0xE7\n\n\nclass LedPage(IntEnum):\n\tUndefined = 0x00\n\tNumLock = 0x01\n\tCapsLock = 0x02\n\tScrollLock = 0x03\n\tCompose = 0x04\n\tKana = 0x05\n\tPower = 0x06\n\tShift = 0x07\n\tDoNotDisturb = 0x08\n\tMute = 0x09\n\tToneEnable = 0x0A\n\tHighCutFilter = 0x0B\n\tLowCutFilter = 0x0C\n\tEqualizerEnable = 0x0D\n\tSoundFieldOn = 0x0E\n\tSurroundOn = 0x0F\n\tRepeat = 0x10\n\tStereo = 0x11\n\tSamplingRateDetect = 0x12\n\tSpinning = 0x13\n\tCAV = 0x14\n\tCLV = 0x15\n\tRecordingFormatDetect = 0x16\n\tOffHook = 0x17\n\tRing = 0x18\n\tMessageWaiting = 0x19\n\tDataMode = 0x1A\n\tBatteryOperation = 0x1B\n\tBatteryOK = 0x1C\n\tBatteryLow = 0x1D\n\tSpeaker = 0x1E\n\tHeadSet = 0x1F\n\tHold = 0x20\n\tMicrophone = 0x21\n\tCoverage = 0x22\n\tNightMode = 0x23\n\tSendCalls = 0x24\n\tCallPickup = 0x25\n\tConference = 0x26\n\tStandBy = 0x27\n\tCameraOn = 0x28\n\tCameraOff = 0x29\n\tOnLine = 0x2A\n\tOffLine = 0x2B\n\tBusy = 0x2C\n\tReady = 0x2D\n\tPaperOut = 0x2E\n\tPaperJam = 0x2F\n\tRemote = 0x30\n\tForward = 0x31\n\tReverse = 0x32\n\tStop = 0x33\n\tRewind = 0x34\n\tFastForward = 0x35\n\tPlay = 0x36\n\tPause = 0x37\n\tRecord = 0x38\n\tError = 0x39\n\tUsageSelectedIndicator = 0x3A\n\tUsageInUseIndicator = 0x3B\n\tUsageMultiModeIndicator = 0x3C\n\tIndicatorOn = 0x3D\n\tIndicatorFlash = 0x3E\n\tIndicatorSlowBlink = 0x3F\n\tIndicatorFastBlink = 0x40\n\tIndicatorOff = 0x41\n\tFlashOnTime = 0x42\n\tSlowBlinkOnTime = 0x43\n\tSlowBlinkOffTime = 0x44\n\tFastBlinkOnTime = 0x45\n\tFastBlinkOffTime = 0x46\n\tUsageIndicatorColor = 0x47\n\tIndicatorRed = 0x48\n\tIndicatorGreen = 0x49\n\tIndicatorAmber = 0x4A\n\tGenericIndicator = 0x4B\n\tSystemSuspend = 0x4C\n\tExternalPowerConnected = 0x4D\n\n\nclass ButtonPage(IntEnum):\n\tNoButtonPressed = 0x00\n\tButton1 = 0x01\n\tButton2 = 0x02\n\tButton3 = 0x03\n\tButton4 = 0x04\n\n\nclass OrdinalPage(IntEnum):\n\tInstance1 = 0x01\n\tInstance2 = 0x02\n\tInstance3 = 0x03\n\tInstance4 = 0x04\n\n\nclass TelephonyDevicePage(IntEnum):\n\tUnassigned = 0x00\n\tPhone = 0x01\n\tAnsweringMachine = 0x02\n\tMessageControls = 0x03\n\tHandset = 0x04\n\tHeadset = 0x05\n\tTelephonyKeyPad = 0x06\n\tProgrammableButton = 0x07\n\tHookSwitch = 0x20\n\tFlash = 0x21\n\tFeature = 0x22\n\tHold = 0x23\n\tRedial = 0x24\n\tTransfer = 0x25\n\tDrop = 0x26\n\tPark = 0x27\n\tForwardCalls = 0x28\n\tAlternateFunction = 0x29\n\tLine = 0x2A\n\tSpeakerPhone = 0x2B\n\tConference = 0x2C\n\tRingEnable = 0x2D\n\tRingSelect = 0x2E\n\tPhoneMute = 0x2F\n\tCallerID = 0x30\n\tSend = 0x31\n\tSpeedDial = 0x50\n\tStoreNumber = 0x51\n\tRecallNumber = 0x52\n\tPhoneDirectory = 0x53\n\tVoiceMail = 0x70\n\tScreenCalls = 0x71\n\tDoNotDisturb = 0x72\n\tMessage = 0x73\n\tAnswerOnOff = 0x74\n\tInsideDialTone = 0x90\n\tOutsideDialTone = 0x91\n\tInsideRingTone = 0x92\n\tOutsideRingTone = 0x93\n\tPriorityRingTone = 0x94\n\tInsideRingback = 0x95\n\tPriorityRingback = 0x96\n\tLineBusyTone = 0x97\n\tReorderTone = 0x98\n\tCallWaitingTone = 0x99\n\tConfirmationTone1 = 0x9A\n\tConfirmationTone2 = 0x9B\n\tTonesOff = 0x9C\n\tOutsideRingback = 0x9D\n\tRinger = 0x9E\n\tPhoneKey0 = 0xB0\n\tPhoneKey1 = 0xB1\n\tPhoneKey2 = 0xB2\n\tPhoneKey3 = 0xB3\n\tPhoneKey4 = 0xB4\n\tPhoneKey5 = 0xB5\n\tPhoneKey6 = 0xB6\n\tPhoneKey7 = 0xB7\n\tPhoneKey8 = 0xB8\n\tPhoneKey9 = 0xB9\n\tPhoneKeyStar = 0xBA\n\tPhoneKeyPound = 0xBB\n\tPhoneKeyA = 0xBC\n\tPhoneKeyB = 0xBD\n\tPhoneKeyC = 0xBE\n\tPhoneKeyD = 0xBF\n\n\nclass ConsumerPage(IntEnum):\n\tUnassigned = 0x00\n\tConsumerControl = 0x01\n\tNumericKeyPad = 0x02\n\tProgrammableButtons = 0x03\n\tMicrophone = 0x04\n\tHeadphone = 0x05\n\tGraphicEqualizer = 0x06\n\tPlus10 = 0x20\n\tPlus100 = 0x21\n\tAMPM = 0x22\n\tPower = 0x30\n\tReset = 0x31\n\tSleep = 0x32\n\tSleepAfter = 0x33\n\tSleepMode = 0x34\n\tIllumination = 0x35\n\tFunctionButtons = 0x36\n\tMenu = 0x40\n\tMenuPick = 0x41\n\tMenuUp = 0x42\n\tMenuDown = 0x43\n\tMenuLeft = 0x44\n\tMenuRight = 0x45\n\tMenuEscape = 0x46\n\tMenuValueIncrease = 0x47\n\tMenuValueDecrease = 0x48\n\tDataOnScreen = 0x60\n\tClosedCaption = 0x61\n\tClosedCaptionSelect = 0x62\n\tVCRTV = 0x63\n\tBroadcastMode = 0x64\n\tSnapshot = 0x65\n\tStill = 0x66\n\tSelection = 0x80\n\tAssignSelection = 0x81\n\tModeStep = 0x82\n\tRecallLast = 0x83\n\tEnterChannel = 0x84\n\tOrderMovie = 0x85\n\tChannel = 0x86\n\tMediaSelection = 0x87\n\tMediaSelectComputer = 0x88\n\tMediaSelectTV = 0x89\n\tMediaSelectWWW = 0x8A\n\tMediaSelectDVD = 0x8B\n\tMediaSelectTelephone = 0x8C\n\tMediaSelectProgramGuide = 0x8D\n\tMediaSelectVideoPhone = 0x8E\n\tMediaSelectGames = 0x8F\n\tMediaSelectMessages = 0x90\n\tMediaSelectCD = 0x91\n\tMediaSelectVCR = 0x92\n\tMediaSelectTuner = 0x93\n\tQuit = 0x94\n\tHelp = 0x95\n\tMediaSelectTape = 0x96\n\tMediaSelectCable = 0x97\n\tMediaSelectSatellite = 0x98\n\tMediaSelectSecurity = 0x99\n\tMediaSelectHome = 0x9A\n\tMediaSelectCall = 0x9B\n\tChannelIncrement = 0x9C\n\tChannelDecrement = 0x9D\n\tMediaSelectSAP = 0x9E\n\tVCRPlus = 0xA0\n\tOnce = 0xA1\n\tDaily = 0xA2\n\tWeekly = 0xA3\n\tMonthly = 0xA4\n\tPlay = 0xB0\n\tPause = 0xB1\n\tRecord = 0xB2\n\tFastForward = 0xB3\n\tRewind = 0xB4\n\tScanNextTrack = 0xB5\n\tScanPreviousTrack = 0xB6\n\tStop = 0xB7\n\tEject = 0xB8\n\tRandomPlay = 0xB9\n\tSelectDisc = 0xBA\n\tEnterDisc = 0xBB\n\tRepeat = 0xBC\n\tTracking = 0xBD\n\tTrackNormal = 0xBE\n\tSlowTracking = 0xBF\n\tFrameForward = 0xC0\n\tFrameBack = 0xC1\n\tMark = 0xC2\n\tClearMark = 0xC3\n\tRepeatFromMark = 0xC4\n\tReturnToMark = 0xC5\n\tSearchMarkForward = 0xC6\n\tSearchMarkBackwards = 0xC7\n\tCounterReset = 0xC8\n\tShowCounter = 0xC9\n\tTrackingIncrement = 0xCA\n\tTrackingDecrement = 0xCB\n\tStopEject = 0xCC\n\tPlayPause = 0xCD\n\tPlaySkip = 0xCE\n\tVolume = 0xE0\n\tBalance = 0xE1\n\tMute = 0xE2\n\tBass = 0xE3\n\tTreble = 0xE4\n\tBassBoost = 0xE5\n\tSurroundMode = 0xE6\n\tLoudness = 0xE7\n\tMPX = 0xE8\n\tVolumeIncrement = 0xE9\n\tVolumeDecrement = 0xEA\n\tSpeedSelect = 0xF0\n\tPlaybackSpeed = 0xF1\n\tStandardPlay = 0xF2\n\tLongPlay = 0xF3\n\tExtendedPlay = 0xF4\n\tSlow = 0xF5\n\tFanEnable = 0x100\n\tFanSpeed = 0x101\n\tLightEnable = 0x102\n\tLightIlluminationLevel = 0x103\n\tClimateControlEnable = 0x104\n\tRoomTemperature = 0x105\n\tSecurityEnable = 0x106\n\tFireAlarm = 0x107\n\tPoliceAlarm = 0x108\n\tProximity = 0x109\n\tMotion = 0x10A\n\tDuressAlarm = 0x10B\n\tHoldupAlarm = 0x10C\n\tMedicalAlarm = 0x10D\n\tBalanceRight = 0x150\n\tBalanceLeft = 0x151\n\tBassIncrement = 0x152\n\tBassDecrement = 0x153\n\tTrebleIncrement = 0x154\n\tTrebleDecrement = 0x155\n\tSpeakerSystem = 0x160\n\tChannelLeft = 0x161\n\n\nclass DigitizersPage(IntEnum):\n\tUndefined = 0x00\n\tDigitizer = 0x01\n\tPen = 0x02\n\tLightPen = 0x03\n\tTouchScreen = 0x04\n\tTouchPad = 0x05\n\tWhiteBoard = 0x06\n\tCoordinateMeasuringMachine = 0x07\n\tDigitizer3D = 0x08\n\tStereoPlotter = 0x09\n\tArticulatedArm = 0x0A\n\tArmature = 0x0B\n\tMultiplePointDigitizer = 0x0C\n\tFreeSpaceWand = 0x0D\n\tStylus = 0x20\n\tPuck = 0x21\n\tFinger = 0x22\n\tTipPressure = 0x30\n\tBarrelPressure = 0x31\n\tInRange = 0x32\n\tTouch = 0x33\n\tUntouch = 0x34\n\tTap = 0x35\n\tQuality = 0x36\n\tDataValid = 0x37\n\tTransducerIndex = 0x38\n\tTabletFunctionKeys = 0x39\n\tProgramChangeKeys = 0x3A\n\tBatteryStrength = 0x3B\n\tInvert = 0x3C\n\tXTilt = 0x3D\n\tYTilt = 0x3E\n\tAzimuth = 0x3F\n\tAltitude = 0x40\n\tTwist = 0x41\n\tTipSwitch = 0x42\n\tSecondaryTipSwitch = 0x43\n\tBarrelSwitch = 0x44\n\tEraser = 0x45\n\tTabletPick = 0x46\n\n\nclass AlphanumericDisplayPage(IntEnum):\n\tUndefined = 0x00\n\tAlphanumericDisplay = 0x01\n\tBitmappedDisplay = 0x02\n\tDisplayAttributesReport = 0x20\n\tASCIICharacterSet = 0x21\n\tDataReadBack = 0x22\n\tFontReadBack = 0x23\n\tDisplayControlReport = 0x24\n\tClearDisplay = 0x25\n\tDisplayEnable = 0x26\n\tScreenSaverDelay = 0x27\n\tScreenSaverEnable = 0x28\n\tVerticalScroll = 0x29\n\tHorizontalScroll = 0x2A\n\tCharacterReport = 0x2B\n\tDisplayData = 0x2C\n\tDisplayStatus = 0x2D\n\tStatNotReady = 0x2E\n\tStatReady = 0x2F\n\tErrNotaloadablecharacter = 0x30\n\tErrFontdatacannotberead = 0x31\n\tCursorPositionReport = 0x32\n\tRow = 0x33\n\tColumn = 0x34\n\tRows = 0x35\n\tColumns = 0x36\n\tCursorPixelPositioning = 0x37\n\tCursorMode = 0x38\n\tCursorEnable = 0x39\n\tCursorBlink = 0x3A\n\tFontReport = 0x3B\n\tFontData = 0x3C\n\tCharacterWidth = 0x3D\n\tCharacterHeight = 0x3E\n\tCharacterSpacingHorizontal = 0x3F\n\tCharacterSpacingVertical = 0x40\n\tUnicodeCharacterSet = 0x41\n\tFont7Segment = 0x42\n\tDirectMap7Segment = 0x43\n\tFont14Segment = 0x44\n\tDirectMap14Segment = 0x45\n\tDisplayBrightness = 0x46\n\tDisplayContrast = 0x47\n\tCharacterAttribute = 0x48\n\tAttributeReadback = 0x49\n\tAttributeData = 0x4A\n\tCharAttrEnhance = 0x4B\n\tCharAttrUnderline = 0x4C\n\tCharAttrBlink = 0x4D\n\tBitmapSizeX = 0x80\n\tBitmapSizeY = 0x81\n\tBitDepthFormat = 0x83\n\tDisplayOrientation = 0x84\n\tPaletteReport = 0x85\n\tPaletteDataSize = 0x86\n\tPaletteDataOffset = 0x87\n\tPaletteData = 0x88\n\tBlitReport = 0x8A\n\tBlitRectangleX1 = 0x8B\n\tBlitRectangleY1 = 0x8C\n\tBlitRectangleX2 = 0x8D\n\tBlitRectangleY2 = 0x8E\n\tBlitData = 0x8F\n\tSoftButton = 0x90\n\tSoftButtonID = 0x91\n\tSoftButtonSide = 0x92\n\tSoftButtonOffset1 = 0x93\n\tSoftButtonOffset2 = 0x94\n\tSoftButtonReport = 0x95\n\n\nclass SensorPage(IntEnum):\n\tSensorTypeCollection = 0x01\n\tBiometricHumanPresence = 0x11\n\tBiometricHumanProximity = 0x12\n\tBiometricHumanTouch = 0x13\n\tElectricalCurrent = 0x22\n\tElectricalPower = 0x23\n\tElectricalVoltage = 0x26\n\tElectricalPotentiometer = 0x27\n\tElectricalFrequency = 0x28\n\tEnvironmentalAtmosphericPressure = 0x31\n\tEnvironmentalHumidity = 0x32\n\tEnvironmentalTemperature = 0x33\n\tLightAmbientLight = 0x41\n\tMechanicalBooleanSwitch = 0x61\n\tMechanicalBooleanSwitchArray = 0x62\n\tMechanicalMultivalueSwitch = 0x63\n\tMotionAccelerometer1D = 0x71\n\tMotionAccelerometer2D = 0x72\n\tMotionAccelerometer3D = 0x73\n\tMotionGyrometer1D = 0x74\n\tMotionGyrometer2D = 0x75\n\tMotionGyrometer3D = 0x76\n\tMotionMotionDetector = 0x77\n\tOrientationCompass1D = 0x81\n\tOrientationCompass3D = 0x83\n\tOrientationInclinometer1D = 0x84\n\tOrientationInclinometer2D = 0x85\n\tOrientationInclinometer3D = 0x86\n\tOrientationDistance1D = 0x87\n\tOrientationDistance2D = 0x88\n\tOrientationDistance3D = 0x89\n\tOrientationDeviceOrientation = 0x8A\n\tOtherCustom = 0xE1\n\tOtherGeneric = 0xE2\n\n\nclass MedicalInstrumentPage(IntEnum):\n\tUndefined = 0x00\n\tMedicalUltrasound = 0x01\n\tVCRAcquisition = 0x20\n\tFreezeThaw = 0x21\n\tClipStore = 0x22\n\tUpdate = 0x23\n\tNext = 0x24\n\tSave = 0x25\n\tPrint = 0x26\n\tMicrophoneEnable = 0x27\n\tCine = 0x40\n\tTransmitPower = 0x41\n\tVolume = 0x42\n\tFocus = 0x43\n\tDepth = 0x44\n\tSoftStepPrimary = 0x60\n\tSoftStepSecondary = 0x61\n\tDepthGainCompensation = 0x70\n\tZoomSelect = 0x80\n\tZoomAdjust = 0x81\n\tSpectralDopplerModeSelect = 0x82\n\tSpectralDopplerAdjust = 0x83\n\tColorDopplerModeSelect = 0x84\n\tColorDopplerAdjust = 0x85\n\tMotionModeSelect = 0x86\n\tMotionModeAdjust = 0x87\n\tModeSelect2D = 0x88\n\tModeAdjust2D = 0x89\n\tSoftControlSelect = 0xA0\n\tSoftControlAdjust = 0xA1\n\n\nclass VendorPage(IntEnum):\n\tUsage1 = 0x01\n\tUsage2 = 0x02\n\tUsage3 = 0x03\n\tUsage4 = 0x04\n\tUsage5 = 0x05\n\tUsage6 = 0x06\n\tUsage7 = 0x07\n\tUsage8 = 0x08\n\tUsage9 = 0x09\n\tUsage10 = 0x0A\n\tUsage11 = 0x0B\n\tUsage12 = 0x0C\n\n\nclass UsagePage(IntEnum):\n\tGenericDesktopPage = 0x01\n\tSimulationControlsPage = 0x02\n\tVRControlsPage = 0x03\n\tSportControlsPage = 0x04\n\tGameControlsPage = 0x05\n\tGenericDeviceControlsPage = 0x06\n\tKeyboardKeypadPage = 0x07\n\tLedPage = 0x08\n\tButtonPage = 0x09\n\tOrdinalPage = 0x0A\n\tTelephonyDevicePage = 0x0B\n\tConsumerPage = 0x0C\n\tDigitizersPage = 0x0D\n\tAlphanumericDisplayPage = 0x14\n\tSensorPage = 0x20\n\tMedicalInstrumentPage = 0x40\n\tVendorPage = 0xFF00\n\n\npage_to_enum = {\n\tUsagePage.GenericDesktopPage : GenericDesktopPage,\n\tUsagePage.SimulationControlsPage : SimulationControlsPage,\n\tUsagePage.VRControlsPage : VRControlsPage,\n\tUsagePage.SportControlsPage : SportControlsPage,\n\tUsagePage.GameControlsPage : GameControlsPage,\n\tUsagePage.GenericDeviceControlsPage : GenericDeviceControlsPage,\n\tUsagePage.KeyboardKeypadPage : KeyboardKeypadPage,\n\tUsagePage.LedPage : LedPage,\n\tUsagePage.ButtonPage : ButtonPage,\n\tUsagePage.OrdinalPage : OrdinalPage,\n\tUsagePage.TelephonyDevicePage : TelephonyDevicePage,\n\tUsagePage.ConsumerPage : ConsumerPage,\n\tUsagePage.DigitizersPage : DigitizersPage,\n\tUsagePage.AlphanumericDisplayPage : AlphanumericDisplayPage,\n\tUsagePage.SensorPage : SensorPage,\n\tUsagePage.MedicalInstrumentPage : MedicalInstrumentPage,\n\tUsagePage.VendorPage : VendorPage,\n}\n\n\nclass SensorEvent(IntEnum):\n\tEvent = 0x00\n\tSensorState = 0x01\n\tSensorEvent = 0x02\n\n\nclass HidSensorProperty(IntEnum):\n\tFriendlyName = 0x01\n\tPersistentUniqueID = 0x02\n\tMinimumReportInterval = 0x04\n\tSensorManufacturer = 0x05\n\tSensorModel = 0x06\n\tSensorSerialNumber = 0x07\n\tSensorDescription = 0x08\n\tSensorConnectionType = 0x09\n\tReportInterval = 0x0E\n\tChangeSensitivityAbsolute = 0x0F\n\tChangeSensitivityrelativepercent = 0x11\n\tSensorAccuracy = 0x12\n\tSensorResolution = 0x13\n\tRangeMaximum = 0x14\n\tRangeMinimum = 0x15\n\tReportingState = 0x16\n\tResponseCurve = 0x18\n\tPowerState = 0x19\n\n\nclass MotionSensor(IntEnum):\n\tMotionState = 0x51\n\tAcceleration = 0x52\n\tAccelerationAxisX = 0x53\n\tAccelerationAxisY = 0x54\n\tAccelerationAxisZ = 0x55\n\tAngularVelocity = 0x56\n\tAngularVelocityXAxis = 0x57\n\tAngularVelocityYAxis = 0x58\n\tAngularVelocityZAxis = 0x59\n\tMotionIntensity = 0x5F\n\n\nclass OrientationSensor(IntEnum):\n\tHeading = 0x71\n\tHeadingCompensatedMagneticNorth = 0x75\n\tHeadingCompensatedTrueNorth = 0x76\n\tHeadingMagneticNorth = 0x77\n\tHeadingTrueNorth = 0x78\n\tDistance = 0x79\n\tDistanceXAxis = 0x7A\n\tDistanceYAxis = 0x7B\n\tDistanceZAxis = 0x7C\n\tDistanceOutOfRange = 0x7D\n\tTilt = 0x7E\n\tTiltXAxis = 0x7F\n\tTiltYAxis = 0x80\n\tTiltZAxis = 0x81\n\tRotationMatrix = 0x82\n\tQuaternion = 0x83\n\tMagneticFlux = 0x84\n\tMagneticFluxXAxis = 0x85\n\tMagneticFluxYAxis = 0x86\n\tMagneticFluxZAxis = 0x87\n\tMagneticAccuracy = 0x88\n\n\nclass LightSensor(IntEnum):\n\tIlluminance = 0xD1\n\tColorTemperature = 0xD2\n\tChromaticity = 0xD3\n\tChromaticityX = 0xD4\n\tChromaticityY = 0xD5\n\n\nclass SensorDataField(IntEnum):\n\tElectrical = 0x00\n\tCapacitanceFarads = 0x01\n\tCurrentAmperes = 0x02\n\tElectricalWatts = 0x03\n\tInductanceHenrys = 0x04\n\tResistanceOhms = 0x05\n\tVoltageVolts = 0x06\n\tFrequencyHertz = 0x07\n\tPeriodMilliseconds = 0x08\n\tPercentOfRange = 0x09\n\tCustom = 0x40\n\tCustomUsage = 0x41\n\tCustomBooleanArray = 0x42\n\tCustomValue = 0x43\n\tCustomValue1 = 0x44\n\tCustomValue2 = 0x45\n\tCustomValue3 = 0x46\n\tCustomValue4 = 0x47\n\tCustomValue5 = 0x48\n\tCustomValue6 = 0x49\n\tCustomValue7 = 0x4A\n\tCustomValue8 = 0x4B\n\tCustomValue9 = 0x4C\n\tCustomValue10 = 0x4D\n\tCustomValue11 = 0x4E\n\tCustomValue12 = 0x4F\n\tCustomValue13 = 0x50\n\tCustomValue14 = 0x51\n\tCustomValue15 = 0x52\n\tCustomValue16 = 0x53\n\tCustomValue17 = 0x54\n\tCustomValue18 = 0x55\n\tCustomValue19 = 0x56\n\tCustomValue20 = 0x57\n\tCustomValue21 = 0x58\n\tCustomValue22 = 0x59\n\tCustomValue23 = 0x5A\n\tCustomValue24 = 0x5B\n\tCustomValue25 = 0x5C\n\tCustomValue26 = 0x5D\n\tCustomValue27 = 0x5E\n\tCustomValue28 = 0x5F\n\n\nclass SensorSelector(IntEnum):\n\tStateUnknown = 0x00\n\tStateNotAvailable = 0x01\n\tStateReady = 0x02\n\tStateNoData = 0x03\n\tStateInitializing = 0x04\n\tStateAccessDenied = 0x05\n\tStateError = 0x06\n\tEventUnknown = 0x10\n\tEventStateChanged = 0x11\n\tEventPropertyChanged = 0x12\n\tEventDataUpdated = 0x13\n\tEventPollResponse = 0x14\n\tEventChangeSensitivity = 0x15\n\tEventMaxReached = 0x16\n\tEventMinReached = 0x17\n\tEventHighThesholdCrossAbove = 0x18\n\tEventHighThresholdCrossBelow = 0x19\n\tEventLowThresholdCrossAbove = 0x1a\n\tEventLowThresholdCrossBelow = 0x1b\n\tEventZeroThresholdCrossAbove = 0x1c\n\tEventZeroThresholdCrossBelow = 0x1d\n\tEventPeriodExceeded = 0x1e\n\tEventFrequencyExceeded = 0x1f\n\tEventComplexTrigger = 0x20\n\tConnectionTypePCIntegrated = 0x30\n\tConnectionTypePCAttached = 0x31\n\tConnectionTypePCExternal = 0x32\n\tPropertyReportingStateNoEvents = 0x40\n\tPropertyReportingStateAllEvents = 0x41\n\tPropertyReportingStateThresholdEvents = 0x42\n\tPropertyReportingStateNoEventsWake = 0x43\n\tPropertyReportingStateAllEventsWake = 0x44\n\tPropertyReportingStateThresholdEventsWake = 0x45\n\tPropertyPowerStateUndefined = 0x50\n\tPropertyPowerStateD0FullPower = 0x51\n\tPropertyPowerStateD1LowPower = 0x52\n\tPropertyPowerStateD2StandbyWithWake = 0x53\n\tPropertyPowerStateD3SleepWithWake = 0x54\n\tPropertyPowerStateD4PowerOff = 0x55\n\tDataFixQualityNoFix = 0x70\n\tDataFixQualityGps = 0x71\n\tDataFixQualityDgps = 0x72\n\tDataFixTypeNoFix = 0x80\n\tDataFixTypeGpsSpsModeFixValid = 0x81\n\tDataFixTypeDgpsSpsModeFixValid = 0x82\n\tDataFixTypeGpsPpsModeFixValid = 0x83\n\tDataFixTypeRealTimeKinematic = 0x84\n\tDataFixTypeFloatRtk = 0x85\n\tDataFixTypeEstimatedDeadReckoning = 0x86\n\tDataFixTypeManualInputMode = 0x87\n\tDataFixTypeSimulatorMode = 0x88\n\tDataGpsOpModeManual = 0x90\n\tDataGpsOpModeAutomatic = 0x91\n\tDataGpsSelModeAutonomous = 0xa0\n\tDataGpsSelModeDgps = 0xa1\n\tDataGpsSelModeEstimatedDeadReckoning = 0xa2\n\tDataGpsSelModeManualInput = 0xa3\n\tDataGpsSelModeSimulator = 0xa4\n\tDataGpsSelModeDataNotValid = 0xa5\n\tDataGpsStatusDataValid = 0xb0\n\tDataGpsStatusDataNotValid = 0xb1\n\tDesiredAccuracyDefault = 0x60\n\tDesiredAccuracyHigh = 0x61\n\tDesiredAccuracyMedium = 0x62\n\tDesiredAccuracyLow = 0x63\n\tGorpkKindCategory = 0xd0\n\tGorpkKindType = 0xd1\n\tGorpkKindEvent = 0xd2\n\tGorpkKindProperty = 0xd3\n\tGorpkKindDatafield = 0xd4\n\n\nclass UnitType(IntEnum):\n\tNoUnit = 0x00\n\tSILinear = 0x01\n\tSIRotation = 0x02\n\tEnglishLinear = 0x03\n\tEnglishRotation = 0x04\n\n\nclass Unit(IntEnum):\n\tLength = 0x01\n\tMass = 0x02\n\tTime = 0x03\n\tTemperature = 0x04\n\tCurrent = 0x05\n\tLuminousIntensity = 0x06\n\n\nclass ModifierI2a(IntEnum):\n\tChangeSensitivityAbsolute = 0x1\n\tMaximum = 0x2\n\tMinimum = 0x3\n\tAccuracy = 0x4\n\tResolution = 0x5\n\tThresholdHigh = 0x6\n\tThresholdLow = 0x7\n\tCalibrationOffset = 0x8\n\tCalibrationMultiplier = 0x9\n\tReportInterval = 0xA\n\tFrequencyMax = 0xB\n\tPeriodMax = 0xC\n\tChangeSensitivityPercentOfRange = 0xD\n\tChangeSensitivityPercentRelative = 0xE\n\tVendorOEM = 0xF\n\n\nclass ItemType(IntEnum):\n\tConstant = 0x1\n\tData = 0x2\n\n\nclass ItemLength(IntEnum):\n\tVariable = 0x1\n\tArray = 0x2\n\n\nclass ItemBase(IntEnum):\n\tRelative = 0x1\n\tAbsolute = 0x2\n"
  },
  {
    "path": "scc/lib/hidraw.py",
    "content": "import ctypes\nimport collections\nimport fcntl\nfrom scc.lib import ioctl_opt\n\n# input.h\nBUS_USB = 0x03\nBUS_HIL = 0x04\nBUS_BLUETOOTH = 0x05\nBUS_VIRTUAL = 0x06\n\n# hid.h\n_HID_MAX_DESCRIPTOR_SIZE = 4096\n\n# hidraw.h\nclass _hidraw_report_descriptor(ctypes.Structure):\n    _fields_ = [\n        ('size', ctypes.c_uint),\n        ('value', ctypes.c_ubyte * _HID_MAX_DESCRIPTOR_SIZE),\n    ]\n\nclass _hidraw_devinfo(ctypes.Structure):\n    _fields_ = [\n        ('bustype', ctypes.c_uint),\n        ('vendor', ctypes.c_short),\n        ('product', ctypes.c_short),\n    ]\n\n_HIDIOCGRDESCSIZE = ioctl_opt.IOR(ord('H'), 0x01, ctypes.c_int)\n_HIDIOCGRDESC = ioctl_opt.IOR(ord('H'), 0x02, _hidraw_report_descriptor)\n_HIDIOCGRAWINFO = ioctl_opt.IOR(ord('H'), 0x03, _hidraw_devinfo)\n_HIDIOCGRAWNAME = lambda len: ioctl_opt.IOC(ioctl_opt.IOC_READ, ord('H'),\n    0x04, len)\n_HIDIOCGRAWPHYS = lambda len: ioctl_opt.IOC(ioctl_opt.IOC_READ, ord('H'),\n    0x05, len)\n_HIDIOCSFEATURE = lambda len: ioctl_opt.IOC(\n    ioctl_opt.IOC_WRITE|ioctl_opt.IOC_READ, ord('H'), 0x06, len)\n_HIDIOCGFEATURE = lambda len: ioctl_opt.IOC(\n    ioctl_opt.IOC_WRITE|ioctl_opt.IOC_READ, ord('H'), 0x07, len)\n\nHIDRAW_FIRST_MINOR = 0\nHIDRAW_MAX_DEVICES = 64\nHIDRAW_BUFFER_SIZE = 64\n\nDevInfo = collections.namedtuple('DevInfo', ['bustype', 'vendor', 'product'])\n\nclass HIDRaw(object):\n    \"\"\"\n    Provides methods to access hidraw device's ioctls.\n    \"\"\"\n    def __init__(self, device):\n        \"\"\"\n        device (file, fileno)\n            A file object or a fileno of an open hidraw device node.\n        \"\"\"\n        self._device = device\n\n    def _ioctl(self, func, arg, mutate_flag=False):\n        result = fcntl.ioctl(self._device, func, arg, mutate_flag)\n        if result < 0:\n            raise IOError(result)\n\n    def read(self, size):\n        return self._device.read(size)\n\n    def write(self, buf):\n        return self._device.write(buf)\n\n    def getRawReportDescriptor(self):\n        \"\"\"\n        Return a binary string containing the raw HID report descriptor.\n        \"\"\"\n        descriptor = _hidraw_report_descriptor()\n        size = ctypes.c_uint()\n        self._ioctl(_HIDIOCGRDESCSIZE, size, True)\n        descriptor.size = size\n        self._ioctl(_HIDIOCGRDESC, descriptor, True)\n        return ''.join(chr(x) for x in descriptor.value[:size.value])\n\n    # TODO: decode descriptor into a python object\n    #def getReportDescriptor(self):\n\n    def getInfo(self):\n        \"\"\"\n        Returns a DevInfo instance, a named tuple with the following items:\n        - bustype: one of BUS_USB, BUS_HIL, BUS_BLUETOOTH or BUS_VIRTUAL\n        - vendor: device's vendor number\n        - product: device's product number\n        \"\"\"\n        devinfo = _hidraw_devinfo()\n        self._ioctl(_HIDIOCGRAWINFO, devinfo, True)\n        return DevInfo(devinfo.bustype, devinfo.vendor, devinfo.product)\n\n    def getName(self, length=512):\n        \"\"\"\n        Returns device name as an unicode object.\n        \"\"\"\n        name = ctypes.create_string_buffer(length)\n        self._ioctl(_HIDIOCGRAWNAME(length), name, True)\n        return name.value.decode('UTF-8')\n\n    def getPhysicalAddress(self, length=512):\n        \"\"\"\n        Returns device physical address as a string.\n        See hidraw documentation for value signification, as it depends on\n        device's bus type.\n        \"\"\"\n        name = ctypes.create_string_buffer(length)\n        self._ioctl(_HIDIOCGRAWPHYS(length), name, True)\n        return name.value\n\n    def sendFeatureReport(self, report, report_num=0):\n        \"\"\"\n        Send a feature report.\n        \"\"\"\n        length = len(report) + 1\n        buf = bytearray(length)\n        buf[0] = report_num\n        buf[1:] = report\n        self._ioctl(\n            _HIDIOCSFEATURE(length),\n            (ctypes.c_char * length).from_buffer(buf),\n            True,\n        )\n\n    def getFeatureReport(self, report_num=0, length=63):\n        \"\"\"\n        Receive a feature report.\n        Blocks, unless you configured provided file (descriptor) to be\n        non-blocking.\n        \"\"\"\n        length += 1\n        buf = bytearray(length)\n        buf[0] = report_num\n        self._ioctl(\n            _HIDIOCGFEATURE(length),\n            (ctypes.c_char * length).from_buffer(buf),\n            True,\n        )\n        return buf\n"
  },
  {
    "path": "scc/lib/ioctl_opt.py",
    "content": "\"\"\"\nPythonified linux asm-generic/ioctl.h .\n\n\"type\" parameters expect ctypes-based types (ctypes.Structure subclasses, ...).\n\nhttps://github.com/vpelletier/python-ioctl-opt\nLicensed under GPL 2.0\n\"\"\"\n\nimport ctypes\n\n_IOC_NRBITS = 8\n_IOC_TYPEBITS = 8\n_IOC_SIZEBITS = 14\n_IOC_DIRBITS = 2\n\n_IOC_NRMASK = (1 << _IOC_NRBITS) - 1\n_IOC_TYPEMASK = (1 << _IOC_TYPEBITS) - 1\n_IOC_SIZEMASK = (1 << _IOC_SIZEBITS) - 1\n_IOC_DIRMASK = (1 << _IOC_DIRBITS) - 1\n\n_IOC_NRSHIFT = 0\n_IOC_TYPESHIFT = _IOC_NRSHIFT + _IOC_NRBITS\n_IOC_SIZESHIFT = _IOC_TYPESHIFT + _IOC_TYPEBITS\n_IOC_DIRSHIFT = _IOC_SIZESHIFT + _IOC_SIZEBITS\n\nIOC_NONE = 0\nIOC_WRITE = 1\nIOC_READ = 2\n\ndef IOC(dir, type, nr, size):\n    assert dir <= _IOC_DIRMASK, dir\n    assert type <= _IOC_TYPEMASK, type\n    assert nr <= _IOC_NRMASK, nr\n    assert size <= _IOC_SIZEMASK, size\n    return (dir << _IOC_DIRSHIFT) | (type << _IOC_TYPESHIFT) | (nr << _IOC_NRSHIFT) | (size << _IOC_SIZESHIFT)\n\ndef IOC_TYPECHECK(t):\n    result = ctypes.sizeof(t)\n    assert result <= _IOC_SIZEMASK, result\n    return result\n\ndef IO(type, nr):\n    return IOC(IOC_NONE, type, nr, 0)\n\ndef IOR(type, nr, size):\n    return IOC(IOC_READ, type, nr, IOC_TYPECHECK(size))\n\ndef IOW(type, nr, size):\n    return IOC(IOC_WRITE, type, nr, IOC_TYPECHECK(size))\n\ndef IORW(type, nr, size):\n    return IOC(IOC_READ | IOC_WRITE, type, nr, IOC_TYPECHECK(size))\n\ndef IOC_DIR(nr):\n    return (nr >> _IOC_DIRSHIFT) & _IOC_DIRMASK\n\ndef IOC_TYPE(nr):\n    return (nr >> _IOC_TYPESHIFT) & _IOC_TYPEMASK\n\ndef IOC_NR(nr):\n    return (nr >> _IOC_NRSHIFT) & _IOC_NRMASK\n\ndef IOC_SIZE(nr):\n    return (nr >> _IOC_SIZESHIFT) & _IOC_SIZEMASK\n\nIOC_IN = IOC_WRITE << _IOC_DIRSHIFT\nIOC_OUT = IOC_READ << _IOC_DIRSHIFT\nIOC_INOUT = (IOC_WRITE | IOC_READ) << _IOC_DIRSHIFT\nIOCSIZE_MASK = _IOC_SIZEMASK << _IOC_SIZESHIFT\nIOCSIZE_SHIFT = _IOC_SIZESHIFT\n"
  },
  {
    "path": "scc/lib/jsonencoder.py",
    "content": "\"\"\"\r\nCopied directly from python because I can't find way how to override\r\n_iterencode_list, function burried in 7th level of hell.\r\n\r\nOnly idea here is to have lists encoded in single line.\r\n\"\"\"\r\nimport re\r\n\r\ntry:\r\n\tfrom _json import encode_basestring_ascii as c_encode_basestring_ascii\r\nexcept ImportError:\r\n\tc_encode_basestring_ascii = None\r\ntry:\r\n\tfrom _json import make_encoder as c_make_encoder\r\nexcept ImportError:\r\n\tc_make_encoder = None\r\n\r\nESCAPE = re.compile(r'[\\x00-\\x1f\\\\\"\\b\\f\\n\\r\\t]')\r\nESCAPE_ASCII = re.compile(r'([\\\\\"]|[^\\ -~])')\r\nHAS_UTF8 = re.compile(r'[\\x80-\\xff]')\r\nESCAPE_DCT = {\r\n\t'\\\\': '\\\\\\\\',\r\n\t'\"': '\\\\\"',\r\n\t'\\b': '\\\\b',\r\n\t'\\f': '\\\\f',\r\n\t'\\n': '\\\\n',\r\n\t'\\r': '\\\\r',\r\n\t'\\t': '\\\\t',\r\n}\r\nfor i in range(0x20):\r\n\tESCAPE_DCT.setdefault(chr(i), '\\\\u{0:04x}'.format(i))\r\n\t#ESCAPE_DCT.setdefault(chr(i), '\\\\u%04x' % (i,))\r\n\r\nINFINITY = float('inf')\r\nFLOAT_REPR = repr\r\n\r\ndef encode_basestring(s):\r\n\t\"\"\"Return a JSON representation of a Python string\r\n\r\n\t\"\"\"\r\n\tdef replace(match):\r\n\t\treturn ESCAPE_DCT[match.group(0)]\r\n\treturn '\"' + ESCAPE.sub(replace, s) + '\"'\r\n\r\n\r\ndef py_encode_basestring_ascii(s):\r\n\t\"\"\"Return an ASCII-only JSON representation of a Python string\r\n\r\n\t\"\"\"\r\n\tif isinstance(s, str) and HAS_UTF8.search(s) is not None:\r\n\t\ts = s\r\n\tdef replace(match):\r\n\t\ts = match.group(0)\r\n\t\ttry:\r\n\t\t\treturn ESCAPE_DCT[s]\r\n\t\texcept KeyError:\r\n\t\t\tn = ord(s)\r\n\t\t\tif n < 0x10000:\r\n\t\t\t\treturn '\\\\u{0:04x}'.format(n)\r\n\t\t\t\t#return '\\\\u%04x' % (n,)\r\n\t\t\telse:\r\n\t\t\t\t# surrogate pair\r\n\t\t\t\tn -= 0x10000\r\n\t\t\t\ts1 = 0xd800 | ((n >> 10) & 0x3ff)\r\n\t\t\t\ts2 = 0xdc00 | (n & 0x3ff)\r\n\t\t\t\treturn '\\\\u{0:04x}\\\\u{1:04x}'.format(s1, s2)\r\n\t\t\t\t#return '\\\\u%04x\\\\u%04x' % (s1, s2)\r\n\treturn '\"' + str(ESCAPE_ASCII.sub(replace, s)) + '\"'\r\n\r\n\r\nencode_basestring_ascii = (\r\n\tc_encode_basestring_ascii or py_encode_basestring_ascii)\r\n\r\nclass JSONEncoder(object):\r\n\t\"\"\"Extensible JSON <http://json.org> encoder for Python data structures.\r\n\r\n\tSupports the following objects and types by default:\r\n\r\n\t+-------------------+---------------+\r\n\t| Python\t\t\t| JSON\t\t  |\r\n\t+===================+===============+\r\n\t| dict\t\t\t  | object\t\t|\r\n\t+-------------------+---------------+\r\n\t| list, tuple\t   | array\t\t |\r\n\t+-------------------+---------------+\r\n\t| str, unicode\t  | string\t\t|\r\n\t+-------------------+---------------+\r\n\t| int, long, float  | number\t\t|\r\n\t+-------------------+---------------+\r\n\t| True\t\t\t  | true\t\t  |\r\n\t+-------------------+---------------+\r\n\t| False\t\t\t | false\t\t |\r\n\t+-------------------+---------------+\r\n\t| None\t\t\t  | null\t\t  |\r\n\t+-------------------+---------------+\r\n\r\n\tTo extend this to recognize other objects, subclass and implement a\r\n\t``.default()`` method with another method that returns a serializable\r\n\tobject for ``o`` if possible, otherwise it should call the superclass\r\n\timplementation (to raise ``TypeError``).\r\n\r\n\t\"\"\"\r\n\titem_separator = ', '\r\n\tkey_separator = ': '\r\n\tdef __init__(self, skipkeys=False, ensure_ascii=True,\r\n\t\t\tcheck_circular=True, allow_nan=True, sort_keys=False,\r\n\t\t\tindent=None, separators=None, encoding='utf-8', default=None):\r\n\t\tr\"\"\"Constructor for JSONEncoder, with sensible defaults.\r\n\r\n\t\tIf skipkeys is false, then it is a TypeError to attempt\r\n\t\tencoding of keys that are not str, int, long, float or None.  If\r\n\t\tskipkeys is True, such items are simply skipped.\r\n\r\n\t\tIf *ensure_ascii* is true (the default), all non-ASCII\r\n\t\tcharacters in the output are escaped with \\uXXXX sequences,\r\n\t\tand the results are str instances consisting of ASCII\r\n\t\tcharacters only.  If ensure_ascii is False, a result may be a\r\n\t\tunicode instance.  This usually happens if the input contains\r\n\t\tunicode strings or the *encoding* parameter is used.\r\n\r\n\t\tIf check_circular is true, then lists, dicts, and custom encoded\r\n\t\tobjects will be checked for circular references during encoding to\r\n\t\tprevent an infinite recursion (which would cause an OverflowError).\r\n\t\tOtherwise, no such check takes place.\r\n\r\n\t\tIf allow_nan is true, then NaN, Infinity, and -Infinity will be\r\n\t\tencoded as such.  This behavior is not JSON specification compliant,\r\n\t\tbut is consistent with most JavaScript based encoders and decoders.\r\n\t\tOtherwise, it will be a ValueError to encode such floats.\r\n\r\n\t\tIf sort_keys is true, then the output of dictionaries will be\r\n\t\tsorted by key; this is useful for regression tests to ensure\r\n\t\tthat JSON serializations can be compared on a day-to-day basis.\r\n\r\n\t\tIf indent is a non-negative integer, then JSON array\r\n\t\telements and object members will be pretty-printed with that\r\n\t\tindent level.  An indent level of 0 will only insert newlines.\r\n\t\tNone is the most compact representation.  Since the default\r\n\t\titem separator is ', ',  the output might include trailing\r\n\t\twhitespace when indent is specified.  You can use\r\n\t\tseparators=(',', ': ') to avoid this.\r\n\r\n\t\tIf specified, separators should be a (item_separator, key_separator)\r\n\t\ttuple.  The default is (', ', ': ').  To get the most compact JSON\r\n\t\trepresentation you should specify (',', ':') to eliminate whitespace.\r\n\r\n\t\tIf specified, default is a function that gets called for objects\r\n\t\tthat can't otherwise be serialized.  It should return a JSON encodable\r\n\t\tversion of the object or raise a ``TypeError``.\r\n\r\n\t\tIf encoding is not None, then all input strings will be\r\n\t\ttransformed into unicode using that encoding prior to JSON-encoding.\r\n\t\tThe default is UTF-8.\r\n\r\n\t\t\"\"\"\r\n\r\n\t\tself.skipkeys = skipkeys\r\n\t\tself.ensure_ascii = ensure_ascii\r\n\t\tself.check_circular = check_circular\r\n\t\tself.allow_nan = allow_nan\r\n\t\tself.sort_keys = sort_keys\r\n\t\tself.indent = indent\r\n\t\tif separators is not None:\r\n\t\t\tself.item_separator, self.key_separator = separators\r\n\t\tif default is not None:\r\n\t\t\tself.default = default\r\n\t\tself.encoding = encoding\r\n\r\n\tdef default(self, o):\r\n\t\t\"\"\"Implement this method in a subclass such that it returns\r\n\t\ta serializable object for ``o``, or calls the base implementation\r\n\t\t(to raise a ``TypeError``).\r\n\r\n\t\tFor example, to support arbitrary iterators, you could\r\n\t\timplement default like this::\r\n\r\n\t\t\tdef default(self, o):\r\n\t\t\t\ttry:\r\n\t\t\t\t\titerable = iter(o)\r\n\t\t\t\texcept TypeError:\r\n\t\t\t\t\tpass\r\n\t\t\t\telse:\r\n\t\t\t\t\treturn list(iterable)\r\n\t\t\t\t# Let the base class default method raise the TypeError\r\n\t\t\t\treturn JSONEncoder.default(self, o)\r\n\r\n\t\t\"\"\"\r\n\t\traise TypeError(repr(o) + \" is not JSON serializable\")\r\n\r\n\tdef encode(self, o):\r\n\t\t\"\"\"Return a JSON string representation of a Python data structure.\r\n\r\n\t\t>>> JSONEncoder().encode({\"foo\": [\"bar\", \"baz\"]})\r\n\t\t'{\"foo\": [\"bar\", \"baz\"]}'\r\n\r\n\t\t\"\"\"\r\n\t\t# This is for extremely simple cases and benchmarks.\r\n\t\tif isinstance(o, str):\r\n\t\t\t_encoding = self.encoding\r\n\t\t\tif (_encoding is not None\r\n\t\t\t\t\tand not (_encoding == 'utf-8')):\r\n\t\t\t\t#o = o.decode(_encoding)\r\n\t\t\t\to = o\r\n\r\n\t\t\tif self.ensure_ascii:\r\n\t\t\t\treturn encode_basestring_ascii(o)\r\n\t\t\telse:\r\n\t\t\t\treturn encode_basestring(o)\r\n\t\t# This doesn't pass the iterator directly to ''.join() because the\r\n\t\t# exceptions aren't as detailed.  The list call should be roughly\r\n\t\t# equivalent to the PySequence_Fast that ''.join() would do.\r\n\t\tchunks = self.iterencode(o, _one_shot=True)\r\n\t\tif not isinstance(chunks, (list, tuple)):\r\n\t\t\tchunks = list(chunks)\r\n\t\treturn ''.join(chunks)\r\n\r\n\tdef iterencode(self, o, _one_shot=False):\r\n\t\t\"\"\"Encode the given object and yield each string\r\n\t\trepresentation as available.\r\n\r\n\t\tFor example::\r\n\r\n\t\t\tfor chunk in JSONEncoder().iterencode(bigobject):\r\n\t\t\t\tmysocket.write(chunk)\r\n\r\n\t\t\"\"\"\r\n\t\tif self.check_circular:\r\n\t\t\tmarkers = {}\r\n\t\telse:\r\n\t\t\tmarkers = None\r\n\t\tif self.ensure_ascii:\r\n\t\t\t_encoder = encode_basestring_ascii\r\n\t\telse:\r\n\t\t\t_encoder = encode_basestring\r\n\t\tif self.encoding != 'utf-8':\r\n\t\t\tdef _encoder(o, _orig_encoder=_encoder, _encoding=self.encoding):\r\n\t\t\t\t#if isinstance(o, str):\r\n\t\t\t\t#\to = o.decode(_encoding)\r\n\t\t\t\treturn _orig_encoder(o)\r\n\r\n\t\tdef floatstr(o, allow_nan=self.allow_nan,\r\n\t\t\t\t_repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY):\r\n\t\t\t# Check for specials.  Note that this type of test is processor\r\n\t\t\t# and/or platform-specific, so do tests which don't depend on the\r\n\t\t\t# internals.\r\n\r\n\t\t\tif o != o:\r\n\t\t\t\ttext = 'NaN'\r\n\t\t\telif o == _inf:\r\n\t\t\t\ttext = 'Infinity'\r\n\t\t\telif o == _neginf:\r\n\t\t\t\ttext = '-Infinity'\r\n\t\t\telse:\r\n\t\t\t\treturn _repr(o)\r\n\r\n\t\t\tif not allow_nan:\r\n\t\t\t\traise ValueError(\r\n\t\t\t\t\t\"Out of range float values are not JSON compliant: \" +\r\n\t\t\t\t\trepr(o))\r\n\r\n\t\t\treturn text\r\n\r\n\r\n\t\tif (_one_shot and c_make_encoder is not None\r\n\t\t\t\tand self.indent is None and not self.sort_keys):\r\n\t\t\t_iterencode = c_make_encoder(\r\n\t\t\t\tmarkers, self.default, _encoder, self.indent,\r\n\t\t\t\tself.key_separator, self.item_separator, self.sort_keys,\r\n\t\t\t\tself.skipkeys, self.allow_nan)\r\n\t\telse:\r\n\t\t\t_iterencode = _make_iterencode(\r\n\t\t\t\tmarkers, self.default, _encoder, self.indent, floatstr,\r\n\t\t\t\tself.key_separator, self.item_separator, self.sort_keys,\r\n\t\t\t\tself.skipkeys, _one_shot)\r\n\t\treturn _iterencode(o, 0)\r\n\r\ndef _make_iterencode(markers, _default, _encoder, _indent, _floatstr,\r\n\t\t_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,\r\n\t\t## HACK: hand-optimized bytecode; turn globals into locals\r\n\t\tValueError=ValueError,\r\n\t\tbasestring=str,\r\n\t\tdict=dict,\r\n\t\tfloat=float,\r\n\t\tid=id,\r\n\t\tint=int,\r\n\t\tisinstance=isinstance,\r\n\t\tlist=list,\r\n\t\tlong=int,\r\n\t\tstr=str,\r\n\t\ttuple=tuple,\r\n\t):\r\n\r\n\tdef _iterencode_list(lst, _current_indent_level):\r\n\t\tif not lst:\r\n\t\t\tyield '[]'\r\n\t\t\treturn\r\n\t\tif markers is not None:\r\n\t\t\tmarkerid = id(lst)\r\n\t\t\tif markerid in markers:\r\n\t\t\t\traise ValueError(\"Circular reference detected\")\r\n\t\t\tmarkers[markerid] = lst\r\n\t\tbuf = '['\r\n\t\tif False: #  _indent is not None:\r\n\t\t\t_current_indent_level += 1\r\n\t\t\tnewline_indent = '\\n' + (' ' * (_indent * _current_indent_level))\r\n\t\t\tseparator = _item_separator + newline_indent\r\n\t\t\tbuf += newline_indent\r\n\t\telse:\r\n\t\t\tnewline_indent = None\r\n\t\t\tseparator = _item_separator\r\n\t\tnewline_indent = None\r\n\t\tseparator = _item_separator\r\n\t\tfirst = True\r\n\t\tfor value in lst:\r\n\t\t\tif first:\r\n\t\t\t\tfirst = False\r\n\t\t\telse:\r\n\t\t\t\tbuf = separator\r\n\t\t\tif isinstance(value, basestring):\r\n\t\t\t\tyield buf + _encoder(value)\r\n\t\t\telif value is None:\r\n\t\t\t\tyield buf + 'null'\r\n\t\t\telif value is True:\r\n\t\t\t\tyield buf + 'true'\r\n\t\t\telif value is False:\r\n\t\t\t\tyield buf + 'false'\r\n\t\t\telif isinstance(value, (int, long)):\r\n\t\t\t\tyield buf + str(value)\r\n\t\t\telif isinstance(value, float):\r\n\t\t\t\tyield buf + _floatstr(value)\r\n\t\t\telse:\r\n\t\t\t\tyield buf\r\n\t\t\t\tif isinstance(value, (list, tuple)):\r\n\t\t\t\t\tchunks = _iterencode_list(value, _current_indent_level)\r\n\t\t\t\telif isinstance(value, dict):\r\n\t\t\t\t\tchunks = _iterencode_dict(value, _current_indent_level)\r\n\t\t\t\telse:\r\n\t\t\t\t\tchunks = _iterencode(value, _current_indent_level)\r\n\t\t\t\tfor chunk in chunks:\r\n\t\t\t\t\tyield chunk\r\n\t\tif newline_indent is not None:\r\n\t\t\t_current_indent_level -= 1\r\n\t\t\tyield '\\n' + (' ' * (_indent * _current_indent_level))\r\n\t\tyield ']'\r\n\t\tif markers is not None:\r\n\t\t\tdel markers[markerid]\r\n\r\n\tdef _iterencode_dict(dct, _current_indent_level):\r\n\t\tif not dct:\r\n\t\t\tyield '{}'\r\n\t\t\treturn\r\n\t\tif markers is not None:\r\n\t\t\tmarkerid = id(dct)\r\n\t\t\tif markerid in markers:\r\n\t\t\t\traise ValueError(\"Circular reference detected\")\r\n\t\t\tmarkers[markerid] = dct\r\n\t\tyield '{'\r\n\t\tif _indent is not None:\r\n\t\t\t_current_indent_level += 1\r\n\t\t\tnewline_indent = '\\n' + (' ' * (_indent * _current_indent_level))\r\n\t\t\titem_separator = _item_separator + newline_indent\r\n\t\t\tyield newline_indent\r\n\t\telse:\r\n\t\t\tnewline_indent = None\r\n\t\t\titem_separator = _item_separator\r\n\t\tfirst = True\r\n\t\tif _sort_keys:\r\n\t\t\titems = sorted(dct.items(), key=lambda kv: kv[0])\r\n\t\telse:\r\n\t\t\titems = dct.iteritems()\r\n\t\tfor key, value in items:\r\n\t\t\tif isinstance(key, basestring):\r\n\t\t\t\tpass\r\n\t\t\t# JavaScript is weakly typed for these, so it makes sense to\r\n\t\t\t# also allow them.  Many encoders seem to do something like this.\r\n\t\t\telif isinstance(key, float):\r\n\t\t\t\tkey = _floatstr(key)\r\n\t\t\telif key is True:\r\n\t\t\t\tkey = 'true'\r\n\t\t\telif key is False:\r\n\t\t\t\tkey = 'false'\r\n\t\t\telif key is None:\r\n\t\t\t\tkey = 'null'\r\n\t\t\telif isinstance(key, (int, long)):\r\n\t\t\t\tkey = str(key)\r\n\t\t\telif _skipkeys:\r\n\t\t\t\tcontinue\r\n\t\t\telse:\r\n\t\t\t\traise TypeError(\"key \" + repr(key) + \" is not a string\")\r\n\t\t\tif first:\r\n\t\t\t\tfirst = False\r\n\t\t\telse:\r\n\t\t\t\tyield item_separator\r\n\t\t\tyield _encoder(key)\r\n\t\t\tyield _key_separator\r\n\t\t\tif isinstance(value, basestring):\r\n\t\t\t\tyield _encoder(value)\r\n\t\t\telif value is None:\r\n\t\t\t\tyield 'null'\r\n\t\t\telif value is True:\r\n\t\t\t\tyield 'true'\r\n\t\t\telif value is False:\r\n\t\t\t\tyield 'false'\r\n\t\t\telif isinstance(value, (int, long)):\r\n\t\t\t\tyield str(value)\r\n\t\t\telif isinstance(value, float):\r\n\t\t\t\tyield _floatstr(value)\r\n\t\t\telse:\r\n\t\t\t\tif isinstance(value, (list, tuple)):\r\n\t\t\t\t\tchunks = _iterencode_list(value, _current_indent_level)\r\n\t\t\t\telif isinstance(value, dict):\r\n\t\t\t\t\tchunks = _iterencode_dict(value, _current_indent_level)\r\n\t\t\t\telse:\r\n\t\t\t\t\tchunks = _iterencode(value, _current_indent_level)\r\n\t\t\t\tfor chunk in chunks:\r\n\t\t\t\t\tyield chunk\r\n\t\tif newline_indent is not None:\r\n\t\t\t_current_indent_level -= 1\r\n\t\t\tyield '\\n' + (' ' * (_indent * _current_indent_level))\r\n\t\tyield '}'\r\n\t\tif markers is not None:\r\n\t\t\tdel markers[markerid]\r\n\r\n\tdef _iterencode(o, _current_indent_level):\r\n\t\tif isinstance(o, basestring):\r\n\t\t\tyield _encoder(o)\r\n\t\telif o is None:\r\n\t\t\tyield 'null'\r\n\t\telif o is True:\r\n\t\t\tyield 'true'\r\n\t\telif o is False:\r\n\t\t\tyield 'false'\r\n\t\telif isinstance(o, (int, long)):\r\n\t\t\tyield str(o)\r\n\t\telif isinstance(o, float):\r\n\t\t\tyield _floatstr(o)\r\n\t\telif isinstance(o, (list, tuple)):\r\n\t\t\tfor chunk in _iterencode_list(o, _current_indent_level):\r\n\t\t\t\tyield chunk\r\n\t\telif isinstance(o, dict):\r\n\t\t\tfor chunk in _iterencode_dict(o, _current_indent_level):\r\n\t\t\t\tyield chunk\r\n\t\telse:\r\n\t\t\tif markers is not None:\r\n\t\t\t\tmarkerid = id(o)\r\n\t\t\t\tif markerid in markers:\r\n\t\t\t\t\traise ValueError(\"Circular reference detected\")\r\n\t\t\t\tmarkers[markerid] = o\r\n\t\t\to = _default(o)\r\n\t\t\tfor chunk in _iterencode(o, _current_indent_level):\r\n\t\t\t\tyield chunk\r\n\t\t\tif markers is not None:\r\n\t\t\t\tdel markers[markerid]\r\n\r\n\treturn _iterencode\r\n"
  },
  {
    "path": "scc/lib/libusb1.py",
    "content": "# Copyright (C) 2010-2016  Vincent Pelletier <plr.vincent@gmail.com>\n#\n# This library is free software; you can redistribute it and/or\n# modify it under the terms of the GNU Lesser General Public\n# License as published by the Free Software Foundation; either\n# version 2.1 of the License, or (at your option) any later version.\n#\n# This library is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public\n# License along with this library; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n\n# pylint: disable=invalid-name, too-few-public-methods, too-many-arguments\n# pylint: disable=missing-docstring\n\"\"\"\nPython ctypes bindings for libusb-1.0.\n\nYou should not need to import this if you use usb1 module.\n\nDeclares all constants, data structures and exported symbols.\nLocates and loads libusb1 dynamic library.\n\"\"\"\nfrom ctypes import Structure, LittleEndianStructure, \\\n    CFUNCTYPE, POINTER, addressof, sizeof, cast, \\\n    c_short, c_int, c_uint, c_size_t, c_long, \\\n    c_uint8, c_uint16, c_uint32, \\\n    c_void_p, c_char_p, py_object, string_at, pointer\ntry:\n    from ctypes import c_ssize_t\nexcept ImportError:\n    from ctypes import c_longlong\n    # c_ssize_t is new in Python 2.7\n    if sizeof(c_int) == sizeof(c_size_t):\n        c_ssize_t = c_int\n    elif sizeof(c_long) == sizeof(c_size_t):\n        c_ssize_t = c_long\n    elif sizeof(c_longlong) == sizeof(c_size_t):\n        c_ssize_t = c_longlong\n    else:\n        raise ValueError('Unsupported arch: sizeof(c_size_t) = %r' % (\n            sizeof(c_size_t), ))\nimport ctypes.util\nimport platform\nimport os.path\nimport sys\n\nclass Enum(object):\n    def __init__(self, member_dict, scope_dict=None):\n        if scope_dict is None:\n            # Affect caller's locals, not this module's.\n            # pylint: disable=protected-access\n            scope_dict = sys._getframe(1).f_locals\n            # pylint: enable=protected-access\n        forward_dict = {}\n        reverse_dict = {}\n        next_value = 0\n        for name, value in member_dict.items():\n            if value is None:\n                value = next_value\n                next_value += 1\n            forward_dict[name] = value\n            if value in reverse_dict:\n                raise ValueError('Multiple names for value %r: %r, %r' % (\n                    value, reverse_dict[value], name\n                ))\n            reverse_dict[value] = name\n            scope_dict[name] = value\n        self.forward_dict = forward_dict\n        self.reverse_dict = reverse_dict\n\n    def __call__(self, value):\n        return self.reverse_dict[value]\n\n    def get(self, value, default=None):\n        return self.reverse_dict.get(value, default)\n\n_desc_type_dict = {\n    'b': c_uint8,\n    'bcd': c_uint16,\n    'bm': c_uint8,\n    'dw': c_uint32,\n    'i': c_uint8,\n    'id': c_uint16,\n    'w': c_uint16,\n}\n\ndef newStruct(field_name_list):\n    \"\"\"\n    Create a ctype structure class based on USB standard field naming\n    (type-prefixed).\n    \"\"\"\n    field_list = []\n    append = field_list.append\n    for field in field_name_list:\n        type_prefix = ''\n        for char in field:\n            if not char.islower():\n                break\n            type_prefix += char\n        append((field, _desc_type_dict[type_prefix]))\n    result = type('some_descriptor', (LittleEndianStructure, ), {})\n    # Not using type()'s 3rd param to initialise class, as per ctypes\n    # documentation:\n    #   _pack_ must already be defined when _fields_ is assigned, otherwise it\n    #   will have no effect.\n    # pylint: disable=protected-access\n    result._pack_ = 1\n    result._fields_ = field_list\n    # pylint: enable=protected-access\n    return result\n\ndef newDescriptor(field_name_list):\n    \"\"\"\n    Create a USB descriptor ctype structure, ie starting with bLength and\n    bDescriptorType fields.\n\n    See newStruct().\n    \"\"\"\n    return newStruct(['bLength', 'bDescriptorType'] + list(field_name_list))\n\nclass USBError(Exception):\n    value = None\n\n    def __init__(self, value=None):\n        Exception.__init__(self)\n        if value is not None:\n            self.value = value\n\n    def __str__(self):\n        return '%s [%s]' % (libusb_error.get(self.value, 'Unknown error'),\n                            self.value)\n\nif sys.version_info[0] == 3:\n    _string_item_to_int = lambda x: x\n    _empty_char_p = bytes()\nelse:\n    _string_item_to_int = ord\n    _empty_char_p = ''\n\nc_uchar = c_uint8\nc_int_p = POINTER(c_int)\n\nLITTLE_ENDIAN = sys.byteorder == 'little'\n\nclass timeval(Structure):\n    _fields_ = [('tv_sec', c_long),\n                ('tv_usec', c_long)]\ntimeval_p = POINTER(timeval)\n\ndef _loadLibrary():\n    system = platform.system()\n    if system == 'Windows':\n        dll_loader = ctypes.WinDLL\n        suffix = '.dll'\n    else:\n        dll_loader = ctypes.CDLL\n        suffix = system == 'Darwin' and '.dylib' or '.so'\n    loader_kw = {}\n    if sys.version_info[:2] >= (2, 6):\n        loader_kw['use_errno'] = True\n        loader_kw['use_last_error'] = True\n    try:\n        return dll_loader('libusb-1.0' + suffix, **loader_kw)\n    except OSError:\n        libusb_path = None\n        base_name = 'usb-1.0'\n        if 'FreeBSD' in system:\n            # libusb.so.2 on FreeBSD: load('libusb.so') would work fine, but...\n            # libusb.so.2debian on Debian GNU/kFreeBSD: here it wouldn't work.\n            # So use find_library instead.\n            base_name = 'usb'\n        elif system == 'Darwin':\n            for libusb_path in (\n                    # macport standard path\n                    '/opt/local/lib/libusb-1.0.dylib',\n                    # fink standard path\n                    '/sw/lib/libusb-1.0.dylib',\n                ):\n                if os.path.exists(libusb_path):\n                    break\n            else:\n                libusb_path = None\n        if libusb_path is None:\n            libusb_path = ctypes.util.find_library(base_name)\n            if libusb_path is None:\n                raise\n        return dll_loader(libusb_path, **loader_kw)\n\nlibusb = _loadLibrary()\n\n# libusb.h\ndef bswap16(x):\n    return ((x & 0xff) << 8) | (x >> 8)\n\nif LITTLE_ENDIAN:\n    def libusb_cpu_to_le16(x):\n        return x\n    def libusb_le16_to_cpu(x):\n        return x\nelse:\n    libusb_cpu_to_le16 = bswap16\n    libusb_le16_to_cpu = bswap16\n\n# standard USB stuff\n\n# Device and/or Interface Class codes\nlibusb_class_code = Enum({\n    # In the context of a device descriptor,\n    # this bDeviceClass value indicates that each interface specifies its\n    # own class information and all interfaces operate independently.\n    'LIBUSB_CLASS_PER_INTERFACE': 0,\n    # Audio class\n    'LIBUSB_CLASS_AUDIO': 1,\n    # Communications class\n    'LIBUSB_CLASS_COMM': 2,\n    # Human Interface Device class\n    'LIBUSB_CLASS_HID': 3,\n    # Physical\n    'LIBUSB_CLASS_PHYSICAL': 5,\n    # Printer class\n    'LIBUSB_CLASS_PRINTER': 7,\n    # Picture transfer protocol class\n    'LIBUSB_CLASS_PTP': 6,\n    # Mass storage class\n    'LIBUSB_CLASS_MASS_STORAGE': 8,\n    # Hub class\n    'LIBUSB_CLASS_HUB': 9,\n    # Data class\n    'LIBUSB_CLASS_DATA': 10,\n    # Smart Card\n    'LIBUSB_CLASS_SMART_CARD': 0x0b,\n    # Content Security\n    'LIBUSB_CLASS_CONTENT_SECURITY': 0x0d,\n    # Video\n    'LIBUSB_CLASS_VIDEO': 0x0e,\n    # Personal Healthcare\n    'LIBUSB_CLASS_PERSONAL_HEALTHCARE': 0x0f,\n    # Diagnostic Device\n    'LIBUSB_CLASS_DIAGNOSTIC_DEVICE': 0xdc,\n    # Wireless class\n    'LIBUSB_CLASS_WIRELESS': 0xe0,\n    # Application class\n    'LIBUSB_CLASS_APPLICATION': 0xfe,\n    # Class is vendor-specific\n    'LIBUSB_CLASS_VENDOR_SPEC': 0xff\n})\n# pylint: disable=undefined-variable\nLIBUSB_CLASS_IMAGE = LIBUSB_CLASS_PTP\n# pylint: enable=undefined-variable\n\n# Descriptor types as defined by the USB specification.\nlibusb_descriptor_type = Enum({\n    # Device descriptor. See libusb_device_descriptor.\n    'LIBUSB_DT_DEVICE': 0x01,\n    # Configuration descriptor. See libusb_config_descriptor.\n    'LIBUSB_DT_CONFIG': 0x02,\n    # String descriptor\n    'LIBUSB_DT_STRING': 0x03,\n    # Interface descriptor. See libusb_interface_descriptor.\n    'LIBUSB_DT_INTERFACE': 0x04,\n    # Endpoint descriptor. See libusb_endpoint_descriptor.\n    'LIBUSB_DT_ENDPOINT': 0x05,\n    # HID descriptor\n    'LIBUSB_DT_HID': 0x21,\n    # HID report descriptor\n    'LIBUSB_DT_REPORT': 0x22,\n    # Physical descriptor\n    'LIBUSB_DT_PHYSICAL': 0x23,\n    # Hub descriptor\n    'LIBUSB_DT_HUB': 0x29,\n})\n\n# Descriptor sizes per descriptor type\nLIBUSB_DT_DEVICE_SIZE = 18\nLIBUSB_DT_CONFIG_SIZE = 9\nLIBUSB_DT_INTERFACE_SIZE = 9\nLIBUSB_DT_ENDPOINT_SIZE = 7\nLIBUSB_DT_ENDPOINT_AUDIO_SIZE = 9 # Audio extension\nLIBUSB_DT_HUB_NONVAR_SIZE = 7\n\nLIBUSB_ENDPOINT_ADDRESS_MASK = 0x0f # in bEndpointAddress\nLIBUSB_ENDPOINT_DIR_MASK = 0x80\n# BBB\nUSB_ENDPOINT_ADDRESS_MASK = LIBUSB_ENDPOINT_ADDRESS_MASK\nUSB_ENDPOINT_DIR_MASK = LIBUSB_ENDPOINT_DIR_MASK\n\n# Endpoint direction. Values for bit 7 of the endpoint address scheme.\nlibusb_endpoint_direction = Enum({\n    # In: device-to-host\n    'LIBUSB_ENDPOINT_IN': 0x80,\n    # Out: host-to-device\n    'LIBUSB_ENDPOINT_OUT': 0x00\n})\n\nLIBUSB_TRANSFER_TYPE_MASK = 0x03 # in bmAttributes\n\n# Endpoint transfer type. Values for bits 0:1 of the endpoint attributes field.\nlibusb_transfer_type = Enum({\n    # Control endpoint\n    'LIBUSB_TRANSFER_TYPE_CONTROL': 0,\n    # Isochronous endpoint\n    'LIBUSB_TRANSFER_TYPE_ISOCHRONOUS': 1,\n    # Bulk endpoint\n    'LIBUSB_TRANSFER_TYPE_BULK': 2,\n    # Interrupt endpoint\n    'LIBUSB_TRANSFER_TYPE_INTERRUPT': 3,\n})\n\n# Standard requests, as defined in table 9-3 of the USB2 specifications\nlibusb_standard_request = Enum({\n    # Request status of the specific recipient\n    'LIBUSB_REQUEST_GET_STATUS': 0x00,\n    # Clear or disable a specific feature\n    'LIBUSB_REQUEST_CLEAR_FEATURE': 0x01,\n    # 0x02 is reserved\n    # Set or enable a specific feature\n    'LIBUSB_REQUEST_SET_FEATURE': 0x03,\n    # 0x04 is reserved\n    # Set device address for all future accesses\n    'LIBUSB_REQUEST_SET_ADDRESS': 0x05,\n    # Get the specified descriptor\n    'LIBUSB_REQUEST_GET_DESCRIPTOR': 0x06,\n    # Used to update existing descriptors or add new descriptors\n    'LIBUSB_REQUEST_SET_DESCRIPTOR': 0x07,\n    # Get the current device configuration value\n    'LIBUSB_REQUEST_GET_CONFIGURATION': 0x08,\n    # Set device configuration\n    'LIBUSB_REQUEST_SET_CONFIGURATION': 0x09,\n    # Return the selected alternate setting for the specified interface\n    'LIBUSB_REQUEST_GET_INTERFACE': 0x0a,\n    # Select an alternate interface for the specified interface\n    'LIBUSB_REQUEST_SET_INTERFACE': 0x0b,\n    # Set then report an endpoint's synchronization frame\n    'LIBUSB_REQUEST_SYNCH_FRAME': 0x0c,\n})\n\n# Request type bits of the bmRequestType field in control transfers.\nlibusb_request_type = Enum({\n    # Standard\n    'LIBUSB_REQUEST_TYPE_STANDARD': (0x00 << 5),\n    # Class\n    'LIBUSB_REQUEST_TYPE_CLASS': (0x01 << 5),\n    # Vendor\n    'LIBUSB_REQUEST_TYPE_VENDOR': (0x02 << 5),\n    # Reserved\n    'LIBUSB_REQUEST_TYPE_RESERVED': (0x03 << 5),\n})\n\n# BBB\n# pylint: disable=bad-whitespace,undefined-variable\nLIBUSB_TYPE_STANDARD = LIBUSB_REQUEST_TYPE_STANDARD\nLIBUSB_TYPE_CLASS    = LIBUSB_REQUEST_TYPE_CLASS\nLIBUSB_TYPE_VENDOR   = LIBUSB_REQUEST_TYPE_VENDOR\nLIBUSB_TYPE_RESERVED = LIBUSB_REQUEST_TYPE_RESERVED\n# pylint: enable=bad-whitespace,undefined-variable\n\n# Recipient bits of the bmRequestType field in control transfers. Values 4\n# through 31 are reserved.\nlibusb_request_recipient = Enum({\n    # Device\n    'LIBUSB_RECIPIENT_DEVICE': 0x00,\n    # Interface\n    'LIBUSB_RECIPIENT_INTERFACE': 0x01,\n    # Endpoint\n    'LIBUSB_RECIPIENT_ENDPOINT': 0x02,\n    # Other\n    'LIBUSB_RECIPIENT_OTHER': 0x03,\n})\n\nLIBUSB_ISO_SYNC_TYPE_MASK = 0x0c\n\n# Synchronization type for isochronous endpoints. Values for bits 2:3 of the\n# bmAttributes field in libusb_endpoint_descriptor.\nlibusb_iso_sync_type = Enum({\n    # No synchronization\n    'LIBUSB_ISO_SYNC_TYPE_NONE': 0,\n    # Asynchronous\n    'LIBUSB_ISO_SYNC_TYPE_ASYNC': 1,\n    # Adaptive\n    'LIBUSB_ISO_SYNC_TYPE_ADAPTIVE': 2,\n    # Synchronous\n    'LIBUSB_ISO_SYNC_TYPE_SYNC': 3,\n})\n\nLIBUSB_ISO_USAGE_TYPE_MASK = 0x30\n\n# Usage type for isochronous endpoints. Values for bits 4:5 of the\n# bmAttributes field in libusb_endpoint_descriptor.\nlibusb_iso_usage_type = Enum({\n    # Data endpoint\n    'LIBUSB_ISO_USAGE_TYPE_DATA': 0,\n    # Feedback endpoint\n    'LIBUSB_ISO_USAGE_TYPE_FEEDBACK': 1,\n    # Implicit feedback Data endpoint\n    'LIBUSB_ISO_USAGE_TYPE_IMPLICIT': 2,\n})\n\n# A structure representing the standard USB device descriptor. This\n# descriptor is documented in section 9.6.1 of the USB 2.0 specification.\n# All multiple-byte fields are represented in host-endian format.\nclass libusb_device_descriptor(Structure):\n    _fields_ = [\n        # Size of this descriptor (in bytes)\n        ('bLength', c_uint8),\n        # Descriptor type. Will have value LIBUSB_DT_DEVICE in this\n        # context.\n        ('bDescriptorType', c_uint8),\n        # USB specification release number in binary-coded decimal. A\n        # value of 0x0200 indicates USB 2.0, 0x0110 indicates USB 1.1,\n        # etc.\n        ('bcdUSB', c_uint16),\n        # USB-IF class code for the device. See libusb_class_code.\n        ('bDeviceClass', c_uint8),\n        # USB-IF subclass code for the device, qualified by the\n        # bDeviceClass value\n        ('bDeviceSubClass', c_uint8),\n        # USB-IF protocol code for the device, qualified by the\n        # bDeviceClass and bDeviceSubClass values\n        ('bDeviceProtocol', c_uint8),\n        # Maximum packet size for endpoint 0\n        ('bMaxPacketSize0', c_uint8),\n        # USB-IF vendor ID\n        ('idVendor', c_uint16),\n        # USB-IF product ID\n        ('idProduct', c_uint16),\n        # Device release number in binary-coded decimal\n        ('bcdDevice', c_uint16),\n        # Index of string descriptor describing manufacturer\n        ('iManufacturer', c_uint8),\n        # Index of string descriptor describing product\n        ('iProduct', c_uint8),\n        # Index of string descriptor containing device serial number\n        ('iSerialNumber', c_uint8),\n        # Number of possible configurations\n        ('bNumConfigurations', c_uint8)]\nlibusb_device_descriptor_p = POINTER(libusb_device_descriptor)\n\nclass libusb_endpoint_descriptor(Structure):\n    _fields_ = [\n        ('bLength', c_uint8),\n        ('bDescriptorType', c_uint8),\n        ('bEndpointAddress', c_uint8),\n        ('bmAttributes', c_uint8),\n        ('wMaxPacketSize', c_uint16),\n        ('bInterval', c_uint8),\n        ('bRefresh', c_uint8),\n        ('bSynchAddress', c_uint8),\n        ('extra', c_void_p),\n        ('extra_length', c_int)]\nlibusb_endpoint_descriptor_p = POINTER(libusb_endpoint_descriptor)\n\nclass libusb_interface_descriptor(Structure):\n    _fields_ = [\n        ('bLength', c_uint8),\n        ('bDescriptorType', c_uint8),\n        ('bInterfaceNumber', c_uint8),\n        ('bAlternateSetting', c_uint8),\n        ('bNumEndpoints', c_uint8),\n        ('bInterfaceClass', c_uint8),\n        ('bInterfaceSubClass', c_uint8),\n        ('bInterfaceProtocol', c_uint8),\n        ('iInterface', c_uint8),\n        ('endpoint', libusb_endpoint_descriptor_p),\n        ('extra', c_void_p),\n        ('extra_length', c_int)]\nlibusb_interface_descriptor_p = POINTER(libusb_interface_descriptor)\n\nclass libusb_interface(Structure):\n    _fields_ = [('altsetting', libusb_interface_descriptor_p),\n                ('num_altsetting', c_int)]\nlibusb_interface_p = POINTER(libusb_interface)\n\nclass libusb_config_descriptor(Structure):\n    _fields_ = [\n        ('bLength', c_uint8),\n        ('bDescriptorType', c_uint8),\n        ('wTotalLength', c_uint16),\n        ('bNumInterfaces', c_uint8),\n        ('bConfigurationValue', c_uint8),\n        ('iConfiguration', c_uint8),\n        ('bmAttributes', c_uint8),\n        ('MaxPower', c_uint8),\n        ('interface', libusb_interface_p),\n        ('extra', c_void_p),\n        ('extra_length', c_int)]\nlibusb_config_descriptor_p = POINTER(libusb_config_descriptor)\nlibusb_config_descriptor_p_p = POINTER(libusb_config_descriptor_p)\n\nclass libusb_control_setup(Structure):\n    _fields_ = [\n        ('bmRequestType', c_uint8),\n        ('bRequest', c_uint8),\n        ('wValue', c_uint16),\n        ('wIndex', c_uint16),\n        ('wLength', c_uint16)]\nlibusb_control_setup_p = POINTER(libusb_control_setup)\n\nLIBUSB_CONTROL_SETUP_SIZE = sizeof(libusb_control_setup)\n\n# Structure representing a libusb session. The concept of individual libusb\n# sessions allows for your program to use two libraries (or dynamically\n# load two modules) which both independently use libusb. This will prevent\n# interference between the individual libusb users - for example\n# libusb_set_debug() will not affect the other user of the library, and\n# libusb_exit() will not destroy resources that the other user is still\n# using.\n#\n# Sessions are created by libusb_init() and destroyed through libusb_exit().\n# If your application is guaranteed to only ever include a single libusb\n# user (i.e. you), you do not have to worry about contexts: pass NULL in\n# every function call where a context is required. The default context\n# will be used.\n#\n# For more information, see \\ref contexts.\nclass libusb_context(Structure):\n    pass\nlibusb_context_p = POINTER(libusb_context)\nlibusb_context_p_p = POINTER(libusb_context_p)\n\n# Structure representing a USB device detected on the system. This is an\n# opaque type for which you are only ever provided with a pointer, usually\n# originating from libusb_get_device_list().\n#\n# Certain operations can be performed on a device, but in order to do any\n# I/O you will have to first obtain a device handle using libusb_open().\n#\n# Devices are reference counted with libusb_device_ref() and\n# libusb_device_unref(), and are freed when the reference count reaches 0.\n# New devices presented by libusb_get_device_list() have a reference count of\n# 1, and libusb_free_device_list() can optionally decrease the reference count\n# on all devices in the list. libusb_open() adds another reference which is\n# later destroyed by libusb_close().\nclass libusb_device(Structure):\n    pass\nlibusb_device_p = POINTER(libusb_device)\nlibusb_device_p_p = POINTER(libusb_device_p)\nlibusb_device_p_p_p = POINTER(libusb_device_p_p)\n\n# Structure representing a handle on a USB device. This is an opaque type for\n# which you are only ever provided with a pointer, usually originating from\n# libusb_open().\n#\n# A device handle is used to perform I/O and other operations. When finished\n# with a device handle, you should call libusb_close().\nclass libusb_device_handle(Structure):\n    pass\nlibusb_device_handle_p = POINTER(libusb_device_handle)\nlibusb_device_handle_p_p = POINTER(libusb_device_handle_p)\n\nclass libusb_version(Structure):\n    _fields_ = [\n        ('major', c_uint16),\n        ('minor', c_uint16),\n        ('micro', c_uint16),\n        ('nano', c_uint16),\n        ('rc', c_char_p),\n        ('describe', c_char_p),\n    ]\n\nlibusb_speed = Enum({\n    # The OS doesn't report or know the device speed.\n    'LIBUSB_SPEED_UNKNOWN': 0,\n    # The device is operating at low speed (1.5MBit/s).\n    'LIBUSB_SPEED_LOW': 1,\n    # The device is operating at full speed (12MBit/s).\n    'LIBUSB_SPEED_FULL': 2,\n    # The device is operating at high speed (480MBit/s).\n    'LIBUSB_SPEED_HIGH': 3,\n    # The device is operating at super speed (5000MBit/s).\n    'LIBUSB_SPEED_SUPER': 4,\n})\n\nlibusb_supported_speed = Enum({\n    # Low speed operation supported (1.5MBit/s).\n    'LIBUSB_LOW_SPEED_OPERATION': 1,\n    # Full speed operation supported (12MBit/s).\n    'LIBUSB_FULL_SPEED_OPERATION': 2,\n    # High speed operation supported (480MBit/s).\n    'LIBUSB_HIGH_SPEED_OPERATION': 4,\n    # Superspeed operation supported (5000MBit/s).\n    'LIBUSB_5GBPS_OPERATION': 8,\n})\n\n# Error codes. Most libusb functions return 0 on success or one of these\n# codes on failure.\nlibusb_error = Enum({\n    # Success (no error)\n    'LIBUSB_SUCCESS': 0,\n    # Input/output error\n    'LIBUSB_ERROR_IO': -1,\n    # Invalid parameter\n    'LIBUSB_ERROR_INVALID_PARAM': -2,\n    # Access denied (insufficient permissions)\n    'LIBUSB_ERROR_ACCESS': -3,\n    # No such device (it may have been disconnected)\n    'LIBUSB_ERROR_NO_DEVICE': -4,\n    # Entity not found\n    'LIBUSB_ERROR_NOT_FOUND': -5,\n    # Resource busy\n    'LIBUSB_ERROR_BUSY': -6,\n    # Operation timed out\n    'LIBUSB_ERROR_TIMEOUT': -7,\n    # Overflow\n    'LIBUSB_ERROR_OVERFLOW': -8,\n    # Pipe error\n    'LIBUSB_ERROR_PIPE': -9,\n    # System call interrupted (perhaps due to signal)\n    'LIBUSB_ERROR_INTERRUPTED': -10,\n    # Insufficient memory\n    'LIBUSB_ERROR_NO_MEM': -11,\n    # Operation not supported or unimplemented on this platform\n    'LIBUSB_ERROR_NOT_SUPPORTED': -12,\n    # Other error\n    'LIBUSB_ERROR_OTHER': -99,\n})\n\n# Transfer status codes\nlibusb_transfer_status = Enum({\n    # Transfer completed without error. Note that this does not indicate\n    # that the entire amount of requested data was transferred.\n    'LIBUSB_TRANSFER_COMPLETED': 0,\n    # Transfer failed\n    'LIBUSB_TRANSFER_ERROR': 1,\n    # Transfer timed out\n    'LIBUSB_TRANSFER_TIMED_OUT': 2,\n    # Transfer was cancelled\n    'LIBUSB_TRANSFER_CANCELLED': 3,\n    # For bulk/interrupt endpoints: halt condition detected (endpoint\n    # stalled). For control endpoints: control request not supported.\n    'LIBUSB_TRANSFER_STALL': 4,\n    # Device was disconnected\n    'LIBUSB_TRANSFER_NO_DEVICE': 5,\n    # Device sent more data than requested\n    'LIBUSB_TRANSFER_OVERFLOW': 6,\n})\n\n# libusb_transfer.flags values\nlibusb_transfer_flags = Enum({\n    # Report short frames as errors\n    'LIBUSB_TRANSFER_SHORT_NOT_OK': 1 << 0,\n    # Automatically free() transfer buffer during libusb_free_transfer()\n    'LIBUSB_TRANSFER_FREE_BUFFER': 1 << 1,\n    # Automatically call libusb_free_transfer() after callback returns.\n    # If this flag is set, it is illegal to call libusb_free_transfer()\n    # from your transfer callback, as this will result in a double-free\n    # when this flag is acted upon.\n    'LIBUSB_TRANSFER_FREE_TRANSFER': 1 << 2,\n    # Terminate transfers that are a multiple of the endpoint's\n    # wMaxPacketSize with an extra zero length packet.\n    'LIBUSB_TRANSFER_ADD_ZERO_PACKET': 1 << 3,\n})\n\n# Isochronous packet descriptor.\nclass libusb_iso_packet_descriptor(Structure):\n    _fields_ = [('length', c_uint),\n                ('actual_length', c_uint),\n                ('status', c_int)] # enum libusb_transfer_status\nlibusb_iso_packet_descriptor_p = POINTER(libusb_iso_packet_descriptor)\n\nclass libusb_transfer(Structure):\n    pass\nlibusb_transfer_p = POINTER(libusb_transfer)\n\nlibusb_transfer_cb_fn_p = CFUNCTYPE(None, libusb_transfer_p)\n\n_libusb_transfer_fields = [\n    ('dev_handle', libusb_device_handle_p),\n    ('flags', c_uint8),\n    ('endpoint', c_uchar),\n    ('type', c_uchar),\n    ('timeout', c_uint),\n    ('status', c_int), # enum libusb_transfer_status\n    ('length', c_int),\n    ('actual_length', c_int),\n    ('callback', libusb_transfer_cb_fn_p),\n    ('user_data', c_void_p),\n    ('buffer', c_void_p),\n    ('num_iso_packets', c_int),\n    ('iso_packet_desc', libusb_iso_packet_descriptor)\n]\nif 'FreeBSD' in platform.system() and getattr(\n        libusb, 'libusb_get_string_descriptor', None\n    ) is None:\n    # Old FreeBSD version has a slight ABI incompatibility.\n    # Work around it unless libusb_get_string_descriptor is available, as it\n    # is only available on fixed versions.\n    assert _libusb_transfer_fields[2][0] == 'endpoint'\n    _libusb_transfer_fields[2] = ('endpoint', c_uint32)\n    assert _libusb_transfer_fields[11][0] == 'num_iso_packets'\n    _libusb_transfer_fields.insert(11, ('os_priv', c_void_p))\n\n# pylint: disable=protected-access\nlibusb_transfer._fields_ = _libusb_transfer_fields\n# pylint: enable=protected-access\n\nlibusb_capability = Enum({\n    # The libusb_has_capability() API is available.\n    'LIBUSB_CAP_HAS_CAPABILITY': 0x0000,\n    # Hotplug support is available.\n    'LIBUSB_CAP_HAS_HOTPLUG': 0x0001,\n    # The library can access HID devices without requiring user intervention.\n    'LIBUSB_CAP_HAS_HID_ACCESS': 0x0100,\n    # The library supports detaching of the default USB driver.\n    'LIBUSB_CAP_SUPPORTS_DETACH_KERNEL_DRIVER': 0x0101,\n})\n\nlibusb_log_level = Enum({\n    'LIBUSB_LOG_LEVEL_NONE': 0,\n    'LIBUSB_LOG_LEVEL_ERROR': 1,\n    'LIBUSB_LOG_LEVEL_WARNING': 2,\n    'LIBUSB_LOG_LEVEL_INFO': 3,\n    'LIBUSB_LOG_LEVEL_DEBUG': 4,\n})\n\n#int libusb_init(libusb_context **ctx);\nlibusb_init = libusb.libusb_init\nlibusb_init.argtypes = [libusb_context_p_p]\n#void libusb_exit(libusb_context *ctx);\nlibusb_exit = libusb.libusb_exit\nlibusb_exit.argtypes = [libusb_context_p]\nlibusb_exit.restype = None\n#void libusb_set_debug(libusb_context *ctx, int level);\nlibusb_set_debug = libusb.libusb_set_debug\nlibusb_set_debug.argtypes = [libusb_context_p, c_int]\nlibusb_set_debug.restype = None\n#const struct libusb_version * libusb_get_version(void);\ntry:\n    libusb_get_version = libusb.libusb_get_version\nexcept AttributeError:\n    _dummy_version = libusb_version(0, 0, 0, 0, _empty_char_p, _empty_char_p)\n    _dummy_version_p = pointer(_dummy_version)\n    def libusb_get_version():\n        return _dummy_version_p\nelse:\n    libusb_get_version.argtypes = []\n    libusb_get_version.restype = POINTER(libusb_version)\n#int libusb_has_capability(uint32_t capability);\ntry:\n    libusb_has_capability = libusb.libusb_has_capability\nexcept AttributeError:\n    def libusb_has_capability(_):\n        return 0\nelse:\n    libusb_has_capability.argtypes = [c_uint32]\n    libusb_has_capability.restype = c_int\ntry:\n    # Note: Should be equivalent to libusb_error.get (except libusb_error.get\n    # one raises on unknown values).\n    #char *libusb_error_name(int errcode);\n    libusb_error_name = libusb.libusb_error_name\nexcept AttributeError:\n    # pylint: disable=unused-argument\n    def libusb_error_name(errcode):\n        return None\n    # pylint: enable=unused-argument\nelse:\n    libusb_error_name.argtypes = [c_int]\n    libusb_error_name.restype = c_char_p\n\n# Note on libusb_strerror, libusb_setlocale and future functions in the\n# same spirit:\n# I do not think end-user-facing messages belong to a technical library.\n# Such features bring a new, non essential set of problems, and is a luxury\n# I do not want to spend time supporting considering limited resources and\n# more important stuff to work on.\n# For backward compatibility, expose libusb_strerror placeholder.\n# pylint: disable=unused-argument\ndef libusb_strerror(errcode):\n    return None\n# pylint: enable=unused-argument\n\n#ssize_t libusb_get_device_list(libusb_context *ctx,\n#        libusb_device ***list);\nlibusb_get_device_list = libusb.libusb_get_device_list\nlibusb_get_device_list.argtypes = [libusb_context_p, libusb_device_p_p_p]\nlibusb_get_device_list.restype = c_ssize_t\n#void libusb_free_device_list(libusb_device **list, int unref_devices);\nlibusb_free_device_list = libusb.libusb_free_device_list\nlibusb_free_device_list.argtypes = [libusb_device_p_p, c_int]\nlibusb_free_device_list.restype = None\n#libusb_device *libusb_ref_device(libusb_device *dev);\nlibusb_ref_device = libusb.libusb_ref_device\nlibusb_ref_device.argtypes = [libusb_device_p]\nlibusb_ref_device.restype = libusb_device_p\n#void libusb_unref_device(libusb_device *dev);\nlibusb_unref_device = libusb.libusb_unref_device\nlibusb_unref_device.argtypes = [libusb_device_p]\nlibusb_unref_device.restype = None\n\n#int libusb_get_configuration(libusb_device_handle *dev, int *config);\nlibusb_get_configuration = libusb.libusb_get_configuration\nlibusb_get_configuration.argtypes = [libusb_device_handle_p, c_int_p]\n#int libusb_get_device_descriptor(libusb_device *dev,\n#        struct libusb_device_descriptor *desc);\nlibusb_get_device_descriptor = libusb.libusb_get_device_descriptor\nlibusb_get_device_descriptor.argtypes = [\n    libusb_device_p, libusb_device_descriptor_p]\n#int libusb_get_active_config_descriptor(libusb_device *dev,\n#        struct libusb_config_descriptor **config);\nlibusb_get_active_config_descriptor = libusb.libusb_get_active_config_descriptor\nlibusb_get_active_config_descriptor.argtypes = [\n    libusb_device_p, libusb_config_descriptor_p_p]\n#int libusb_get_config_descriptor(libusb_device *dev, uint8_t config_index,\n#        struct libusb_config_descriptor **config);\nlibusb_get_config_descriptor = libusb.libusb_get_config_descriptor\nlibusb_get_config_descriptor.argtypes = [\n    libusb_device_p, c_uint8, libusb_config_descriptor_p_p]\n#int libusb_get_config_descriptor_by_value(libusb_device *dev,\n#        uint8_t bConfigurationValue, struct libusb_config_descriptor **config);\nlibusb_get_config_descriptor_by_value = \\\n    libusb.libusb_get_config_descriptor_by_value\nlibusb_get_config_descriptor_by_value.argtypes = [\n    libusb_device_p, c_uint8, libusb_config_descriptor_p_p]\n#void libusb_free_config_descriptor(struct libusb_config_descriptor *config);\nlibusb_free_config_descriptor = libusb.libusb_free_config_descriptor\nlibusb_free_config_descriptor.argtypes = [libusb_config_descriptor_p]\nlibusb_free_config_descriptor.restype = None\n#uint8_t libusb_get_bus_number(libusb_device *dev);\nlibusb_get_bus_number = libusb.libusb_get_bus_number\nlibusb_get_bus_number.argtypes = [libusb_device_p]\nlibusb_get_bus_number.restype = c_uint8\ntry:\n    #uint8_t libusb_get_port_number(libusb_device *dev);\n    libusb_get_port_number = libusb.libusb_get_port_number\nexcept AttributeError:\n    pass\nelse:\n    libusb_get_port_number.argtypes = [libusb_device_p]\n    libusb_get_port_number.restype = c_uint8\ntry:\n    #int libusb_get_port_numbers(libusb_device *dev,\n    #       uint8_t* port_numbers, int port_numbers_len);\n    libusb_get_port_numbers = libusb.libusb_get_port_numbers\nexcept AttributeError:\n    pass\nelse:\n    libusb_get_port_numbers.argtypes = [\n        libusb_device_p, POINTER(c_uint8), c_int]\n    libusb_get_port_numbers.restype = c_int\n# Missing: libusb_get_port_path (deprecated since 1.0.16)\ntry:\n    #libusb_device * LIBUSB_CALL libusb_get_parent(libusb_device *dev);\n    libusb_get_parent = libusb.libusb_get_parent\nexcept AttributeError:\n    pass\nelse:\n    libusb_get_parent.argtypes = [libusb_device_p]\n    libusb_get_parent.restype = libusb_device_p\n#uint8_t libusb_get_device_address(libusb_device *dev);\nlibusb_get_device_address = libusb.libusb_get_device_address\nlibusb_get_device_address.argtypes = [libusb_device_p]\nlibusb_get_device_address.restype = c_uint8\ntry:\n    #int libusb_get_device_speed(libusb_device *dev);\n    libusb_get_device_speed = libusb.libusb_get_device_speed\nexcept AttributeError:\n    # Place holder\n    def libusb_get_device_speed(_):\n        # pylint: disable=undefined-variable\n        return LIBUSB_SPEED_UNKNOWN\n        # pylint: enable=undefined-variable\nelse:\n    libusb_get_device_speed.argtypes = [libusb_device_p]\n#int libusb_get_max_packet_size(libusb_device *dev, unsigned char endpoint);\nlibusb_get_max_packet_size = libusb.libusb_get_max_packet_size\nlibusb_get_max_packet_size.argtypes = [libusb_device_p, c_uchar]\n#int libusb_get_max_iso_packet_size(libusb_device *dev, unsigned char endpoint);\ntry:\n    libusb_get_max_iso_packet_size = libusb.libusb_get_max_iso_packet_size\nexcept AttributeError:\n    # FreeBSD's reimplementation of the API [used to ]lack[s] this function.\n    # It has been added in r234193, but is lacking in default 9.x install as\n    # of this change. Provide a fallback to error-out only if actually used.\n    # pylint: disable=unused-argument\n    def libusb_get_max_iso_packet_size(_, __):\n        raise NotImplementedError\n    # pylint: enable=unused-argument\nelse:\n    libusb_get_max_iso_packet_size.argtypes = [libusb_device_p, c_uchar]\n\n#int libusb_open(libusb_device *dev, libusb_device_handle **handle);\nlibusb_open = libusb.libusb_open\nlibusb_open.argtypes = [libusb_device_p, libusb_device_handle_p_p]\n#void libusb_close(libusb_device_handle *dev_handle);\nlibusb_close = libusb.libusb_close\nlibusb_close.argtypes = [libusb_device_handle_p]\nlibusb_close.restype = None\n#libusb_device *libusb_get_device(libusb_device_handle *dev_handle);\nlibusb_get_device = libusb.libusb_get_device\nlibusb_get_device.argtypes = [libusb_device_handle_p]\nlibusb_get_device.restype = libusb_device_p\n\n#int libusb_set_configuration(libusb_device_handle *dev, int configuration);\nlibusb_set_configuration = libusb.libusb_set_configuration\nlibusb_set_configuration.argtypes = [libusb_device_handle_p, c_int]\n#int libusb_claim_interface(libusb_device_handle *dev, int iface);\nlibusb_claim_interface = libusb.libusb_claim_interface\nlibusb_claim_interface.argtypes = [libusb_device_handle_p, c_int]\n#int libusb_release_interface(libusb_device_handle *dev, int iface);\nlibusb_release_interface = libusb.libusb_release_interface\nlibusb_release_interface.argtypes = [libusb_device_handle_p, c_int]\n\n#libusb_device_handle *libusb_open_device_with_vid_pid(libusb_context *ctx,\n#        uint16_t vendor_id, uint16_t product_id);\nlibusb_open_device_with_vid_pid = libusb.libusb_open_device_with_vid_pid\nlibusb_open_device_with_vid_pid.argtypes = [\n    libusb_context_p, c_uint16, c_uint16]\nlibusb_open_device_with_vid_pid.restype = libusb_device_handle_p\n\n#int libusb_set_interface_alt_setting(libusb_device_handle *dev,\n#        int interface_number, int alternate_setting);\nlibusb_set_interface_alt_setting = libusb.libusb_set_interface_alt_setting\nlibusb_set_interface_alt_setting.argtypes = [\n    libusb_device_handle_p, c_int, c_int]\n#int libusb_clear_halt(libusb_device_handle *dev, unsigned char endpoint);\nlibusb_clear_halt = libusb.libusb_clear_halt\nlibusb_clear_halt.argtypes = [libusb_device_handle_p, c_uchar]\n#int libusb_reset_device(libusb_device_handle *dev);\nlibusb_reset_device = libusb.libusb_reset_device\nlibusb_reset_device.argtypes = [libusb_device_handle_p]\n\n#int libusb_kernel_driver_active(libusb_device_handle *dev, int interface);\nlibusb_kernel_driver_active = libusb.libusb_kernel_driver_active\nlibusb_kernel_driver_active.argtypes = [libusb_device_handle_p, c_int]\n#int libusb_detach_kernel_driver(libusb_device_handle *dev, int interface);\nlibusb_detach_kernel_driver = libusb.libusb_detach_kernel_driver\nlibusb_detach_kernel_driver.argtypes = [libusb_device_handle_p, c_int]\n#int libusb_attach_kernel_driver(libusb_device_handle *dev, int interface);\nlibusb_attach_kernel_driver = libusb.libusb_attach_kernel_driver\nlibusb_attach_kernel_driver.argtypes = [libusb_device_handle_p, c_int]\ntry:\n    #int libusb_set_auto_detach_kernel_driver(\n    #       libusb_device_handle *dev, int enable);\n    libusb_set_auto_detach_kernel_driver = \\\n        libusb.libusb_set_auto_detach_kernel_driver\nexcept AttributeError:\n    pass\nelse:\n    libusb_set_auto_detach_kernel_driver.argtypes = [\n        libusb_device_handle_p, c_int]\n    libusb_set_auto_detach_kernel_driver.restype = c_int\n\n# Get the data section of a control transfer. This convenience function is here\n# to remind you that the data does not start until 8 bytes into the actual\n# buffer, as the setup packet comes first.\n#\n# Calling this function only makes sense from a transfer callback function,\n# or situations where you have already allocated a suitably sized buffer at\n# transfer->buffer.\n#\n# \\param transfer a transfer\n# \\returns pointer to the first byte of the data section\n\ndef libusb_control_transfer_get_data(transfer_p):\n    transfer = transfer_p.contents\n    return string_at(transfer.buffer, transfer.length)[\n        LIBUSB_CONTROL_SETUP_SIZE:]\n\ndef libusb_control_transfer_get_setup(transfer_p):\n    return cast(transfer_p.contents.buffer, libusb_control_setup_p)\n\ndef libusb_fill_control_setup(\n        setup_p, bmRequestType, bRequest, wValue, wIndex, wLength):\n    setup = cast(setup_p, libusb_control_setup_p).contents\n    setup.bmRequestType = bmRequestType\n    setup.bRequest = bRequest\n    setup.wValue = libusb_cpu_to_le16(wValue)\n    setup.wIndex = libusb_cpu_to_le16(wIndex)\n    setup.wLength = libusb_cpu_to_le16(wLength)\n\n#struct libusb_transfer *libusb_alloc_transfer(int iso_packets);\nlibusb_alloc_transfer = libusb.libusb_alloc_transfer\nlibusb_alloc_transfer.argtypes = [c_int]\nlibusb_alloc_transfer.restype = libusb_transfer_p\n#int libusb_submit_transfer(struct libusb_transfer *transfer);\nlibusb_submit_transfer = libusb.libusb_submit_transfer\nlibusb_submit_transfer.argtypes = [libusb_transfer_p]\n#int libusb_cancel_transfer(struct libusb_transfer *transfer);\nlibusb_cancel_transfer = libusb.libusb_cancel_transfer\nlibusb_cancel_transfer.argtypes = [libusb_transfer_p]\n#void libusb_free_transfer(struct libusb_transfer *transfer);\nlibusb_free_transfer = libusb.libusb_free_transfer\nlibusb_free_transfer.argtypes = [libusb_transfer_p]\nlibusb_free_transfer.restype = None\n\n# pylint: disable=redefined-builtin\ndef libusb_fill_control_transfer(\n        transfer_p, dev_handle, buffer, callback, user_data, timeout):\n    transfer = transfer_p.contents\n    transfer.dev_handle = dev_handle\n    transfer.endpoint = 0\n    # pylint: disable=undefined-variable\n    transfer.type = LIBUSB_TRANSFER_TYPE_CONTROL\n    # pylint: enable=undefined-variable\n    transfer.timeout = timeout\n    transfer.buffer = cast(buffer, c_void_p)\n    if buffer is not None:\n        setup = cast(buffer, libusb_control_setup_p).contents\n        # pylint: disable=undefined-variable\n        transfer.length = LIBUSB_CONTROL_SETUP_SIZE + \\\n            libusb_le16_to_cpu(setup.wLength)\n        # pylint: enable=undefined-variable\n    transfer.user_data = user_data\n    transfer.callback = callback\n# pylint: enable=redefined-builtin\n\n# pylint: disable=redefined-builtin\ndef libusb_fill_bulk_transfer(\n        transfer_p, dev_handle, endpoint, buffer, length,\n        callback, user_data, timeout):\n    transfer = transfer_p.contents\n    transfer.dev_handle = dev_handle\n    transfer.endpoint = endpoint\n    # pylint: disable=undefined-variable\n    transfer.type = LIBUSB_TRANSFER_TYPE_BULK\n    # pylint: enable=undefined-variable\n    transfer.timeout = timeout\n    transfer.buffer = cast(buffer, c_void_p)\n    transfer.length = length\n    transfer.user_data = user_data\n    transfer.callback = callback\n# pylint: enable=redefined-builtin\n\n# pylint: disable=redefined-builtin\ndef libusb_fill_interrupt_transfer(\n        transfer_p, dev_handle, endpoint, buffer,\n        length, callback, user_data, timeout):\n    transfer = transfer_p.contents\n    transfer.dev_handle = dev_handle\n    transfer.endpoint = endpoint\n    # pylint: disable=undefined-variable\n    transfer.type = LIBUSB_TRANSFER_TYPE_INTERRUPT\n    # pylint: enable=undefined-variable\n    transfer.timeout = timeout\n    transfer.buffer = cast(buffer, c_void_p)\n    transfer.length = length\n    transfer.user_data = user_data\n    transfer.callback = callback\n# pylint: enable=redefined-builtin\n\n# pylint: disable=redefined-builtin\ndef libusb_fill_iso_transfer(\n        transfer_p, dev_handle, endpoint, buffer, length,\n        num_iso_packets, callback, user_data, timeout):\n    transfer = transfer_p.contents\n    transfer.dev_handle = dev_handle\n    transfer.endpoint = endpoint\n    # pylint: disable=undefined-variable\n    transfer.type = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS\n    # pylint: enable=undefined-variable\n    transfer.timeout = timeout\n    transfer.buffer = cast(buffer, c_void_p)\n    transfer.length = length\n    transfer.num_iso_packets = num_iso_packets\n    transfer.user_data = user_data\n    transfer.callback = callback\n# pylint: enable=redefined-builtin\n\ndef _get_iso_packet_list(transfer):\n    list_type = libusb_iso_packet_descriptor * transfer.num_iso_packets\n    return list_type.from_address(addressof(transfer.iso_packet_desc))\n\ndef get_iso_packet_list(transfer_p):\n    \"\"\"\n    Python-specific helper extracting a list of iso packet descriptors,\n    because it's not as straight-forward as in C.\n    \"\"\"\n    return _get_iso_packet_list(transfer_p.contents)\n\ndef _get_iso_packet_buffer(transfer, offset, length):\n    return string_at(transfer.buffer + offset, length)\n\ndef get_iso_packet_buffer_list(transfer_p):\n    \"\"\"\n    Python-specific helper extracting a list of iso packet buffers.\n    \"\"\"\n    transfer = transfer_p.contents\n    offset = 0\n    result = []\n    append = result.append\n    for iso_transfer in _get_iso_packet_list(transfer):\n        length = iso_transfer.length\n        append(_get_iso_packet_buffer(transfer, offset, length))\n        offset += length\n    return result\n\ndef get_extra(descriptor):\n    \"\"\"\n    Python-specific helper to access \"extra\" field of descriptors,\n    because it's not as straight-forward as in C.\n    Returns a list, where each entry is an individual extra descriptor.\n    \"\"\"\n    result = []\n    extra_length = descriptor.extra_length\n    if extra_length:\n        extra = string_at(descriptor.extra, extra_length)\n        append = result.append\n        while extra:\n            length = _string_item_to_int(extra[0])\n            if not 0 < length <= len(extra):\n                raise ValueError(\n                    'Extra descriptor %i is incomplete/invalid' % (\n                        len(result),\n                    ),\n                )\n            append(extra[:length])\n            extra = extra[length:]\n    return result\n\ndef libusb_set_iso_packet_lengths(transfer_p, length):\n    transfer = transfer_p.contents\n    for iso_packet_desc in _get_iso_packet_list(transfer):\n        iso_packet_desc.length = length\n\ndef libusb_get_iso_packet_buffer(transfer_p, packet):\n    transfer = transfer_p.contents\n    offset = 0\n    if packet >= transfer.num_iso_packets:\n        return None\n    iso_packet_desc_list = _get_iso_packet_list(transfer)\n    for i in range(packet):\n        offset += iso_packet_desc_list[i].length\n    return _get_iso_packet_buffer(\n        transfer, offset, iso_packet_desc_list[packet].length)\n\ndef libusb_get_iso_packet_buffer_simple(transfer_p, packet):\n    transfer = transfer_p.contents\n    if packet >= transfer.num_iso_packets:\n        return None\n    iso_length = transfer.iso_packet_desc.length\n    return _get_iso_packet_buffer(transfer, iso_length * packet, iso_length)\n\n# sync I/O\n\n#int libusb_control_transfer(libusb_device_handle *dev_handle,\n#        uint8_t request_type, uint8_t request, uint16_t value, uint16_t index,\n#        unsigned char *data, uint16_t length, unsigned int timeout);\nlibusb_control_transfer = libusb.libusb_control_transfer\nlibusb_control_transfer.argtypes = [libusb_device_handle_p, c_uint8, c_uint8,\n                                    c_uint16, c_uint16, c_void_p, c_uint16,\n                                    c_uint]\n\n#int libusb_bulk_transfer(libusb_device_handle *dev_handle,\n#        unsigned char endpoint, unsigned char *data, int length,\n#        int *actual_length, unsigned int timeout);\nlibusb_bulk_transfer = libusb.libusb_bulk_transfer\nlibusb_bulk_transfer.argtypes = [libusb_device_handle_p, c_uchar, c_void_p,\n                                 c_int, c_int_p, c_uint]\n\n#int libusb_interrupt_transfer(libusb_device_handle *dev_handle,\n#        unsigned char endpoint, unsigned char *data, int length,\n#        int *actual_length, unsigned int timeout);\nlibusb_interrupt_transfer = libusb.libusb_interrupt_transfer\nlibusb_interrupt_transfer.argtypes = [libusb_device_handle_p, c_uchar,\n                                      c_void_p, c_int, c_int_p, c_uint]\n\n# pylint: disable=undefined-variable\ndef libusb_get_descriptor(dev, desc_type, desc_index, data, length):\n    return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN,\n                                   LIBUSB_REQUEST_GET_DESCRIPTOR,\n                                   (desc_type << 8) | desc_index, 0, data,\n                                   length, 1000)\n# pylint: enable=undefined-variable\n\n# pylint: disable=undefined-variable\ndef libusb_get_string_descriptor(dev, desc_index, langid, data, length):\n    return libusb_control_transfer(dev, LIBUSB_ENDPOINT_IN,\n                                   LIBUSB_REQUEST_GET_DESCRIPTOR,\n                                   (LIBUSB_DT_STRING << 8) | desc_index,\n                                   langid, data, length, 1000)\n# pylint: enable=undefined-variable\n\n#int libusb_get_string_descriptor_ascii(libusb_device_handle *dev,\n#        uint8_t index, unsigned char *data, int length);\nlibusb_get_string_descriptor_ascii = libusb.libusb_get_string_descriptor_ascii\nlibusb_get_string_descriptor_ascii.argtypes = [libusb_device_handle_p,\n                                               c_uint8, c_void_p, c_int]\n\n# polling and timeouts\n\n#int libusb_try_lock_events(libusb_context *ctx);\nlibusb_try_lock_events = libusb.libusb_try_lock_events\nlibusb_try_lock_events.argtypes = [libusb_context_p]\n#void libusb_lock_events(libusb_context *ctx);\nlibusb_lock_events = libusb.libusb_lock_events\nlibusb_lock_events.argtypes = [libusb_context_p]\n#void libusb_unlock_events(libusb_context *ctx);\nlibusb_unlock_events = libusb.libusb_unlock_events\nlibusb_unlock_events.argtypes = [libusb_context_p]\nlibusb_unlock_events.restype = None\n#int libusb_event_handling_ok(libusb_context *ctx);\nlibusb_event_handling_ok = libusb.libusb_event_handling_ok\nlibusb_event_handling_ok.argtypes = [libusb_context_p]\n#int libusb_event_handler_active(libusb_context *ctx);\nlibusb_event_handler_active = libusb.libusb_event_handler_active\nlibusb_event_handler_active.argtypes = [libusb_context_p]\n#void libusb_lock_event_waiters(libusb_context *ctx);\nlibusb_lock_event_waiters = libusb.libusb_lock_event_waiters\nlibusb_lock_event_waiters.argtypes = [libusb_context_p]\nlibusb_lock_event_waiters.restype = None\n#void libusb_unlock_event_waiters(libusb_context *ctx);\nlibusb_unlock_event_waiters = libusb.libusb_unlock_event_waiters\nlibusb_unlock_event_waiters.argtypes = []\nlibusb_unlock_event_waiters.restype = None\n#int libusb_wait_for_event(libusb_context *ctx, struct timeval *tv);\nlibusb_wait_for_event = libusb.libusb_wait_for_event\nlibusb_wait_for_event.argtypes = [libusb_context_p, timeval_p]\n\n#int libusb_handle_events_timeout(libusb_context *ctx, struct timeval *tv);\nlibusb_handle_events_timeout = libusb.libusb_handle_events_timeout\nlibusb_handle_events_timeout.argtypes = [libusb_context_p, timeval_p]\n#int libusb_handle_events_timeout_completed(libusb_context *ctx,\n#   struct timeval *tv, int *completed);\ntry:\n    libusb_handle_events_timeout_completed = libusb.\\\n        libusb_handle_events_timeout_completed\nexcept AttributeError:\n    # No safe replacement possible.\n    pass\nelse:\n    libusb_handle_events_timeout_completed.argtypes = [\n        libusb_context_p, timeval_p, c_int_p]\n#int libusb_handle_events(libusb_context *ctx);\nlibusb_handle_events = libusb.libusb_handle_events\nlibusb_handle_events.argtypes = [libusb_context_p]\n#int libusb_handle_events_completed(libusb_context *ctx, int *completed);\ntry:\n    libusb_handle_events_completed = libusb.libusb_handle_events_completed\nexcept AttributeError:\n    # No safe replacement possible.\n    pass\nelse:\n    libusb_handle_events_completed.argtypes = [libusb_context_p, c_int_p]\n#int libusb_handle_events_locked(libusb_context *ctx, struct timeval *tv);\nlibusb_handle_events_locked = libusb.libusb_handle_events_locked\nlibusb_handle_events_locked.argtypes = [libusb_context_p, timeval_p]\n#int libusb_get_next_timeout(libusb_context *ctx, struct timeval *tv);\nlibusb_get_next_timeout = libusb.libusb_get_next_timeout\nlibusb_get_next_timeout.argtypes = [libusb_context_p, timeval_p]\n\nclass libusb_pollfd(Structure):\n    _fields_ = [\n        ('fd', c_int),\n        ('events', c_short),\n    ]\nlibusb_pollfd_p = POINTER(libusb_pollfd)\nlibusb_pollfd_p_p = POINTER(libusb_pollfd_p)\n\nlibusb_pollfd_added_cb_p = CFUNCTYPE(None, c_int, c_short, py_object)\nlibusb_pollfd_removed_cb_p = CFUNCTYPE(None, c_int, py_object)\n\n#const struct libusb_pollfd **libusb_get_pollfds(libusb_context *ctx);\nlibusb_get_pollfds = libusb.libusb_get_pollfds\nlibusb_get_pollfds.argtypes = [libusb_context_p]\nlibusb_get_pollfds.restype = libusb_pollfd_p_p\n#void libusb_set_pollfd_notifiers(libusb_context *ctx,\n#        libusb_pollfd_added_cb added_cb, libusb_pollfd_removed_cb removed_cb,\n#        void *user_data);\nlibusb_set_pollfd_notifiers = libusb.libusb_set_pollfd_notifiers\nlibusb_set_pollfd_notifiers.argtypes = [libusb_context_p,\n                                        libusb_pollfd_added_cb_p,\n                                        libusb_pollfd_removed_cb_p, py_object]\nlibusb_set_pollfd_notifiers.restype = None\n\n#typedef int libusb_hotplug_callback_handle;\nlibusb_hotplug_callback_handle = c_int\n\nlibusb_hotplug_flag = Enum({\n    'LIBUSB_HOTPLUG_ENUMERATE': 1,\n})\n\nlibusb_hotplug_event = Enum({\n    'LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED': 0x01,\n    'LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT': 0x02,\n})\n\nLIBUSB_HOTPLUG_MATCH_ANY = -1\n\n#typedef int (*libusb_hotplug_callback_fn)(libusb_context *ctx,\n#        libusb_device *device, libusb_hotplug_event event, void *user_data);\nlibusb_hotplug_callback_fn_p = CFUNCTYPE(\n    c_int, libusb_context_p, libusb_device_p, c_int, c_void_p)\n\n#int libusb_hotplug_register_callback(libusb_context *ctx,\n#        libusb_hotplug_event events, libusb_hotplug_flag flags,\n#        int vendor_id, int product_id, int dev_class,\n#        libusb_hotplug_callback_fn cb_fn, void *user_data,\n#        libusb_hotplug_callback_handle *handle);\ntry:\n    libusb_hotplug_register_callback = libusb.libusb_hotplug_register_callback\nexcept AttributeError:\n    pass\nelse:\n    libusb_hotplug_register_callback.argtypes = [\n        libusb_context_p,\n        c_int, c_int,\n        c_int, c_int, c_int,\n        libusb_hotplug_callback_fn_p, c_void_p,\n        POINTER(libusb_hotplug_callback_handle),\n    ]\n    libusb_hotplug_register_callback.restype = c_int\n\n#void libusb_hotplug_deregister_callback(libusb_context *ctx,\n#        libusb_hotplug_callback_handle handle);\ntry:\n    libusb_hotplug_deregister_callback = \\\n        libusb.libusb_hotplug_deregister_callback\nexcept AttributeError:\n    pass\nelse:\n    libusb_hotplug_deregister_callback.argtypes = [\n        libusb_context_p,\n        libusb_hotplug_callback_handle,\n    ]\n    libusb_hotplug_deregister_callback.restype = None\n\n# /libusb.h\n"
  },
  {
    "path": "scc/lib/usb1.py",
    "content": "# Copyright (C) 2010-2016  Vincent Pelletier <plr.vincent@gmail.com>\n#\n# This library is free software; you can redistribute it and/or\n# modify it under the terms of the GNU Lesser General Public\n# License as published by the Free Software Foundation; either\n# version 2.1 of the License, or (at your option) any later version.\n#\n# This library is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n# Lesser General Public License for more details.\n#\n# You should have received a copy of the GNU Lesser General Public\n# License along with this library; if not, write to the Free Software\n# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n\n# pylint: disable=invalid-name, too-many-locals, too-many-arguments\n# pylint: disable=too-many-public-methods, too-many-instance-attributes\n# pylint: disable=missing-docstring\n\"\"\"\nPythonic wrapper for libusb-1.0.\n\nThe first thing you must do is to get an \"USB context\". To do so, create an\nUSBContext instance.\nThen, you can use it to browse available USB devices and open the one you want\nto talk to.\nAt this point, you should have a USBDeviceHandle instance (as returned by\nUSBContext or USBDevice instances), and you can start exchanging with the\ndevice.\n\nFeatures:\n- Basic device settings (configuration & interface selection, ...)\n- String descriptor lookups (ASCII & unicode), and list supported language\n  codes\n- Synchronous I/O (control, bulk, interrupt)\n- Asynchronous I/O (control, bulk, interrupt, isochronous)\n  Note: Isochronous support is not well tested.\n  See USBPoller, USBTransfer and USBTransferHelper.\n\nAll LIBUSB_* constants are available in this module, without the LIBUSB_\nprefix - with one exception: LIBUSB_5GBPS_OPERATION is available as\nSUPER_SPEED_OPERATION, so it is a valid python identifier.\n\nAll LIBUSB_ERROR_* constants are available in this module as exception classes,\nsubclassing USBError.\n\"\"\"\n\nfrom ctypes import byref, create_string_buffer, c_int, sizeof, POINTER, \\\n    cast, c_uint8, c_uint16, c_ubyte, string_at, c_void_p, cdll, addressof, \\\n    c_char, py_object\nfrom ctypes.util import find_library\nimport sys\nimport threading\nimport warnings\nimport weakref\nimport collections\nimport functools\nimport contextlib\nimport inspect\nfrom scc.lib import libusb1\nif sys.version_info[:2] >= (2, 6):\n# pylint: disable=wrong-import-order,ungrouped-imports\n    if sys.platform == 'win32':\n        from ctypes import get_last_error as get_errno\n    else:\n        from ctypes import get_errno\n# pylint: enable=wrong-import-order,ungrouped-imports\nelse:\n    def get_errno():\n        raise NotImplementedError(\n            'Your python version does not support errno/last_error'\n        )\n\n__all__ = [\n    'USBContext', 'USBDeviceHandle', 'USBDevice', 'hasCapability',\n    'USBPoller', 'USBTransfer', 'USBTransferHelper', 'EVENT_CALLBACK_SET',\n    'USBPollerThread', 'USBEndpoint', 'USBInterfaceSetting', 'USBInterface',\n    'USBConfiguration', 'DoomedTransferError', 'getVersion', 'USBError',\n]\n# Bind libusb1 constants and libusb1.USBError to this module, so user does not\n# have to import two modules.\nUSBError = libusb1.USBError\nSTATUS_TO_EXCEPTION_DICT = {}\ndef __bindConstants():\n    global_dict = globals()\n    PREFIX = 'LIBUSB_'\n    for name, value in libusb1.__dict__.items():\n        if name.startswith(PREFIX):\n            name = name[len(PREFIX):]\n            # Gah.\n            if name == '5GBPS_OPERATION':\n                name = 'SUPER_SPEED_OPERATION'\n            assert name not in global_dict\n            global_dict[name] = value\n            __all__.append(name)\n    # Finer-grained exceptions.\n    for name, value in libusb1.libusb_error.forward_dict.items():\n        if value:\n            assert name.startswith(PREFIX + 'ERROR_'), name\n            if name == 'LIBUSB_ERROR_IO':\n                name = 'ErrorIO'\n            else:\n                name = ''.join(x.capitalize() for x in name.split('_')[1:])\n            name = 'USB' + name\n            assert name not in global_dict, name\n            assert value not in STATUS_TO_EXCEPTION_DICT\n            STATUS_TO_EXCEPTION_DICT[value] = global_dict[name] = type(\n                name,\n                (USBError, ),\n                {'value': value},\n            )\n            __all__.append(name)\n__bindConstants()\ndel __bindConstants\n\ndef raiseUSBError(value):\n    raise STATUS_TO_EXCEPTION_DICT.get(value, USBError)(value)\n\ndef mayRaiseUSBError(value):\n    if value < 0:\n        raiseUSBError(value)\n\ntry:\n    namedtuple = collections.namedtuple\nexcept AttributeError:\n    Version = tuple\nelse:\n    Version = namedtuple(\n        'Version',\n        ['major', 'minor', 'micro', 'nano', 'rc', 'describe'],\n    )\n\nif sys.version_info[0] == 3:\n    BYTE = bytes([0])\n    # pylint: disable=redefined-builtin\n    long = int\n    # pylint: enable=redefined-builtin\nelse:\n    BYTE = '\\x00'\n# pylint: disable=undefined-variable\nCONTROL_SETUP = BYTE * CONTROL_SETUP_SIZE\n# pylint: enable=undefined-variable\n\n__libc_name = find_library('c')\nif __libc_name is None:\n    # Of course, will leak memory.\n    # Should we warn user ? How ?\n    _free = lambda x: None\nelse:\n    _free = getattr(cdll, __libc_name).free\ndel __libc_name\n\ntry:\n    WeakSet = weakref.WeakSet\nexcept AttributeError:\n    # Python < 2.7: tiny wrapper around WeakKeyDictionary\n    class WeakSet(object):\n        def __init__(self):\n            self.__dict = weakref.WeakKeyDictionary()\n\n        def add(self, item):\n            self.__dict[item] = None\n\n        def pop(self):\n            return self.__dict.popitem()[0]\n\n# Default string length\n# From a comment in libusb-1.0: \"Some devices choke on size > 255\"\nSTRING_LENGTH = 255\n\n# As of v3 of USB specs, there cannot be more than 7 hubs from controller to\n# device.\nPATH_MAX_DEPTH = 7\n\nEVENT_CALLBACK_SET = frozenset((\n    # pylint: disable=undefined-variable\n    TRANSFER_COMPLETED,\n    TRANSFER_ERROR,\n    TRANSFER_TIMED_OUT,\n    TRANSFER_CANCELLED,\n    TRANSFER_STALL,\n    TRANSFER_NO_DEVICE,\n    TRANSFER_OVERFLOW,\n    # pylint: enable=undefined-variable\n))\n\nDEFAULT_ASYNC_TRANSFER_ERROR_CALLBACK = lambda x: False\n\ndef create_binary_buffer(init_or_size):\n    \"\"\"\n    ctypes.create_string_buffer variant which does not add a trailing null\n    when init_or_size is not a size.\n    \"\"\"\n    # As per ctypes.create_string_buffer, as of python 2.7.10 at least:\n    # - int or long is a length\n    # - str or unicode is an initialiser\n    # Testing the latter confuses 2to3, so test the former.\n    if isinstance(init_or_size, (int, long)):\n        result = create_string_buffer(init_or_size)\n    else:\n        result = create_string_buffer(init_or_size, len(init_or_size))\n    return result\n\nclass DoomedTransferError(Exception):\n    \"\"\"Exception raised when altering/submitting a doomed transfer.\"\"\"\n    pass\n\nclass USBTransfer(object):\n    \"\"\"\n    USB asynchronous transfer control & data.\n\n    All modification methods will raise if called on a submitted transfer.\n    Methods noted as \"should not be called on a submitted transfer\" will not\n    prevent you from reading, but returned value is unspecified.\n\n    Note on user_data: because of pypy's current ctype restrictions, user_data\n    is not provided to C level, but is managed purely in python. It should\n    change nothing for you, unless you are looking at underlying C transfer\n    structure - which you should never have to.\n    \"\"\"\n    # Prevent garbage collector from freeing the free function before our\n    # instances, as we need it to property destruct them.\n    __libusb_free_transfer = libusb1.libusb_free_transfer\n    __libusb_cancel_transfer = libusb1.libusb_cancel_transfer\n    __USBError = USBError\n    # pylint: disable=undefined-variable\n    __USBErrorNotFound = USBErrorNotFound\n    # pylint: enable=undefined-variable\n    __transfer = None\n    __initialized = False\n    __submitted = False\n    __callback = None\n    __ctypesCallbackWrapper = None\n    __doomed = False\n    __user_data = None\n    __transfer_buffer = None\n\n    def __init__(self, handle, iso_packets, before_submit, after_completion):\n        \"\"\"\n        You should not instanciate this class directly.\n        Call \"getTransfer\" method on an USBDeviceHandle instance to get\n        instances of this class.\n        \"\"\"\n        if iso_packets < 0:\n            raise ValueError(\n                'Cannot request a negative number of iso packets.'\n            )\n        self.__handle = handle\n        self.__before_submit = before_submit\n        self.__after_completion = after_completion\n        self.__num_iso_packets = iso_packets\n        result = libusb1.libusb_alloc_transfer(iso_packets)\n        if not result:\n            # pylint: disable=undefined-variable\n            raise USBErrorNoMem\n            # pylint: enable=undefined-variable\n        self.__transfer = result\n        self.__ctypesCallbackWrapper = libusb1.libusb_transfer_cb_fn_p(\n            self.__callbackWrapper)\n\n    def close(self):\n        \"\"\"\n        Break reference cycles to allow instance to be garbage-collected.\n        Raises if called on a submitted transfer.\n        \"\"\"\n        if self.__submitted:\n            raise ValueError('Cannot close a submitted transfer')\n        self.doom()\n        self.__initialized = False\n        # Break possible external reference cycles\n        self.__callback = None\n        self.__user_data = None\n        # Break libusb_transfer reference cycles\n        self.__ctypesCallbackWrapper = None\n        # For some reason, overwriting callback is not enough to remove this\n        # reference cycle - though sometimes it works:\n        #   self -> self.__dict__ -> libusb_transfer -> dict[x] -> dict[x] ->\n        #   CThunkObject -> __callbackWrapper -> self\n        # So free transfer altogether.\n        if self.__transfer is not None:\n            self.__libusb_free_transfer(self.__transfer)\n            self.__transfer = None\n        self.__transfer_buffer = None\n        # Break USBDeviceHandle reference cycle\n        self.__before_submit = None\n        self.__after_completion = None\n\n    def doom(self):\n        \"\"\"\n        Prevent transfer from being submitted again.\n        \"\"\"\n        self.__doomed = True\n\n    def __del__(self):\n        if self.__transfer is not None:\n            try:\n                # If this doesn't raise, we're doomed; transfer was submitted,\n                # still python decided to garbage-collect this instance.\n                # Stick to libusb's documentation, and don't free the\n                # transfer. If interpreter is shutting down, kernel will\n                # reclaim memory anyway.\n                # Note: we can't prevent transfer's buffer from being\n                # garbage-collected as soon as there will be no remaining\n                # reference to transfer, so a segfault might happen anyway.\n                # Should we warn user ? How ?\n                self.cancel()\n            except self.__USBErrorNotFound:\n                # Transfer was not submitted, we can free it.\n                self.__libusb_free_transfer(self.__transfer)\n\n    # pylint: disable=unused-argument\n    def __callbackWrapper(self, transfer_p):\n        \"\"\"\n        Makes it possible for user-provided callback to alter transfer when\n        fired (ie, mark transfer as not submitted upon call).\n        \"\"\"\n        self.__submitted = False\n        self.__after_completion(self)\n        callback = self.__callback\n        if callback is not None:\n            callback(self)\n        if self.__doomed:\n            self.close()\n    # pylint: enable=unused-argument\n\n    def setCallback(self, callback):\n        \"\"\"\n        Change transfer's callback.\n        \"\"\"\n        self.__callback = callback\n\n    def getCallback(self):\n        \"\"\"\n        Get currently set callback.\n        \"\"\"\n        return self.__callback\n\n    def setControl(\n            self, request_type, request, value, index, buffer_or_len,\n            callback=None, user_data=None, timeout=0):\n        \"\"\"\n        Setup transfer for control use.\n\n        request_type, request, value, index\n            See USBDeviceHandle.controlWrite.\n            request_type defines transfer direction (see\n            ENDPOINT_OUT and ENDPOINT_IN)).\n        buffer_or_len\n            Either a string (when sending data), or expected data length (when\n            receiving data).\n        callback\n            Callback function to be invoked on transfer completion.\n            Called with transfer as parameter, return value ignored.\n        user_data\n            User data to pass to callback function.\n        timeout\n            Transfer timeout in milliseconds. 0 to disable.\n        \"\"\"\n        if self.__submitted:\n            raise ValueError('Cannot alter a submitted transfer')\n        if self.__doomed:\n            raise DoomedTransferError('Cannot reuse a doomed transfer')\n        if isinstance(buffer_or_len, (int, long)):\n            length = buffer_or_len\n            # pylint: disable=undefined-variable\n            string_buffer = create_binary_buffer(length + CONTROL_SETUP_SIZE)\n            # pylint: enable=undefined-variable\n        else:\n            length = len(buffer_or_len)\n            string_buffer = create_binary_buffer(CONTROL_SETUP + buffer_or_len)\n        self.__initialized = False\n        self.__transfer_buffer = string_buffer\n        self.__user_data = user_data\n        libusb1.libusb_fill_control_setup(\n            string_buffer, request_type, request, value, index, length)\n        libusb1.libusb_fill_control_transfer(\n            self.__transfer, self.__handle, string_buffer,\n            self.__ctypesCallbackWrapper, None, timeout)\n        self.__callback = callback\n        self.__initialized = True\n\n    def setBulk(\n            self, endpoint, buffer_or_len, callback=None, user_data=None,\n            timeout=0):\n        \"\"\"\n        Setup transfer for bulk use.\n\n        endpoint\n            Endpoint to submit transfer to. Defines transfer direction (see\n            ENDPOINT_OUT and ENDPOINT_IN)).\n        buffer_or_len\n            Either a string (when sending data), or expected data length (when\n            receiving data)\n        callback\n            Callback function to be invoked on transfer completion.\n            Called with transfer as parameter, return value ignored.\n        user_data\n            User data to pass to callback function.\n        timeout\n            Transfer timeout in milliseconds. 0 to disable.\n        \"\"\"\n        if self.__submitted:\n            raise ValueError('Cannot alter a submitted transfer')\n        if self.__doomed:\n            raise DoomedTransferError('Cannot reuse a doomed transfer')\n        string_buffer = create_binary_buffer(buffer_or_len)\n        self.__initialized = False\n        self.__transfer_buffer = string_buffer\n        self.__user_data = user_data\n        libusb1.libusb_fill_bulk_transfer(\n            self.__transfer, self.__handle, endpoint, string_buffer,\n            sizeof(string_buffer), self.__ctypesCallbackWrapper, None, timeout)\n        self.__callback = callback\n        self.__initialized = True\n\n    def setInterrupt(\n            self, endpoint, buffer_or_len, callback=None, user_data=None,\n            timeout=0):\n        \"\"\"\n        Setup transfer for interrupt use.\n\n        endpoint\n            Endpoint to submit transfer to. Defines transfer direction (see\n            ENDPOINT_OUT and ENDPOINT_IN)).\n        buffer_or_len\n            Either a string (when sending data), or expected data length (when\n            receiving data)\n        callback\n            Callback function to be invoked on transfer completion.\n            Called with transfer as parameter, return value ignored.\n        user_data\n            User data to pass to callback function.\n        timeout\n            Transfer timeout in milliseconds. 0 to disable.\n        \"\"\"\n        if self.__submitted:\n            raise ValueError('Cannot alter a submitted transfer')\n        if self.__doomed:\n            raise DoomedTransferError('Cannot reuse a doomed transfer')\n        string_buffer = create_binary_buffer(buffer_or_len)\n        self.__initialized = False\n        self.__transfer_buffer = string_buffer\n        self.__user_data = user_data\n        libusb1.libusb_fill_interrupt_transfer(\n            self.__transfer, self.__handle, endpoint, string_buffer,\n            sizeof(string_buffer), self.__ctypesCallbackWrapper, None, timeout)\n        self.__callback = callback\n        self.__initialized = True\n\n    def setIsochronous(\n            self, endpoint, buffer_or_len, callback=None,\n            user_data=None, timeout=0, iso_transfer_length_list=None):\n        \"\"\"\n        Setup transfer for isochronous use.\n\n        endpoint\n            Endpoint to submit transfer to. Defines transfer direction (see\n            ENDPOINT_OUT and ENDPOINT_IN)).\n        buffer_or_len\n            Either a string (when sending data), or expected data length (when\n            receiving data)\n        callback\n            Callback function to be invoked on transfer completion.\n            Called with transfer as parameter, return value ignored.\n        user_data\n            User data to pass to callback function.\n        timeout\n            Transfer timeout in milliseconds. 0 to disable.\n        iso_transfer_length_list\n            List of individual transfer sizes. If not provided, buffer_or_len\n            will be divided evenly among available transfers if possible, and\n            raise ValueError otherwise.\n        \"\"\"\n        if self.__submitted:\n            raise ValueError('Cannot alter a submitted transfer')\n        num_iso_packets = self.__num_iso_packets\n        if num_iso_packets == 0:\n            raise TypeError(\n                'This transfer canot be used for isochronous I/O. '\n                'You must get another one with a non-zero iso_packets '\n                'parameter.'\n            )\n        if self.__doomed:\n            raise DoomedTransferError('Cannot reuse a doomed transfer')\n        string_buffer = create_binary_buffer(buffer_or_len)\n        buffer_length = sizeof(string_buffer)\n        if iso_transfer_length_list is None:\n            iso_length, remainder = divmod(buffer_length, num_iso_packets)\n            if remainder:\n                raise ValueError(\n                    'Buffer size %i cannot be evenly distributed among %i '\n                    'transfers' % (\n                        buffer_length,\n                        num_iso_packets,\n                    )\n                )\n            iso_transfer_length_list = [iso_length] * num_iso_packets\n        configured_iso_packets = len(iso_transfer_length_list)\n        if configured_iso_packets > num_iso_packets:\n            raise ValueError(\n                'Too many ISO transfer lengths (%i), there are '\n                'only %i ISO transfers available' % (\n                    configured_iso_packets,\n                    num_iso_packets,\n                )\n            )\n        if sum(iso_transfer_length_list) > buffer_length:\n            raise ValueError(\n                'ISO transfers too long (%i), there are only '\n                '%i bytes available' % (\n                    sum(iso_transfer_length_list),\n                    buffer_length,\n                )\n            )\n        transfer_p = self.__transfer\n        self.__initialized = False\n        self.__transfer_buffer = string_buffer\n        self.__user_data = user_data\n        libusb1.libusb_fill_iso_transfer(\n            transfer_p, self.__handle, endpoint, string_buffer, buffer_length,\n            configured_iso_packets, self.__ctypesCallbackWrapper, None,\n            timeout)\n        for length, iso_packet_desc in zip(\n                iso_transfer_length_list,\n                libusb1.get_iso_packet_list(transfer_p)):\n            if length <= 0:\n                raise ValueError(\n                    'Negative/null length transfers are not possible.'\n                )\n            iso_packet_desc.length = length\n        self.__callback = callback\n        self.__initialized = True\n\n    def getType(self):\n        \"\"\"\n        Get transfer type.\n\n        Returns one of:\n            TRANSFER_TYPE_CONTROL\n            TRANSFER_TYPE_ISOCHRONOUS\n            TRANSFER_TYPE_BULK\n            TRANSFER_TYPE_INTERRUPT\n        \"\"\"\n        return self.__transfer.contents.type\n\n    def getEndpoint(self):\n        \"\"\"\n        Get endpoint.\n        \"\"\"\n        return self.__transfer.contents.endpoint\n\n    def getStatus(self):\n        \"\"\"\n        Get transfer status.\n        Should not be called on a submitted transfer.\n        \"\"\"\n        return self.__transfer.contents.status\n\n    def getActualLength(self):\n        \"\"\"\n        Get actually transfered data length.\n        Should not be called on a submitted transfer.\n        \"\"\"\n        return self.__transfer.contents.actual_length\n\n    def getBuffer(self):\n        \"\"\"\n        Get data buffer content.\n        Should not be called on a submitted transfer.\n        \"\"\"\n        transfer_p = self.__transfer\n        transfer = transfer_p.contents\n        # pylint: disable=undefined-variable\n        if transfer.type == TRANSFER_TYPE_CONTROL:\n            # pylint: enable=undefined-variable\n            result = libusb1.libusb_control_transfer_get_data(transfer_p)\n        else:\n            result = string_at(transfer.buffer, transfer.length)\n        return result\n\n    def getUserData(self):\n        \"\"\"\n        Retrieve user data provided on setup.\n        \"\"\"\n        return self.__user_data\n\n    def setUserData(self, user_data):\n        \"\"\"\n        Change user data.\n        \"\"\"\n        self.__user_data = user_data\n\n    def getISOBufferList(self):\n        \"\"\"\n        Get individual ISO transfer's buffer.\n        Returns a list with one item per ISO transfer, with their\n        individually-configured sizes.\n        Returned list is consistent with getISOSetupList return value.\n        Should not be called on a submitted transfer.\n\n        See also iterISO.\n        \"\"\"\n        transfer_p = self.__transfer\n        transfer = transfer_p.contents\n        # pylint: disable=undefined-variable\n        if transfer.type != TRANSFER_TYPE_ISOCHRONOUS:\n            # pylint: enable=undefined-variable\n            raise TypeError(\n                'This method cannot be called on non-iso transfers.'\n            )\n        return libusb1.get_iso_packet_buffer_list(transfer_p)\n\n    def getISOSetupList(self):\n        \"\"\"\n        Get individual ISO transfer's setup.\n        Returns a list of dicts, each containing an individual ISO transfer\n        parameters:\n        - length\n        - actual_length\n        - status\n        (see libusb1's API documentation for their signification)\n        Returned list is consistent with getISOBufferList return value.\n        Should not be called on a submitted transfer (except for 'length'\n        values).\n        \"\"\"\n        transfer_p = self.__transfer\n        transfer = transfer_p.contents\n        # pylint: disable=undefined-variable\n        if transfer.type != TRANSFER_TYPE_ISOCHRONOUS:\n            # pylint: enable=undefined-variable\n            raise TypeError(\n                'This method cannot be called on non-iso transfers.'\n            )\n        return [\n            {\n                'length': x.length,\n                'actual_length': x.actual_length,\n                'status': x.status,\n            }\n            for x in libusb1.get_iso_packet_list(transfer_p)\n        ]\n\n    def iterISO(self):\n        \"\"\"\n        Generator yielding (status, buffer) for each isochornous transfer.\n        buffer is truncated to actual_length.\n        This is more efficient than calling both getISOBufferList and\n        getISOSetupList when receiving data.\n        Should not be called on a submitted transfer.\n        \"\"\"\n        transfer_p = self.__transfer\n        transfer = transfer_p.contents\n        # pylint: disable=undefined-variable\n        if transfer.type != TRANSFER_TYPE_ISOCHRONOUS:\n            # pylint: enable=undefined-variable\n            raise TypeError(\n                'This method cannot be called on non-iso transfers.'\n            )\n        buffer_position = transfer.buffer\n        for iso_transfer in libusb1.get_iso_packet_list(transfer_p):\n            yield (\n                iso_transfer.status,\n                string_at(buffer_position, iso_transfer.actual_length),\n            )\n            buffer_position += iso_transfer.length\n\n    def setBuffer(self, buffer_or_len):\n        \"\"\"\n        Replace buffer with a new one.\n        Allows resizing read buffer and replacing data sent.\n        Note: resizing is not allowed for isochronous buffer (use\n        setIsochronous).\n        Note: disallowed on control transfers (use setControl).\n        \"\"\"\n        if self.__submitted:\n            raise ValueError('Cannot alter a submitted transfer')\n        transfer = self.__transfer.contents\n        # pylint: disable=undefined-variable\n        if transfer.type == TRANSFER_TYPE_CONTROL:\n            # pylint: enable=undefined-variable\n            raise ValueError(\n                'To alter control transfer buffer, use setControl'\n            )\n        buff = create_binary_buffer(buffer_or_len)\n        # pylint: disable=undefined-variable\n        if transfer.type == TRANSFER_TYPE_ISOCHRONOUS and \\\n                sizeof(buff) != transfer.length:\n            # pylint: enable=undefined-variable\n            raise ValueError(\n                'To alter isochronous transfer buffer length, use '\n                'setIsochronous'\n            )\n        self.__transfer_buffer = buff\n        transfer.buffer = cast(buff, c_void_p)\n        transfer.length = sizeof(buff)\n\n    def isSubmitted(self):\n        \"\"\"\n        Tells if this transfer is submitted and still pending.\n        \"\"\"\n        return self.__submitted\n\n    def submit(self):\n        \"\"\"\n        Submit transfer for asynchronous handling.\n        \"\"\"\n        if self.__submitted:\n            raise ValueError('Cannot submit a submitted transfer')\n        if not self.__initialized:\n            raise ValueError(\n                'Cannot submit a transfer until it has been initialized'\n            )\n        if self.__doomed:\n            raise DoomedTransferError('Cannot submit doomed transfer')\n        self.__before_submit(self)\n        self.__submitted = True\n        result = libusb1.libusb_submit_transfer(self.__transfer)\n        if result:\n            self.__after_completion(self)\n            self.__submitted = False\n            raiseUSBError(result)\n\n    def cancel(self):\n        \"\"\"\n        Cancel transfer.\n        Note: cancellation happens asynchronously, so you must wait for\n        TRANSFER_CANCELLED.\n        \"\"\"\n        if not self.__submitted:\n            # XXX: Workaround for a bug reported on libusb 1.0.8: calling\n            # libusb_cancel_transfer on a non-submitted transfer might\n            # trigger a segfault.\n            raise self.__USBErrorNotFound\n        result = self.__libusb_cancel_transfer(self.__transfer)\n        if result:\n            raise self.__USBError(result)\n\nclass USBTransferHelper(object):\n    \"\"\"\n    Simplifies subscribing to the same transfer over and over, and callback\n    handling:\n    - no need to read event status to execute apropriate code, just setup\n      different functions for each status code\n    - just return True instead of calling submit\n    - no need to check if transfer is doomed before submitting it again,\n      DoomedTransferError is caught.\n\n    Callbacks used in this class must follow the callback API described in\n    USBTransfer, and are expected to return a boolean:\n    - True if transfer is to be submitted again (to receive/send more data)\n    - False otherwise\n\n    Note: as per libusb1 specifications, isochronous transfer global state\n    might be TRANSFER_COMPLETED although some individual packets might\n    have an error status. You can check individual packet status by calling\n    getISOSetupList on transfer object in your callback.\n    \"\"\"\n    def __init__(self, transfer=None):\n        \"\"\"\n        Create a transfer callback dispatcher.\n\n        transfer parameter is deprecated. If provided, it will be equivalent\n        to:\n            helper = USBTransferHelper()\n            transfer.setCallback(helper)\n        and also allows using deprecated methods on this class (otherwise,\n        they raise AttributeError).\n        \"\"\"\n        if transfer is not None:\n            # Deprecated: to drop\n            self.__transfer = transfer\n            transfer.setCallback(self)\n        self.__event_callback_dict = {}\n        self.__errorCallback = DEFAULT_ASYNC_TRANSFER_ERROR_CALLBACK\n\n    def submit(self):\n        \"\"\"\n        Submit the asynchronous read request.\n        Deprecated. Use submit on transfer.\n        \"\"\"\n        # Deprecated: to drop\n        self.__transfer.submit()\n\n    def cancel(self):\n        \"\"\"\n        Cancel a pending read request.\n        Deprecated. Use cancel on transfer.\n        \"\"\"\n        # Deprecated: to drop\n        self.__transfer.cancel()\n\n    def setEventCallback(self, event, callback):\n        \"\"\"\n        Set a function to call for a given event.\n        event must be one of:\n            TRANSFER_COMPLETED\n            TRANSFER_ERROR\n            TRANSFER_TIMED_OUT\n            TRANSFER_CANCELLED\n            TRANSFER_STALL\n            TRANSFER_NO_DEVICE\n            TRANSFER_OVERFLOW\n        \"\"\"\n        if event not in EVENT_CALLBACK_SET:\n            raise ValueError('Unknown event %r.' % (event, ))\n        self.__event_callback_dict[event] = callback\n\n    def setDefaultCallback(self, callback):\n        \"\"\"\n        Set the function to call for event which don't have a specific callback\n        registered.\n        The initial default callback does nothing and returns False.\n        \"\"\"\n        self.__errorCallback = callback\n\n    def getEventCallback(self, event, default=None):\n        \"\"\"\n        Return the function registered to be called for given event identifier.\n        \"\"\"\n        return self.__event_callback_dict.get(event, default)\n\n    def __call__(self, transfer):\n        \"\"\"\n        Callback to set on transfers.\n        \"\"\"\n        if self.getEventCallback(transfer.getStatus(), self.__errorCallback)(\n                transfer):\n            try:\n                transfer.submit()\n            except DoomedTransferError:\n                pass\n\n    def isSubmited(self):\n        \"\"\"\n        Returns whether this reader is currently waiting for an event.\n        Deprecatd. Use isSubmitted on transfer.\n        \"\"\"\n        # Deprecated: to drop\n        return self.__transfer.isSubmitted()\n\nclass USBPollerThread(threading.Thread):\n    \"\"\"\n    Implements libusb1 documentation about threaded, asynchronous\n    applications.\n    In short, instanciate this class once (...per USBContext instance), call\n    start() on the instance, and do whatever you need.\n    This thread will be used to execute transfer completion callbacks, and you\n    are free to use libusb1's synchronous API in another thread, and can forget\n    about libusb1 file descriptors.\n\n    See http://libusb.sourceforge.net/api-1.0/mtasync.html .\n    \"\"\"\n    def __init__(self, context, poller, exc_callback=None):\n        \"\"\"\n        Create a poller thread for given context.\n        Warning: it will not check if another poller instance was already\n        present for that context, and will replace it.\n\n        poller\n            (same as USBPoller.__init__ \"poller\" parameter)\n\n        exc_callback (callable)\n          Called with a libusb_error value as single parameter when event\n          handling fails.\n          If not given, an USBError will be raised, interrupting the thread.\n        \"\"\"\n        super(USBPollerThread, self).__init__()\n        self.daemon = True\n        self.__context = context\n        self.__poller = poller\n        self.__fd_set = set()\n        context.setPollFDNotifiers(self._registerFD, self._unregisterFD)\n        for fd, events in context.getPollFDList():\n            self._registerFD(fd, events, None)\n        if exc_callback is not None:\n            self.exceptionHandler = exc_callback\n\n    def __del__(self):\n        self.__context.setPollFDNotifiers(None, None)\n\n    # pylint: disable=method-hidden\n    @staticmethod\n    def exceptionHandler(exc):\n        raise exc\n    # pylint: enable=method-hidden\n\n    def run(self):\n        # We expect quite some spinning in below loop, so move any unneeded\n        # operation out of it.\n        context = self.__context\n        poll = self.__poller.poll\n        try_lock_events = context.tryLockEvents\n        lock_event_waiters = context.lockEventWaiters\n        wait_for_event = context.waitForEvent\n        unlock_event_waiters = context.unlockEventWaiters\n        event_handling_ok = context.eventHandlingOK\n        unlock_events = context.unlockEvents\n        handle_events_locked = context.handleEventsLocked\n        event_handler_active = context.eventHandlerActive\n        getNextTimeout = context.getNextTimeout\n        exceptionHandler = self.exceptionHandler\n        fd_set = self.__fd_set\n        while fd_set:\n            if try_lock_events():\n                lock_event_waiters()\n                while event_handler_active():\n                    wait_for_event()\n                unlock_event_waiters()\n            else:\n                try:\n                    while event_handling_ok():\n                        if poll(getNextTimeout()):\n                            try:\n                                handle_events_locked()\n                            except USBError:\n                                exceptionHandler(sys.exc_info()[1])\n                finally:\n                    unlock_events()\n\n    def _registerFD(self, fd, events, _):\n        self.__poller.register(fd, events)\n        self.__fd_set.add(fd)\n\n    def _unregisterFD(self, fd, _):\n        self.__fd_set.discard(fd)\n        self.__poller.unregister(fd)\n\nclass USBPoller(object):\n    \"\"\"\n    Class allowing integration of USB event polling in a file-descriptor\n    monitoring event loop.\n\n    WARNING: Do not call \"poll\" from several threads concurently. Do not use\n    synchronous USB transfers in a thread while \"poll\" is running. Doing so\n    will result in unnecessarily long pauses in some threads. Opening and/or\n    closing devices while polling can cause race conditions to occur.\n    \"\"\"\n    def __init__(self, context, poller):\n        \"\"\"\n        Create a poller for given context.\n        Warning: it will not check if another poller instance was already\n        present for that context, and will replace it.\n\n        poller is a polling instance implementing the following methods:\n        - register(fd, event_flags)\n          event_flags have the same meaning as in poll API (POLLIN & POLLOUT)\n        - unregister(fd)\n        - poll(timeout)\n          timeout being a float in seconds, or negative/None if there is no\n          timeout.\n          It must return a list of (descriptor, event) pairs.\n        Note: USBPoller is itself a valid poller.\n        Note2: select.poll uses a timeout in milliseconds, for some reason\n        (all other select.* classes use seconds for timeout), so you should\n        wrap it to convert & round/truncate timeout.\n        \"\"\"\n        self.__context = context\n        self.__poller = poller\n        self.__fd_set = set()\n        context.setPollFDNotifiers(self._registerFD, self._unregisterFD)\n        for fd, events in context.getPollFDList():\n            self._registerFD(fd, events)\n\n    def __del__(self):\n        self.__context.setPollFDNotifiers(None, None)\n\n    def poll(self, timeout=None):\n        \"\"\"\n        Poll for events.\n        timeout can be a float in seconds, or None for no timeout.\n        Returns a list of (descriptor, event) pairs.\n        \"\"\"\n        next_usb_timeout = self.__context.getNextTimeout()\n        if timeout is None or timeout < 0:\n            usb_timeout = next_usb_timeout\n        elif next_usb_timeout:\n            usb_timeout = min(next_usb_timeout, timeout)\n        else:\n            usb_timeout = timeout\n        event_list = self.__poller.poll(usb_timeout)\n        if event_list:\n            fd_set = self.__fd_set\n            result = [(x, y) for x, y in event_list if x not in fd_set]\n            if len(result) != len(event_list):\n                self.__context.handleEventsTimeout()\n        else:\n            result = event_list\n            self.__context.handleEventsTimeout()\n        return result\n\n    def register(self, fd, events):\n        \"\"\"\n        Register an USB-unrelated fd to poller.\n        Convenience method.\n        \"\"\"\n        if fd in self.__fd_set:\n            raise ValueError(\n                'This fd is a special USB event fd, it cannot be polled.'\n            )\n        self.__poller.register(fd, events)\n\n    def unregister(self, fd):\n        \"\"\"\n        Unregister an USB-unrelated fd from poller.\n        Convenience method.\n        \"\"\"\n        if fd in self.__fd_set:\n            raise ValueError(\n                'This fd is a special USB event fd, it must stay registered.'\n            )\n        self.__poller.unregister(fd)\n\n    # pylint: disable=unused-argument\n    def _registerFD(self, fd, events, user_data=None):\n        self.register(fd, events)\n        self.__fd_set.add(fd)\n    # pylint: enable=unused-argument\n\n    # pylint: disable=unused-argument\n    def _unregisterFD(self, fd, user_data=None):\n        self.__fd_set.discard(fd)\n        self.unregister(fd)\n    # pylint: enable=unused-argument\n\nclass _ReleaseInterface(object):\n    def __init__(self, handle, interface):\n        self._handle = handle\n        self._interface = interface\n\n    def __enter__(self):\n        # USBDeviceHandle.claimInterface already claimed the interface.\n        pass\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self._handle.releaseInterface(self._interface)\n\nclass USBDeviceHandle(object):\n    \"\"\"\n    Represents an opened USB device.\n    \"\"\"\n    __handle = None\n    __libusb_close = libusb1.libusb_close\n    __USBError = USBError\n    # pylint: disable=undefined-variable\n    __USBErrorNoDevice = USBErrorNoDevice\n    __USBErrorNotFound = USBErrorNotFound\n    __USBErrorInterrupted = USBErrorInterrupted\n    # pylint: enable=undefined-variable\n    __set = set\n    __KeyError = KeyError\n    __sys = sys\n\n    def __init__(self, context, handle, device):\n        \"\"\"\n        You should not instanciate this class directly.\n        Call \"open\" method on an USBDevice instance to get an USBDeviceHandle\n        instance.\n        \"\"\"\n        self.__context = context\n        # Weak reference to transfers about this device so we can clean up\n        # before closing device.\n        self.__transfer_set = WeakSet()\n        # Strong references to inflight transfers so they do not get freed\n        # even if user drops all strong references to them. If this instance\n        # is garbage-collected, we close all transfers, so it's fine.\n        self.__inflight = inflight = set()\n        # XXX: For some reason, doing self.__inflight.{add|remove} inside\n        # getTransfer causes extra intermediate python objects for each\n        # allocated transfer. Storing them as properties solves this. Found\n        # with objgraph.\n        self.__inflight_add = inflight.add\n        self.__inflight_remove = inflight.remove\n        self.__handle = handle\n        self.__device = device\n\n    def __del__(self):\n        self.close()\n\n    def close(self):\n        \"\"\"\n        Close this handle. If not called explicitely, will be called by\n        destructor.\n\n        This method cancels any in-flight transfer when it is called. As\n        cancellation is not immediate, this method needs to let libusb handle\n        events until transfers are actually cancelled.\n        In multi-threaded programs, this can lead to stalls. To avoid this,\n        do not close nor let GC collect a USBDeviceHandle which has in-flight\n        transfers.\n        \"\"\"\n        handle = self.__handle\n        if handle is None:\n            return\n        # Build a strong set from weak self.__transfer_set so we can doom\n        # and close all contained transfers.\n        # Because of backward compatibility, self.__transfer_set might be a\n        # wrapper around WeakKeyDictionary. As it might be modified by gc,\n        # we must pop until there is not key left instead of iterating over\n        # it.\n        weak_transfer_set = self.__transfer_set\n        transfer_set = self.__set()\n        while True:\n            try:\n                transfer = weak_transfer_set.pop()\n            except self.__KeyError:\n                break\n            transfer_set.add(transfer)\n            transfer.doom()\n        inflight = self.__inflight\n        for transfer in inflight:\n            try:\n                transfer.cancel()\n            except (self.__USBErrorNotFound, self.__USBErrorNoDevice):\n                pass\n        while inflight:\n            try:\n                self.__context.handleEvents()\n            except self.__USBErrorInterrupted:\n                pass\n        for transfer in transfer_set:\n            transfer.close()\n        self.__libusb_close(handle)\n        self.__handle = None\n\n    def getDevice(self):\n        \"\"\"\n        Get an USBDevice instance for the device accessed through this handle.\n        Useful for example to query its configurations.\n        \"\"\"\n        return self.__device\n\n    def getConfiguration(self):\n        \"\"\"\n        Get the current configuration number for this device.\n        \"\"\"\n        configuration = c_int()\n        mayRaiseUSBError(libusb1.libusb_get_configuration(\n            self.__handle, byref(configuration),\n        ))\n        return configuration.value\n\n    def setConfiguration(self, configuration):\n        \"\"\"\n        Set the configuration number for this device.\n        \"\"\"\n        mayRaiseUSBError(\n            libusb1.libusb_set_configuration(self.__handle, configuration),\n        )\n\n    def claimInterface(self, interface):\n        \"\"\"\n        Claim (= get exclusive access to) given interface number. Required to\n        receive/send data.\n\n        Can be used as a context manager:\n            with handle.claimInterface(0):\n                # do stuff\n            # handle.releaseInterface(0) gets automatically called\n        \"\"\"\n        mayRaiseUSBError(\n            libusb1.libusb_claim_interface(self.__handle, interface),\n        )\n        return _ReleaseInterface(self, interface)\n\n    def releaseInterface(self, interface):\n        \"\"\"\n        Release interface, allowing another process to use it.\n        \"\"\"\n        mayRaiseUSBError(\n            libusb1.libusb_release_interface(self.__handle, interface),\n        )\n\n    def setInterfaceAltSetting(self, interface, alt_setting):\n        \"\"\"\n        Set interface's alternative setting (both parameters are integers).\n        \"\"\"\n        mayRaiseUSBError(libusb1.libusb_set_interface_alt_setting(\n            self.__handle, interface, alt_setting,\n        ))\n\n    def clearHalt(self, endpoint):\n        \"\"\"\n        Clear a halt state on given endpoint number.\n        \"\"\"\n        mayRaiseUSBError(libusb1.libusb_clear_halt(self.__handle, endpoint))\n\n    def resetDevice(self):\n        \"\"\"\n        Reinitialise current device.\n        Attempts to restore current configuration & alt settings.\n        If this fails, will result in a device disconnect & reconnect, so you\n        have to close current device and rediscover it (notified by a\n        ERROR_NOT_FOUND error code).\n        \"\"\"\n        mayRaiseUSBError(libusb1.libusb_reset_device(self.__handle))\n\n    def kernelDriverActive(self, interface):\n        \"\"\"\n        Tell whether a kernel driver is active on given interface number.\n        \"\"\"\n        result = libusb1.libusb_kernel_driver_active(self.__handle, interface)\n        if result == 0:\n            return False\n        elif result == 1:\n            return True\n        raiseUSBError(result)\n\n    def detachKernelDriver(self, interface):\n        \"\"\"\n        Ask kernel driver to detach from given interface number.\n        \"\"\"\n        mayRaiseUSBError(\n            libusb1.libusb_detach_kernel_driver(self.__handle, interface),\n        )\n\n    def attachKernelDriver(self, interface):\n        \"\"\"\n        Ask kernel driver to re-attach to given interface number.\n        \"\"\"\n        mayRaiseUSBError(\n            libusb1.libusb_attach_kernel_driver(self.__handle, interface),\n        )\n\n    def setAutoDetachKernelDriver(self, enable):\n        \"\"\"\n        Control automatic kernel driver detach.\n        enable (bool)\n            True to enable auto-detach, False to disable it.\n        \"\"\"\n        mayRaiseUSBError(libusb1.libusb_set_auto_detach_kernel_driver(\n            self.__handle, bool(enable),\n        ))\n\n    def getSupportedLanguageList(self):\n        \"\"\"\n        Return a list of USB language identifiers (as integers) supported by\n        current device for its string descriptors.\n\n        Note: language identifiers seem (I didn't check them all...) very\n        similar to windows language identifiers, so you may want to use\n        locales.windows_locale to get an rfc3066 representation. The 5 standard\n        HID language codes are missing though.\n        \"\"\"\n        descriptor_string = create_binary_buffer(STRING_LENGTH)\n        result = libusb1.libusb_get_string_descriptor(\n            self.__handle, 0, 0, descriptor_string, sizeof(descriptor_string),\n        )\n        # pylint: disable=undefined-variable\n        if result == ERROR_PIPE:\n            # pylint: enable=undefined-variable\n            # From libusb_control_transfer doc:\n            # control request not supported by the device\n            return []\n        mayRaiseUSBError(result)\n        length = cast(descriptor_string, POINTER(c_ubyte))[0]\n        langid_list = cast(descriptor_string, POINTER(c_uint16))\n        result = []\n        append = result.append\n        for offset in range(1, length / 2):\n            append(libusb1.libusb_le16_to_cpu(langid_list[offset]))\n        return result\n\n    def getStringDescriptor(self, descriptor, lang_id):\n        \"\"\"\n        Fetch description string for given descriptor and in given language.\n        Use getSupportedLanguageList to know which languages are available.\n        Return value is an unicode string.\n        Return None if there is no such descriptor on device.\n        \"\"\"\n        descriptor_string = create_binary_buffer(STRING_LENGTH)\n        try:\n            mayRaiseUSBError(libusb1.libusb_get_string_descriptor(\n                self.__handle, descriptor, lang_id, descriptor_string,\n                sizeof(descriptor_string),\n            ))\n        # pylint: disable=undefined-variable\n        except USBErrorNotFound:\n            # pylint: enable=undefined-variable\n            return None\n        return descriptor_string.value.decode('UTF-16-LE')\n\n    def getRawDescriptor(self, descriptor, desc_index, length):\n        \"\"\"\n        Fetch description string for given descriptor and in given language.\n        Use getSupportedLanguageList to know which languages are available.\n        Return value is an unicode string.\n        Return None if there is no such descriptor on device.\n        \"\"\"\n        data = (c_uint8 * length)()\n        try:\n            mayRaiseUSBError(libusb1.libusb_get_descriptor(\n                self.__handle, descriptor, desc_index, data, length\n            ))\n        # pylint: disable=undefined-variable\n        except USBErrorNotFound:\n            # pylint: enable=undefined-variable\n            return None\n        return [ x for x in data ]\n\n    def getASCIIStringDescriptor(self, descriptor):\n        \"\"\"\n        Fetch description string for given descriptor in first available\n        language.\n        Return value is an ASCII string.\n        Return None if there is no such descriptor on device.\n        \"\"\"\n        descriptor_string = create_binary_buffer(STRING_LENGTH)\n        try:\n            mayRaiseUSBError(libusb1.libusb_get_string_descriptor_ascii(\n                self.__handle, descriptor, descriptor_string,\n                sizeof(descriptor_string),\n            ))\n        # pylint: disable=undefined-variable\n        except USBErrorNotFound:\n            # pylint: enable=undefined-variable\n            return None\n        return descriptor_string.value.decode('ASCII')\n\n    # Sync I/O\n\n    def _controlTransfer(\n            self, request_type, request, value, index, data, length, timeout):\n        result = libusb1.libusb_control_transfer(\n            self.__handle, request_type, request, value, index, data, length,\n            timeout,\n        )\n        mayRaiseUSBError(result)\n        return result\n\n    def controlWrite(\n            self, request_type, request, value, index, data, timeout=0):\n        \"\"\"\n        Synchronous control write.\n        request_type: request type bitmask (bmRequestType), see\n          constants TYPE_* and RECIPIENT_*.\n        request: request id (some values are standard).\n        value, index, data: meaning is request-dependent.\n        timeout: in milliseconds, how long to wait for device acknowledgement.\n          Set to 0 to disable.\n\n        Returns the number of bytes actually sent.\n        \"\"\"\n        # pylint: disable=undefined-variable\n        request_type = (request_type & ~ENDPOINT_DIR_MASK) | ENDPOINT_OUT\n        # pylint: enable=undefined-variable\n        data = (c_char * len(data))(*data)\n        return self._controlTransfer(request_type, request, value, index, data,\n                                     sizeof(data), timeout)\n\n    def controlRead(\n            self, request_type, request, value, index, length, timeout=0):\n        \"\"\"\n        Synchronous control read.\n        timeout: in milliseconds, how long to wait for data. Set to 0 to\n          disable.\n        See controlWrite for other parameters description.\n\n        Returns received data.\n        \"\"\"\n        # pylint: disable=undefined-variable\n        request_type = (request_type & ~ENDPOINT_DIR_MASK) | ENDPOINT_IN\n        # pylint: enable=undefined-variable\n        data = create_binary_buffer(length)\n        transferred = self._controlTransfer(\n            request_type, request, value, index, data, length, timeout,\n        )\n        return data.raw[:transferred]\n\n    def _bulkTransfer(self, endpoint, data, length, timeout):\n        transferred = c_int()\n        mayRaiseUSBError(libusb1.libusb_bulk_transfer(\n            self.__handle, endpoint, data, length, byref(transferred), timeout,\n        ))\n        return transferred.value\n\n    def bulkWrite(self, endpoint, data, timeout=0):\n        \"\"\"\n        Synchronous bulk write.\n        endpoint: endpoint to send data to.\n        data: data to send.\n        timeout: in milliseconds, how long to wait for device acknowledgement.\n          Set to 0 to disable.\n\n        Returns the number of bytes actually sent.\n        \"\"\"\n        # pylint: disable=undefined-variable\n        endpoint = (endpoint & ~ENDPOINT_DIR_MASK) | ENDPOINT_OUT\n        # pylint: enable=undefined-variable\n        data = (c_char * len(data))(*data)\n        return self._bulkTransfer(endpoint, data, sizeof(data), timeout)\n\n    def bulkRead(self, endpoint, length, timeout=0):\n        \"\"\"\n        Synchronous bulk read.\n        timeout: in milliseconds, how long to wait for data. Set to 0 to\n          disable.\n        See bulkWrite for other parameters description.\n\n        Returns received data.\n        \"\"\"\n        # pylint: disable=undefined-variable\n        endpoint = (endpoint & ~ENDPOINT_DIR_MASK) | ENDPOINT_IN\n        # pylint: enable=undefined-variable\n        data = create_binary_buffer(length)\n        transferred = self._bulkTransfer(endpoint, data, length, timeout)\n        # pylint: disable=invalid-slice-index\n        return data.raw[:transferred]\n        # pylint: enable=invalid-slice-index\n\n    def _interruptTransfer(self, endpoint, data, length, timeout):\n        transferred = c_int()\n        mayRaiseUSBError(libusb1.libusb_interrupt_transfer(\n            self.__handle, endpoint, data, length, byref(transferred), timeout,\n        ))\n        return transferred.value\n\n    def interruptWrite(self, endpoint, data, timeout=0):\n        \"\"\"\n        Synchronous interrupt write.\n        endpoint: endpoint to send data to.\n        data: data to send.\n        timeout: in milliseconds, how long to wait for device acknowledgement.\n          Set to 0 to disable.\n\n        Returns the number of bytes actually sent.\n        \"\"\"\n        # pylint: disable=undefined-variable\n        endpoint = (endpoint & ~ENDPOINT_DIR_MASK) | ENDPOINT_OUT\n        # pylint: enable=undefined-variable\n        data = (c_char * len(data))(*data)\n        return self._interruptTransfer(endpoint, data, sizeof(data), timeout)\n\n    def interruptRead(self, endpoint, length, timeout=0):\n        \"\"\"\n        Synchronous interrupt write.\n        timeout: in milliseconds, how long to wait for data. Set to 0 to\n          disable.\n        See interruptRead for other parameters description.\n\n        Returns received data.\n        \"\"\"\n        # pylint: disable=undefined-variable\n        endpoint = (endpoint & ~ENDPOINT_DIR_MASK) | ENDPOINT_IN\n        # pylint: enable=undefined-variable\n        data = create_binary_buffer(length)\n        transferred = self._interruptTransfer(endpoint, data, length, timeout)\n        # pylint: disable=invalid-slice-index\n        return data.raw[:transferred]\n        # pylint: enable=invalid-slice-index\n\n    def getTransfer(self, iso_packets=0):\n        \"\"\"\n        Get an USBTransfer instance for asynchronous use.\n        iso_packets: the number of isochronous transfer descriptors to\n          allocate.\n        \"\"\"\n        result = USBTransfer(\n            self.__handle, iso_packets,\n            self.__inflight_add, self.__inflight_remove,\n        )\n        self.__transfer_set.add(result)\n        return result\n\nclass USBConfiguration(object):\n    def __init__(self, context, config):\n        \"\"\"\n        You should not instanciate this class directly.\n        Call USBDevice methods to get instances of this class.\n        \"\"\"\n        if not isinstance(config, libusb1.libusb_config_descriptor):\n            raise TypeError('Unexpected descriptor type.')\n        self.__config = config\n        self.__context = context\n\n    def getNumInterfaces(self):\n        return self.__config.bNumInterfaces\n\n    __len__ = getNumInterfaces\n\n    def getConfigurationValue(self):\n        return self.__config.bConfigurationValue\n\n    def getDescriptor(self):\n        return self.__config.iConfiguration\n\n    def getAttributes(self):\n        return self.__config.bmAttributes\n\n    def getMaxPower(self):\n        \"\"\"\n        Returns device's power consumption in mW.\n        Beware of unit: USB descriptor uses 2mW increments, this method\n        converts it to mW units.\n        \"\"\"\n        return self.__config.MaxPower * 2\n\n    def getExtra(self):\n        \"\"\"\n        Returns a list of extra (non-basic) descriptors (DFU, HID, ...).\n        \"\"\"\n        return libusb1.get_extra(self.__config)\n\n    def __iter__(self):\n        \"\"\"\n        Iterates over interfaces available in this configuration, yielding\n        USBInterface instances.\n        \"\"\"\n        context = self.__context\n        interface_list = self.__config.interface\n        for interface_num in range(self.getNumInterfaces()):\n            yield USBInterface(context, interface_list[interface_num])\n\n    # BBB\n    iterInterfaces = __iter__\n\n    def __getitem__(self, interface):\n        \"\"\"\n        Returns an USBInterface instance.\n        \"\"\"\n        if not isinstance(interface, int):\n            raise TypeError('interface parameter must be an integer')\n        if not 0 <= interface < self.getNumInterfaces():\n            raise IndexError('No such interface: %r' % (interface, ))\n        return USBInterface(self.__context, self.__config.interface[interface])\n\nclass USBInterface(object):\n    def __init__(self, context, interface):\n        \"\"\"\n        You should not instanciate this class directly.\n        Call USBConfiguration methods to get instances of this class.\n        \"\"\"\n        if not isinstance(interface, libusb1.libusb_interface):\n            raise TypeError('Unexpected descriptor type.')\n        self.__interface = interface\n        self.__context = context\n\n    def getNumSettings(self):\n        return self.__interface.num_altsetting\n\n    __len__ = getNumSettings\n\n    def __iter__(self):\n        \"\"\"\n        Iterates over settings in this insterface, yielding\n        USBInterfaceSetting instances.\n        \"\"\"\n        context = self.__context\n        alt_setting_list = self.__interface.altsetting\n        for alt_setting_num in range(self.getNumSettings()):\n            yield USBInterfaceSetting(\n                context, alt_setting_list[alt_setting_num])\n\n    # BBB\n    iterSettings = __iter__\n\n    def __getitem__(self, alt_setting):\n        \"\"\"\n        Returns an USBInterfaceSetting instance.\n        \"\"\"\n        if not isinstance(alt_setting, int):\n            raise TypeError('alt_setting parameter must be an integer')\n        if not 0 <= alt_setting < self.getNumSettings():\n            raise IndexError('No such setting: %r' % (alt_setting, ))\n        return USBInterfaceSetting(\n            self.__context, self.__interface.altsetting[alt_setting])\n\nclass USBInterfaceSetting(object):\n    def __init__(self, context, alt_setting):\n        \"\"\"\n        You should not instanciate this class directly.\n        Call USBDevice or USBInterface methods to get instances of this class.\n        \"\"\"\n        if not isinstance(alt_setting, libusb1.libusb_interface_descriptor):\n            raise TypeError('Unexpected descriptor type.')\n        self.__alt_setting = alt_setting\n        self.__context = context\n\n    def getNumber(self):\n        return self.__alt_setting.bInterfaceNumber\n\n    def getAlternateSetting(self):\n        return self.__alt_setting.bAlternateSetting\n\n    def getNumEndpoints(self):\n        return self.__alt_setting.bNumEndpoints\n\n    __len__ = getNumEndpoints\n\n    def getClass(self):\n        return self.__alt_setting.bInterfaceClass\n\n    def getSubClass(self):\n        return self.__alt_setting.bInterfaceSubClass\n\n    def getClassTuple(self):\n        \"\"\"\n        For convenience: class and subclass are probably often matched\n        simultaneously.\n        \"\"\"\n        alt_setting = self.__alt_setting\n        return (alt_setting.bInterfaceClass, alt_setting.bInterfaceSubClass)\n\n    # BBB\n    getClassTupple = getClassTuple\n\n    def getProtocol(self):\n        return self.__alt_setting.bInterfaceProtocol\n\n    def getDescriptor(self):\n        return self.__alt_setting.iInterface\n\n    def getExtra(self):\n        return libusb1.get_extra(self.__alt_setting)\n\n    def __iter__(self):\n        \"\"\"\n        Iterates over endpoints in this interface setting , yielding\n        USBEndpoint instances.\n        \"\"\"\n        context = self.__context\n        endpoint_list = self.__alt_setting.endpoint\n        for endpoint_num in range(self.getNumEndpoints()):\n            yield USBEndpoint(context, endpoint_list[endpoint_num])\n\n    # BBB\n    iterEndpoints = __iter__\n\n    def __getitem__(self, endpoint):\n        \"\"\"\n        Returns an USBEndpoint instance.\n        \"\"\"\n        if not isinstance(endpoint, int):\n            raise TypeError('endpoint parameter must be an integer')\n        if not 0 <= endpoint < self.getNumEndpoints():\n            raise ValueError('No such endpoint: %r' % (endpoint, ))\n        return USBEndpoint(\n            self.__context, self.__alt_setting.endpoint[endpoint])\n\nclass USBEndpoint(object):\n    def __init__(self, context, endpoint):\n        if not isinstance(endpoint, libusb1.libusb_endpoint_descriptor):\n            raise TypeError('Unexpected descriptor type.')\n        self.__endpoint = endpoint\n        self.__context = context\n\n    def getAddress(self):\n        return self.__endpoint.bEndpointAddress\n\n    def getAttributes(self):\n        return self.__endpoint.bmAttributes\n\n    def getMaxPacketSize(self):\n        return self.__endpoint.wMaxPacketSize\n\n    def getInterval(self):\n        return self.__endpoint.bInterval\n\n    def getRefresh(self):\n        return self.__endpoint.bRefresh\n\n    def getSyncAddress(self):\n        return self.__endpoint.bSynchAddress\n\n    def getExtra(self):\n        return libusb1.get_extra(self.__endpoint)\n\nclass USBDevice(object):\n    \"\"\"\n    Represents a USB device.\n    \"\"\"\n\n    __configuration_descriptor_list = ()\n    __libusb_unref_device = libusb1.libusb_unref_device\n    __libusb_free_config_descriptor = libusb1.libusb_free_config_descriptor\n    __byref = byref\n    __KeyError = KeyError\n\n    def __init__(self, context, device_p, can_load_configuration=True):\n        \"\"\"\n        You should not instanciate this class directly.\n        Call USBContext methods to receive instances of this class.\n        \"\"\"\n        self.__context = context\n        self.__close_set = WeakSet()\n        libusb1.libusb_ref_device(device_p)\n        self.device_p = device_p\n        # Fetch device descriptor\n        device_descriptor = libusb1.libusb_device_descriptor()\n        result = libusb1.libusb_get_device_descriptor(\n            device_p, byref(device_descriptor))\n        mayRaiseUSBError(result)\n        self.device_descriptor = device_descriptor\n        if can_load_configuration:\n            self.__configuration_descriptor_list = descriptor_list = []\n            append = descriptor_list.append\n            device_p = self.device_p\n            for configuration_id in range(\n                    self.device_descriptor.bNumConfigurations):\n                config = libusb1.libusb_config_descriptor_p()\n                result = libusb1.libusb_get_config_descriptor(\n                    device_p, configuration_id, byref(config))\n                # pylint: disable=undefined-variable\n                if result == ERROR_NOT_FOUND:\n                # pylint: enable=undefined-variable\n                    # Some devices (ex windows' root hubs) tell they have\n                    # one configuration, but they have no configuration\n                    # descriptor.\n                    continue\n                mayRaiseUSBError(result)\n                append(config.contents)\n\n    def __del__(self):\n        self.close()\n\n    def close(self):\n        pop = self.__close_set.pop\n        while True:\n            try:\n                closable = pop()\n            except self.__KeyError:\n                break\n            closable.close()\n        if not self.device_p:\n            return\n        self.__libusb_unref_device(self.device_p)\n        # pylint: disable=redefined-outer-name\n        byref = self.__byref\n        # pylint: enable=redefined-outer-name\n        descriptor_list = self.__configuration_descriptor_list\n        while descriptor_list:\n            self.__libusb_free_config_descriptor(\n                byref(descriptor_list.pop()),\n            )\n        self.device_p = None\n\n    def __str__(self):\n        return 'Bus %03i Device %03i: ID %04x:%04x' % (\n            self.getBusNumber(),\n            self.getDeviceAddress(),\n            self.getVendorID(),\n            self.getProductID(),\n        )\n\n    def __len__(self):\n        return len(self.__configuration_descriptor_list)\n\n    def __getitem__(self, index):\n        return USBConfiguration(\n            self.__context, self.__configuration_descriptor_list[index])\n\n    def __key(self):\n        return (\n            id(self.__context), self.getBusNumber(),\n            self.getDeviceAddress(), self.getVendorID(),\n            self.getProductID(),\n        )\n\n    def __hash__(self):\n        return hash(self.__key())\n\n    def __eq__(self, other):\n        # pylint: disable=unidiomatic-typecheck\n        return type(self) == type(other) and (\n            # pylint: enable=unidiomatic-typecheck\n            self.device_p == other.device_p or\n            # pylint: disable=protected-access\n            self.__key() == other.__key()\n            # pylint: enable=protected-access\n        )\n\n    def iterConfigurations(self):\n        context = self.__context\n        for config in self.__configuration_descriptor_list:\n            yield USBConfiguration(context, config)\n\n    # BBB\n    iterConfiguations = iterConfigurations\n\n    def iterSettings(self):\n        for config in self.iterConfigurations():\n            for interface in config:\n                for setting in interface:\n                    yield setting\n\n    def getBusNumber(self):\n        \"\"\"\n        Get device's bus number.\n        \"\"\"\n        return libusb1.libusb_get_bus_number(self.device_p)\n\n    def getPortNumber(self):\n        \"\"\"\n        Get device's port number.\n        \"\"\"\n        return libusb1.libusb_get_port_number(self.device_p)\n\n    def getPortNumberList(self):\n        \"\"\"\n        Get the port number of each hub toward device.\n        \"\"\"\n        port_list = (c_uint8 * PATH_MAX_DEPTH)()\n        result = libusb1.libusb_get_port_numbers(\n            self.device_p, port_list, len(port_list))\n        mayRaiseUSBError(result)\n        return list(port_list[:result])\n\n    # TODO: wrap libusb_get_parent when/if libusb removes the need to be inside\n    # a libusb_(get|free)_device_list block.\n\n    def getDeviceAddress(self):\n        \"\"\"\n        Get device's address on its bus.\n        \"\"\"\n        return libusb1.libusb_get_device_address(self.device_p)\n\n    def getbcdUSB(self):\n        \"\"\"\n        Get the USB spec version device complies to, in BCD format.\n        \"\"\"\n        return self.device_descriptor.bcdUSB\n\n    def getDeviceClass(self):\n        \"\"\"\n        Get device's class id.\n        \"\"\"\n        return self.device_descriptor.bDeviceClass\n\n    def getDeviceSubClass(self):\n        \"\"\"\n        Get device's subclass id.\n        \"\"\"\n        return self.device_descriptor.bDeviceSubClass\n\n    def getDeviceProtocol(self):\n        \"\"\"\n        Get device's protocol id.\n        \"\"\"\n        return self.device_descriptor.bDeviceProtocol\n\n    def getMaxPacketSize0(self):\n        \"\"\"\n        Get device's max packet size for endpoint 0 (control).\n        \"\"\"\n        return self.device_descriptor.bMaxPacketSize0\n\n    def getMaxPacketSize(self, endpoint):\n        \"\"\"\n        Get device's max packet size for given endpoint.\n\n        Warning: this function will not always give you the expected result.\n        See https://libusb.org/ticket/77 . You should instead consult the\n        endpoint descriptor of current configuration and alternate setting.\n        \"\"\"\n        result = libusb1.libusb_get_max_packet_size(self.device_p, endpoint)\n        mayRaiseUSBError(result)\n        return result\n\n    def getMaxISOPacketSize(self, endpoint):\n        \"\"\"\n        Get the maximum size for a single isochronous packet for given\n        endpoint.\n\n        Warning: this function will not always give you the expected result.\n        See https://libusb.org/ticket/77 . You should instead consult the\n        endpoint descriptor of current configuration and alternate setting.\n        \"\"\"\n        result = libusb1.libusb_get_max_iso_packet_size(self.device_p, endpoint)\n        mayRaiseUSBError(result)\n        return result\n\n    def getVendorID(self):\n        \"\"\"\n        Get device's vendor id.\n        \"\"\"\n        return self.device_descriptor.idVendor\n\n    def getProductID(self):\n        \"\"\"\n        Get device's product id.\n        \"\"\"\n        return self.device_descriptor.idProduct\n\n    def getbcdDevice(self):\n        \"\"\"\n        Get device's release number.\n        \"\"\"\n        return self.device_descriptor.bcdDevice\n\n    def getSupportedLanguageList(self):\n        \"\"\"\n        Get the list of language ids device has string descriptors for.\n        \"\"\"\n        return self.open().getSupportedLanguageList()\n\n    def _getStringDescriptor(self, descriptor, lang_id):\n        if descriptor:\n            return self.open().getStringDescriptor(descriptor, lang_id)\n\n    def _getASCIIStringDescriptor(self, descriptor):\n        if descriptor:\n            return self.open().getASCIIStringDescriptor(descriptor)\n\n    def getManufacturer(self):\n        \"\"\"\n        Get device's manufaturer name.\n        Note: opens the device temporarily.\n        \"\"\"\n        return self._getASCIIStringDescriptor(\n            self.device_descriptor.iManufacturer)\n\n    def getProduct(self):\n        \"\"\"\n        Get device's product name.\n        Note: opens the device temporarily.\n        \"\"\"\n        return self._getASCIIStringDescriptor(self.device_descriptor.iProduct)\n\n    def getSerialNumber(self):\n        \"\"\"\n        Get device's serial number.\n        Note: opens the device temporarily.\n        \"\"\"\n        return self._getASCIIStringDescriptor(\n            self.device_descriptor.iSerialNumber)\n\n    def getNumConfigurations(self):\n        \"\"\"\n        Get device's number of possible configurations.\n        \"\"\"\n        return self.device_descriptor.bNumConfigurations\n\n    def getDeviceSpeed(self):\n        \"\"\"\n        Get device's speed.\n\n        Returns one of:\n            SPEED_UNKNOWN\n            SPEED_LOW\n            SPEED_FULL\n            SPEED_HIGH\n            SPEED_SUPER\n        \"\"\"\n        return libusb1.libusb_get_device_speed(self.device_p)\n\n    def open(self):\n        \"\"\"\n        Open device.\n        Returns an USBDeviceHandle instance.\n        \"\"\"\n        handle = libusb1.libusb_device_handle_p()\n        mayRaiseUSBError(libusb1.libusb_open(self.device_p, byref(handle)))\n        result = USBDeviceHandle(self.__context, handle, self)\n        self.__close_set.add(result)\n        return result\n\n_zero_tv = libusb1.timeval(0, 0)\n_zero_tv_p = byref(_zero_tv)\n\nclass USBContext(object):\n    \"\"\"\n    libusb1 USB context.\n\n    Provides methods to enumerate & look up USB devices.\n    Also provides access to global (device-independent) libusb1 functions.\n    \"\"\"\n    __libusb_exit = libusb1.libusb_exit\n    __context_p = None\n    __added_cb = None\n    __removed_cb = None\n    __poll_cb_user_data = None\n    __libusb_set_pollfd_notifiers = libusb1.libusb_set_pollfd_notifiers\n    __null_pointer = POINTER(None)\n    __KeyError = KeyError\n    __auto_open = True\n\n    # pylint: disable=no-self-argument,protected-access\n    def _validContext(func):\n        # Defined inside USBContext so we can access \"self.__*\".\n        @contextlib.contextmanager\n        def refcount(self):\n            with self.__context_cond:\n                if not self.__context_p and self.__auto_open:\n                    # BBB\n                    warnings.warn(\n                        'Use \"with USBContext() as context:\" for safer cleanup'\n                        ' on interpreter shutdown. See also USBContext.open().',\n                        DeprecationWarning,\n                    )\n                    self.open()\n                self.__context_refcount += 1\n            try:\n                yield\n            finally:\n                with self.__context_cond:\n                    self.__context_refcount -= 1\n                    if not self.__context_refcount:\n                        self.__context_cond.notifyAll()\n        if inspect.isgeneratorfunction(func):\n            def wrapper(self, *args, **kw):\n                with refcount(self):\n                    if self.__context_p:\n                        # pylint: disable=not-callable\n                        for value in func(self, *args, **kw):\n                            # pylint: enable=not-callable\n                            yield value\n        else:\n            def wrapper(self, *args, **kw):\n                with refcount(self):\n                    if self.__context_p:\n                        # pylint: disable=not-callable\n                        return func(self, *args, **kw)\n                        # pylint: enable=not-callable\n        functools.update_wrapper(wrapper, func)\n        return wrapper\n    # pylint: enable=no-self-argument,protected-access\n\n    def __init__(self):\n        \"\"\"\n        Create a new USB context.\n        \"\"\"\n        # Used to prevent an exit to cause a segfault if a concurrent thread\n        # is still in libusb.\n        self.__context_refcount = 0\n        self.__context_cond = threading.Condition()\n        self.__context_p = libusb1.libusb_context_p()\n        self.__hotplug_callback_dict = {}\n        self.__close_set = WeakSet()\n\n    def __del__(self):\n        # Avoid locking.\n        # XXX: Assumes __del__ should not normally be called while any\n        # instance's method is being executed. It seems unlikely (they hold a\n        # reference to their instance).\n        self._exit()\n\n    def __enter__(self):\n        return self.open()\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.close()\n\n    def open(self):\n        \"\"\"\n        Finish context initialisation, as is normally done in __enter__ .\n\n        This happens automatically on the first method call needing access to\n        the uninitialised properties, but with a warning.\n        Call this method ONLY if your usage pattern prevents you from using the\n            with USBContext() as contewt:\n        form: this means there are ways to avoid calling close(), which can\n        cause issues particularly hard to debug (ex: interpreter hangs on\n        exit).\n        \"\"\"\n        assert self.__context_refcount == 0\n        mayRaiseUSBError(libusb1.libusb_init(byref(self.__context_p)))\n        return self\n\n    def close(self):\n        \"\"\"\n        Close (destroy) this USB context, and all related instances.\n\n        When this method has been called, methods on its instance will\n        become mosty no-ops, returning None until explicitly re-opened\n        (by calling open() or __enter__()).\n\n        Note: \"exit\" is a deprecated alias of \"close\".\n        \"\"\"\n        self.__auto_open = False\n        self.__context_cond.acquire()\n        try:\n            while self.__context_refcount and self.__context_p:\n                self.__context_cond.wait()\n            self._exit()\n        finally:\n            self.__context_cond.notifyAll()\n            self.__context_cond.release()\n\n    def _exit(self):\n        context_p = self.__context_p\n        if context_p:\n            for handle in self.__hotplug_callback_dict.keys():\n                self.hotplugDeregisterCallback(handle)\n            pop = self.__close_set.pop\n            while True:\n                try:\n                    closable = pop()\n                except self.__KeyError:\n                    break\n                closable.close()\n            self.__libusb_exit(context_p)\n            self.__context_p = libusb1.libusb_context_p()\n            self.__added_cb = self.__null_pointer\n            self.__removed_cb = self.__null_pointer\n\n    # BBB\n    exit = close\n\n    @_validContext\n    def getDeviceIterator(self, skip_on_error=False):\n        \"\"\"\n        Return an iterator over all USB devices currently plugged in, as USBDevice\n        instances.\n\n        skip_on_error (bool)\n            If True, ignore devices which raise USBError.\n        \"\"\"\n        device_p_p = libusb1.libusb_device_p_p()\n        libusb_device_p = libusb1.libusb_device_p\n        device_list_len = libusb1.libusb_get_device_list(self.__context_p,\n                                                         byref(device_p_p))\n        mayRaiseUSBError(device_list_len)\n        try:\n            for device_p in device_p_p[:device_list_len]:\n                try:\n                    # Instanciate our own libusb_device_p object so we can free\n                    # libusb-provided device list. Is this a bug in ctypes that\n                    # it doesn't copy pointer value (=pointed memory address) ?\n                    # At least, it's not so convenient and forces using such\n                    # weird code.\n                    device = USBDevice(self, libusb_device_p(device_p.contents))\n                except USBError:\n                    if not skip_on_error:\n                        raise\n                else:\n                    self.__close_set.add(device)\n                    yield device\n        finally:\n            libusb1.libusb_free_device_list(device_p_p, 1)\n\n    def getDeviceList(self, skip_on_access_error=False, skip_on_error=False):\n        \"\"\"\n        Return a list of all USB devices currently plugged in, as USBDevice\n        instances.\n\n        skip_on_error (bool)\n            If True, ignore devices which raise USBError.\n\n        skip_on_access_error (bool)\n            DEPRECATED. Alias for skip_on_error.\n        \"\"\"\n        return list(\n            self.getDeviceIterator(\n                skip_on_error=skip_on_access_error or skip_on_error,\n            ),\n        )\n\n    def getByVendorIDAndProductID(\n            self, vendor_id, product_id,\n            skip_on_access_error=False, skip_on_error=False):\n        \"\"\"\n        Get the first USB device matching given vendor and product ids.\n        Returns an USBDevice instance, or None if no present device match.\n        skip_on_error (bool)\n            (see getDeviceList)\n        skip_on_access_error (bool)\n            (see getDeviceList)\n        \"\"\"\n        for device in self.getDeviceIterator(\n                skip_on_error=skip_on_access_error or skip_on_error,\n            ):\n            if device.getVendorID() == vendor_id and \\\n                    device.getProductID() == product_id:\n                return device\n\n    def openByVendorIDAndProductID(\n            self, vendor_id, product_id,\n            skip_on_access_error=False, skip_on_error=False):\n        \"\"\"\n        Get the first USB device matching given vendor and product ids.\n        Returns an USBDeviceHandle instance, or None if no present device\n        match.\n        skip_on_error (bool)\n            (see getDeviceList)\n        skip_on_access_error (bool)\n            (see getDeviceList)\n        \"\"\"\n        result = self.getByVendorIDAndProductID(\n            vendor_id, product_id,\n            skip_on_access_error=skip_on_access_error,\n            skip_on_error=skip_on_error)\n        if result is not None:\n            return result.open()\n\n    @_validContext\n    def getPollFDList(self):\n        \"\"\"\n        Return file descriptors to be used to poll USB events.\n        You should not have to call this method, unless you are integrating\n        this class with a polling mechanism.\n        \"\"\"\n        pollfd_p_p = libusb1.libusb_get_pollfds(self.__context_p)\n        if not pollfd_p_p:\n            errno = get_errno()\n            if errno:\n                raise OSError(errno)\n            else:\n                # Assume not implemented\n                raise NotImplementedError(\n                    'Your libusb does not seem to implement pollable FDs')\n        try:\n            result = []\n            append = result.append\n            fd_index = 0\n            while pollfd_p_p[fd_index]:\n                append((\n                    pollfd_p_p[fd_index].contents.fd,\n                    pollfd_p_p[fd_index].contents.events,\n                ))\n                fd_index += 1\n        finally:\n            _free(pollfd_p_p)\n        return result\n\n    @_validContext\n    def handleEvents(self):\n        \"\"\"\n        Handle any pending event (blocking).\n        See libusb1 documentation for details (there is a timeout, so it's\n        not \"really\" blocking).\n        \"\"\"\n        mayRaiseUSBError(\n            libusb1.libusb_handle_events(self.__context_p),\n        )\n\n    # TODO: handleEventsCompleted\n\n    @_validContext\n    def handleEventsTimeout(self, tv=0):\n        \"\"\"\n        Handle any pending event.\n        If tv is 0, will return immediately after handling already-pending\n        events.\n        Otherwise, defines the maximum amount of time to wait for events, in\n        seconds.\n        \"\"\"\n        if tv is None:\n            tv = 0\n        tv_s = int(tv)\n        real_tv = libusb1.timeval(tv_s, int((tv - tv_s) * 1000000))\n        mayRaiseUSBError(\n            libusb1.libusb_handle_events_timeout(\n                self.__context_p, byref(real_tv),\n            ),\n        )\n\n    # TODO: handleEventsTimeoutCompleted\n\n    @_validContext\n    def setPollFDNotifiers(\n            self, added_cb=None, removed_cb=None, user_data=None):\n        \"\"\"\n        Give libusb1 methods to call when it should add/remove file descriptor\n        for polling.\n        You should not have to call this method, unless you are integrating\n        this class with a polling mechanism.\n        \"\"\"\n        if added_cb is None:\n            added_cb = self.__null_pointer\n        else:\n            added_cb = libusb1.libusb_pollfd_added_cb_p(added_cb)\n        if removed_cb is None:\n            removed_cb = self.__null_pointer\n        else:\n            removed_cb = libusb1.libusb_pollfd_removed_cb_p(removed_cb)\n        if user_data is None:\n            user_data = self.__null_pointer\n        self.__added_cb = added_cb\n        self.__removed_cb = removed_cb\n        self.__poll_cb_user_data = user_data\n        self.__libusb_set_pollfd_notifiers(\n            self.__context_p, added_cb, removed_cb, user_data)\n\n    @_validContext\n    def getNextTimeout(self):\n        \"\"\"\n        Returns the next internal timeout that libusb needs to handle, in\n        seconds, or None if no timeout is needed.\n        You should not have to call this method, unless you are integrating\n        this class with a polling mechanism.\n        \"\"\"\n        timeval = libusb1.timeval()\n        result = libusb1.libusb_get_next_timeout(\n            self.__context_p, byref(timeval))\n        if result == 0:\n            return None\n        elif result == 1:\n            return timeval.tv_sec + (timeval.tv_usec * 0.000001)\n        raiseUSBError(result)\n\n    @_validContext\n    def setDebug(self, level):\n        \"\"\"\n        Set debugging level.\n        Note: depending on libusb compilation settings, this might have no\n        effect.\n        \"\"\"\n        libusb1.libusb_set_debug(self.__context_p, level)\n\n    @_validContext\n    def tryLockEvents(self):\n        \"\"\"\n        See libusb_try_lock_events doc.\n        \"\"\"\n        return libusb1.libusb_try_lock_events(self.__context_p)\n\n    @_validContext\n    def lockEvents(self):\n        \"\"\"\n        See libusb_lock_events doc.\n        \"\"\"\n        libusb1.libusb_lock_events(self.__context_p)\n\n    @_validContext\n    def lockEventWaiters(self):\n        \"\"\"\n        See libusb_lock_event_waiters doc.\n        \"\"\"\n        libusb1.libusb_lock_event_waiters(self.__context_p)\n\n    @_validContext\n    def waitForEvent(self, tv=0):\n        \"\"\"\n        See libusb_wait_for_event doc.\n        \"\"\"\n        if tv is None:\n            tv = 0\n        tv_s = int(tv)\n        real_tv = libusb1.timeval(tv_s, int((tv - tv_s) * 1000000))\n        libusb1.libusb_wait_for_event(self.__context_p, byref(real_tv))\n\n    @_validContext\n    def unlockEventWaiters(self):\n        \"\"\"\n        See libusb_unlock_event_waiters doc.\n        \"\"\"\n        libusb1.libusb_unlock_event_waiters(self.__context_p)\n\n    @_validContext\n    def eventHandlingOK(self):\n        \"\"\"\n        See libusb_event_handling_ok doc.\n        \"\"\"\n        return libusb1.libusb_event_handling_ok(self.__context_p)\n\n    @_validContext\n    def unlockEvents(self):\n        \"\"\"\n        See libusb_unlock_events doc.\n        \"\"\"\n        libusb1.libusb_unlock_events(self.__context_p)\n\n    @_validContext\n    def handleEventsLocked(self):\n        \"\"\"\n        See libusb_handle_events_locked doc.\n        \"\"\"\n        # XXX: does tv parameter need to be exposed ?\n        mayRaiseUSBError(libusb1.libusb_handle_events_locked(\n            self.__context_p, _zero_tv_p,\n        ))\n\n    @_validContext\n    def eventHandlerActive(self):\n        \"\"\"\n        See libusb_event_handler_active doc.\n        \"\"\"\n        return libusb1.libusb_event_handler_active(self.__context_p)\n\n    @staticmethod\n    def hasCapability(capability):\n        \"\"\"\n        Backward-compatibility alias for module-level hasCapability.\n        \"\"\"\n        return hasCapability(capability)\n\n    @_validContext\n    def hotplugRegisterCallback(\n            self, callback,\n            # pylint: disable=undefined-variable\n            events=HOTPLUG_EVENT_DEVICE_ARRIVED | HOTPLUG_EVENT_DEVICE_LEFT,\n            flags=HOTPLUG_ENUMERATE,\n            vendor_id=HOTPLUG_MATCH_ANY,\n            product_id=HOTPLUG_MATCH_ANY,\n            dev_class=HOTPLUG_MATCH_ANY,\n            # pylint: enable=undefined-variable\n        ):\n        \"\"\"\n        Registers an hotplug callback.\n        On success, returns an opaque value which can be passed to\n        hotplugDeregisterCallback.\n        Callback must accept the following positional arguments:\n        - this USBContext instance\n        - an USBDevice instance\n          If device has left, configuration descriptors may not be\n          available. Its device descriptor will be available.\n        - event type, one of:\n            HOTPLUG_EVENT_DEVICE_ARRIVED\n            HOTPLUG_EVENT_DEVICE_LEFT\n        Callback must return whether it must be unregistered (any true value\n        to be unregistered, any false value to be kept registered).\n        \"\"\"\n        def wrapped_callback(context_p, device_p, event, _):\n            assert addressof(context_p.contents) == addressof(\n                self.__context_p.contents), (context_p, self.__context_p)\n            device = USBDevice(\n                self,\n                device_p,\n                # pylint: disable=undefined-variable\n                event != HOTPLUG_EVENT_DEVICE_LEFT,\n                # pylint: enable=undefined-variable\n            )\n            self.__close_set.add(device)\n            unregister = bool(callback(\n                self,\n                device,\n                event,\n            ))\n            if unregister:\n                del self.__hotplug_callback_dict[handle]\n            return unregister\n        handle = c_int()\n        callback_p = libusb1.libusb_hotplug_callback_fn_p(wrapped_callback)\n        mayRaiseUSBError(libusb1.libusb_hotplug_register_callback(\n            self.__context_p, events, flags, vendor_id, product_id, dev_class,\n            callback_p, None, byref(handle),\n        ))\n        handle = handle.value\n        # Keep strong references\n        assert handle not in self.__hotplug_callback_dict, (\n            handle,\n            self.__hotplug_callback_dict,\n        )\n        self.__hotplug_callback_dict[handle] = (callback_p, wrapped_callback)\n        return handle\n\n    @_validContext\n    def hotplugDeregisterCallback(self, handle):\n        \"\"\"\n        Deregisters an hotplug callback.\n        handle (opaque)\n            Return value of a former hotplugRegisterCallback call.\n        \"\"\"\n        del self.__hotplug_callback_dict[handle]\n        libusb1.libusb_hotplug_deregister_callback(self.__context_p, handle)\n\ndel USBContext._validContext\n\ndef getVersion():\n    \"\"\"\n    Returns underlying libusb's version information as a 6-namedtuple (or\n    6-tuple if namedtuples are not avaiable):\n    - major\n    - minor\n    - micro\n    - nano\n    - rc\n    - describe\n    Returns (0, 0, 0, 0, '', '') if libusb doesn't have required entry point.\n    \"\"\"\n    version = libusb1.libusb_get_version().contents\n    return Version(\n        version.major,\n        version.minor,\n        version.micro,\n        version.nano,\n        version.rc,\n        version.describe,\n    )\n\ndef hasCapability(capability):\n    \"\"\"\n    Tests feature presence.\n\n    capability should be one of:\n        CAP_HAS_CAPABILITY\n        CAP_HAS_HOTPLUG\n        CAP_HAS_HID_ACCESS\n        CAP_SUPPORTS_DETACH_KERNEL_DRIVER\n    \"\"\"\n    return libusb1.libusb_has_capability(capability)\n\nclass LibUSBContext(USBContext):\n    \"\"\"\n    Backward-compatibility alias for USBContext.\n    \"\"\"\n    def __init__(self):\n        warnings.warn(\n            'LibUSBContext is being renamed to USBContext',\n            DeprecationWarning,\n        )\n        super(LibUSBContext, self).__init__()\n"
  },
  {
    "path": "scc/lib/vdf.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nVDF file reader\nCopyright (C) 2017 Kozec\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License version 2 as published by\nthe Free Software Foundation\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along\nwith this program; if not, write to the Free Software Foundation, Inc.,\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\"\"\"\nimport os, sys, importlib\nimport vdf\n\ndef parse_vdf(file):\n\treturn vdf.parse(file, mapper=vdf.VDFDict, merge_duplicate_keys=False)\n\n\ndef ensure_list(value):\n\t\"\"\"\n\tIf value is list, returns same value.\n\tOtherwise, returns [ value ]\n\t\"\"\"\n\treturn value if type(value) == list else [ value ]\n\n\nif __name__ == \"__main__\":\n\tprint(parse_vdf(open('test.vdf', \"r\")))\n"
  },
  {
    "path": "scc/lib/xinput.py",
    "content": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\"\"\"\nXInput tools\n\nInterfaces with XInput by calling `xinput` command.\nCurrently allows only querying list of xinput devices and floating them.\n\nCopyright (C) 2017 Kozec\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License version 2 as published by\nthe Free Software Foundation\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along\nwith this program; if not, write to the Free Software Foundation, Inc.,\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport logging, re, subprocess\nlog = logging.getLogger(\"XI\")\n\nRE_DEVICE = re.compile(r\"[ \\t⎜]+↳ (.*)\\tid=([0-9]+)[ \\t]+\\[([a-z ]+)\")\n\ndef get_devices():\n\t\"\"\"\n\tReturns list of devices reported by xinput.\n\t\"\"\"\n\trv = []\n\ttry:\n\t\tlst = (subprocess.Popen([ \"xinput\" ], stdout=subprocess.PIPE, stdin=None)\n\t\t\t.communicate()[0]\n\t\t\t.decode(\"utf-8\"))\n\texcept:\n\t\t# calling xinput failed, return empty list\n\t\treturn rv\n\t\n\tfor line in lst.split(\"\\n\"):\n\t\tmatch = RE_DEVICE.match(line)\n\t\tif match:\n\t\t\tname, id, type = match.groups()\n\t\t\tname = name.strip(\" \\t\")\n\t\t\twhile \"  \" in type:\n\t\t\t\ttype = type.replace(\"  \", \" \")\n\t\t\tid = int(id)\n\t\t\trv.append(XIDevice(id, name, type))\n\treturn rv\n\n\nclass XIDevice(object):\n\tdef __init__(self, id, name, type):\n\t\tself._id = id\n\t\tself._name = name\n\t\tself._type = type\n\t\n\t\n\tdef float(self):\n\t\t\"\"\" Removes slave device from its current master \"\"\"\n\t\tsubprocess.Popen([ \"xinput\", \"float\", str(self._id) ])\n\t\tlog.info(\"Deatached device %s from its master\", self._id)\n\t\n\t\n\tdef get_name(self):\n\t\treturn self._name\n\t\n\t\n\tdef is_pointer(self):\n\t\t\"\"\" Returns True if device is pointer, ie can controll mouse \"\"\"\n\t\treturn \"pointer\" in self._type\n\t\n\t\n\tdef is_slave(self):\n\t\t\"\"\" Returns True if device is slave pointer or slave keyboard \"\"\"\n\t\treturn \"slave\" in self._type\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<XIDevice #%s '%s' (%s)>\" % (self._id, self._name, self._type)\n\t\n\t__repr__ = __str__\n"
  },
  {
    "path": "scc/lib/xwrappers.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nPython wrapper for some X-related stuff.\n\nCopyright (C) 2017 Kozec\n\nThis program is free software; you can redistribute it and/or modify\nit under the terms of the GNU General Public License version 2 as published by\nthe Free Software Foundation\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License along\nwith this program; if not, write to the Free Software Foundation, Inc.,\n51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\"\"\"\n\nfrom ctypes import CDLL, POINTER, c_void_p, Structure, byref, cast\nfrom ctypes import c_long, c_ulong, c_int, c_uint, c_short\nfrom ctypes import c_ushort, c_ubyte, c_char_p, c_bool\n\n\ndef _load_lib(*names):\n\t\"\"\"\n\tTries multiple alternative names to load .so library.\n\t\"\"\"\n\tfor l in names:\n\t\ttry:\n\t\t\treturn CDLL(l)\n\t\texcept OSError:\n\t\t\tpass\n\traise OSError(\"Failed to load %s, library not found\" % (names[0],))\n\n\nlibXFixes = _load_lib('libXfixes.so', 'libXfixes.so.3')\nlibX11 = _load_lib('libX11.so', 'libX11.so.6')\nlibXext = _load_lib('libXext.so', 'libXext.so.6')\n\n\n# Types\nXID = c_ulong\nPixmap = XID\nColormap = XID\nAtom = c_ulong\nXserverRegion = c_ulong\nGC = c_void_p\nDisplay = c_void_p\n\n# Structures\nclass XRectangle(Structure):\n\t_fields_ = [\n\t\t('x', c_short),\n\t\t('y', c_short),\n\t\t('width', c_ushort),\n\t\t('height', c_ushort),\n\t]\n\nclass XClassHint(Structure):\n\t_fields_ = [\n\t\t('res_name', c_char_p),\n\t\t('res_class', c_char_p),\n\t]\n\nclass XkbStateRec(Structure):\n\t_fields_ = [\n\t\t('group', c_ubyte),\n\t\t('locked_group', c_ubyte),\n\t\t('base_group', c_ushort),\n\t\t('latched_group', c_ushort),\n\t\t('mods', c_ubyte),\n\t\t('base_mods', c_ubyte),\n\t\t('latched_mods', c_ubyte),\n\t\t('locked_mods', c_ubyte),\n\t\t('compat_state', c_ubyte),\n\t\t('grab_mods', c_ubyte),\n\t\t('compat_grab_mods', c_ubyte),\n\t\t('lookup_mods', c_ubyte),\n\t\t('compat_lookup_mods', c_ubyte),\n\t\t('ptr_buttons', c_ushort),\n\t]\n\nclass XWindowAttributes(Structure):\n\t_fields_ = [\n\t\t('x', c_int),\n\t\t('y', c_int),\n\t\t('width', c_int),\n\t\t('height', c_int),\n\t\t('depth', c_int),\n\t\t('visual', c_void_p),\n\t\t('root', XID),\n\t\t('i_class', c_int),\n\t\t('bit_gravity', c_int),\n\t\t('win_gravity', c_int),\n\t\t('backing_store', c_int),\n\t\t('backing_planes', c_ulong),\n\t\t('backing_pixel', c_ulong),\n\t\t('save_under', c_bool),\n\t\t('colormap', Colormap),\n\t\t('map_installed', c_bool),\n\t\t('map_state', c_int),\n\t\t('all_event_masks', c_long),\n\t\t('your_event_mask', c_long),\n\t\t('do_not_propagate_mask', c_long),\n\t\t('map_installed', c_bool),\n\t\t('screen', c_void_p)\n\t]\n\n\n# Consants\nSHAPE_BOUNDING\t= 0\nSHAPE_CLIP\t\t= 1\nSHAPE_INPUT\t\t= 2\nSHAPE_SET\t\t= 0\n\nXKBUSECOREKBD\t= 0x0100\nANYPROPERTYTYPE\t= 0\nSUCCESS\t\t\t= 0\n\nISVIEWABLE\t\t= 2\n\n\n# Functions\nopen_display = libX11.XOpenDisplay\nopen_display.__doc__ = \"Opens connection to XDisplay\"\nopen_display.argtypes = [ c_char_p ]\nopen_display.restype = c_void_p\n\nfree = libX11.XFree\nfree.__doc__ = \"Used to free some resource returned by XLib\"\nfree.argtypes = [ c_void_p ]\n\ncreate_region = libXFixes.XFixesCreateRegion\ncreate_region.__doc__ = \"Creates rectanglular region for use with set_window_shape_region\"\ncreate_region.argtypes = [ c_void_p, POINTER(XRectangle), c_int ]\ncreate_region.restype = XserverRegion\n\nset_window_shape_region = libXFixes.XFixesSetWindowShapeRegion\nset_window_shape_region.__doc__ = \"Sets region in which window accepts inputs\"\nset_window_shape_region.argtypes = [ c_void_p, XID, c_int, c_int, c_int, XserverRegion ]\n\ndestroy_region = libXFixes.XFixesDestroyRegion\ndestroy_region.__doc__ = \"Frees region created by create_region\"\ndestroy_region.argtypes = [ c_void_p, XserverRegion ]\n\nget_default_root_window = libX11.XDefaultRootWindow\nget_default_root_window.argtypes = [ c_void_p ]\n\nflush = libX11.XFlush\nflush.__doc__ = \"Asks Xlib to send queued commands to XServer\"\nflush.argtypes = [ c_void_p ]\n\nwarp_pointer = libX11.XWarpPointer\nwarp_pointer.__doc__ = \"Very, very, V*E*R*Y complicated shit used to move cursor\"\nwarp_pointer.argtypes = [ c_void_p, XID, XID, c_int, c_int, c_int, c_int, c_int, c_int ]\n\nquery_pointer = libX11.XQueryPointer\nquery_pointer.__doc__ = \"Returns a lot of nonsense along with mouse cursor position\"\nquery_pointer.argtypes = [ c_void_p, XID, POINTER(XID), POINTER(XID),\n\tPOINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_int), POINTER(c_uint) ]\n\nget_window_attributes = libX11.XGetWindowAttributes\nget_window_attributes.__doc__ = \"https://tronche.com/gui/x/xlib/window-information/XGetWindowAttributes.html\"\nget_window_attributes.argtypes = [ c_void_p, XID, POINTER(XWindowAttributes) ]\n\n\ntranslate_coordinates = libX11.XTranslateCoordinates\ntranslate_coordinates.argtypes = [ c_void_p, XID, XID, c_int, c_int,\n\tPOINTER(c_int), POINTER(c_int), POINTER(XID) ]\ntranslate_coordinates.restype = c_bool\n\n\nget_input_focus = libX11.XGetInputFocus\nget_input_focus.__doc__ = \"\"\"Returns window that currently have window focus.\n\tMost of window managers and some GTK applications are breaking this.\n\tSee https://specifications.freedesktop.org/wm-spec/1.3/ar01s03.html\n\t\"\"\"\nget_input_focus.argtypes = [ c_void_p, POINTER(XID), POINTER(c_int) ]\n\nget_window_property = libX11.XGetWindowProperty\nget_window_property.__doc__ = \"Returns value of property associated with window\"\nget_window_property.argtypes = [ c_void_p, XID, Atom, c_long, c_long, c_bool,\n\tAtom, POINTER(Atom), POINTER(Atom), POINTER(c_ulong), POINTER(c_ulong),\n\tPOINTER(c_void_p) ]\nget_window_property.restype = c_int\n\nalloc_class_hint = libX11.XAllocClassHint\nalloc_class_hint.restype = POINTER(XClassHint)\nalloc_class_hint.argtypes = []\nalloc_class_hint.__doc__ = \t\"\"\"Allocates and returns a pointer to a XClassHint\n\tstructure. Returned pointer has to be deallocated using free()\"\"\"\n\nget_class_hint = libX11.XGetClassHint\nget_class_hint.argtypes = [ c_void_p, XID, POINTER(XClassHint) ]\nget_class_hint.restype = c_int\n\nintern_atom = libX11.XInternAtom\nintern_atom.__doc__ = \"Returns integer ID for specified Atom name.\"\nintern_atom.argtypes = [ c_void_p, c_char_p, c_bool ]\nintern_atom.restype = Atom\n\ncreate_pixmap = libX11.XCreatePixmap\ncreate_pixmap.argtypes = [ c_void_p, XID, c_uint, c_uint, c_uint ]\ncreate_pixmap.restype = Pixmap\n\ncreate_pixmap_from_bitmap = libX11.XCreatePixmapFromBitmapData\ncreate_pixmap_from_bitmap.argtypes = [ c_void_p, XID, c_char_p, c_uint, c_uint, c_uint, c_uint, c_uint ]\ncreate_pixmap_from_bitmap.restype = Pixmap\n\nwrite_bitmap = libX11.XWriteBitmapFile\nwrite_bitmap.argtypes = [ c_void_p, c_char_p, Pixmap, c_uint, c_uint, c_int, c_int ]\nwrite_bitmap.restype = c_int\n\nfree_pixmap = libX11.XFreePixmap\nfree_pixmap.__doc__ = \"Deallocates pixmap created by create_pixmap\"\nfree_pixmap.argtypes = [ c_void_p, Pixmap ]\n\ncreate_gc = libX11.XCreateGC\ncreate_gc.__doc__ = \"Creates graphics context to draw on\"\ncreate_gc.argtypes = [ c_void_p, XID, c_ulong, c_void_p ]\ncreate_gc.restype = GC\n\nflush_gc = libX11.XFlushGC\nflush_gc.__doc__ = \"Force sending GC component changes\"\nflush_gc.argtypes = [ c_void_p, GC ]\n\nfree_gc = libX11.XFreeGC\nfree_gc.__doc__ = \"Deallocates graphics context created by create_gc\"\nfree_gc.argtypes = [ c_void_p, GC ]\n\nfill_rectangle = libX11.XFillRectangle\nfill_rectangle.__doc__ = \"Draws and fills rectangle on graphics context\"\nfill_rectangle.argtypes = [ c_void_p, XID, GC, c_int, c_int, c_uint, c_uint ]\n\ndraw_arc = libX11.XDrawArc\ndraw_arc.argtypes = [ c_void_p, Pixmap, GC, c_int, c_int, c_uint, c_uint, c_int, c_int ]\n\nfill_arc = libX11.XFillArc\nfill_arc.argtypes = [ c_void_p, Pixmap, GC, c_int, c_int, c_uint, c_uint, c_int, c_int ]\n\n\nset_foreground = libX11.XSetForeground\nset_foreground.__doc__ = \"Sets foreground color for drawing on graphics context\"\nset_foreground.argtypes = [ c_void_p, GC, c_ulong ]\n\nset_background = libX11.XSetBackground\nset_background.__doc__ = \"Sets background color for drawing on graphics context\"\nset_background.argtypes = set_foreground.argtypes\n\nshape_combine_mask = libXext.XShapeCombineMask\nshape_combine_mask.__doc__ = \"Sets 1-bit transparency mask for window\"\nshape_combine_mask.argtypes = [ c_void_p, XID, c_int, c_int, c_int, Pixmap, c_int ]\n\n\n\n# Wrapped functions\n_xkb_get_state = libX11.XkbGetState\n_xkb_get_state.argtypes = [c_void_p, c_uint, POINTER(XkbStateRec)]\n\n# Wrappers\ndef get_xkb_state(dpy):\n\trec = XkbStateRec()\n\t_xkb_get_state(dpy, XKBUSECOREKBD, rec)\n\treturn rec\n\n\ndef get_window_size(dpy, window):\n\tattrs = XWindowAttributes()\n\tget_window_attributes(dpy, window, byref(attrs))\n\treturn attrs.width, attrs.height\n\n\ndef is_window_visible(dpy, window):\n\t\"\"\" Return True if window mapping state is IsViewable \"\"\"\n\tattrs = XWindowAttributes()\n\tget_window_attributes(dpy, window, byref(attrs))\n\treturn attrs.map_state == ISVIEWABLE\n\n\ndef get_window_geometry(dpy, win):\n\t\"\"\" Returns window x,y,width,height \"\"\"\n\tattrs = XWindowAttributes()\n\tget_window_attributes(dpy, win, byref(attrs))\n\tx, y = c_int(), c_int()\n\ttrash = XID()\n\tif translate_coordinates(dpy, win, get_default_root_window(dpy),\n\t\t\t0, 0, byref(x), byref(y), byref(trash)):\n\t\treturn x.value, y.value, attrs.width, attrs.height\n\telse:\n\t\t# translate_coordinates failed\n\t\treturn attrs.x, attrs.y, attrs.width, attrs.height\n\n\ndef get_screen_size(dpy):\n\treturn get_window_size(dpy, get_default_root_window(dpy))\n\n\ndef get_mouse_pos(dpy, relative_to=None):\n\t\"\"\"\n\tReturns mouse position relative to specified window or to screen, if no\n\twindow is specified.\n\t\"\"\"\n\tif relative_to is None:\n\t\trelative_to = get_default_root_window(dpy)\n\troot_return, child = XID(), XID()\n\tx, y = c_int(), c_int()\n\tchild_x, child_y = c_int(), c_int()\n\tmask = c_uint()\n\t\n\tquery_pointer(dpy, relative_to, byref(root_return), byref(child),\n\t\tbyref(x), byref(y),\n\t\tbyref(child_x), byref(child_y), byref(mask))\n\treturn x.value, y.value\n\n\ndef set_mouse_pos(dpy, x, y, relative_to=None):\n\t\"\"\"\n\tSets mouse position relative to specified window or to screen, if no\n\twindow is specified.\n\t\"\"\"\n\tif relative_to is None:\n\t\trelative_to = get_default_root_window(dpy)\n\twarp_pointer(dpy, 0, relative_to, 0, 0, 0, 0, x, y)\n\tflush(dpy)\n\n\ndef get_window_prop(dpy, window, prop_name, max_size=2):\n\t\"\"\"\n\tReturns (nitems, property) of specified window or (-1, None) if anything fails.\n\tReturned 'property' is POINTER(c_void_p) and has to be freed using X.free().\n\t\"\"\"\n\tif type(prop_name) is str:\n\t\tprop_name = prop_name.encode(\"utf-8\")\n\n\tprop_atom = intern_atom(dpy, prop_name, False)\n\ttype_return, format_return = Atom(), Atom()\n\tnitems, bytes_after = c_ulong(), c_ulong()\n\tprop = c_void_p()\n\t\n\tif SUCCESS == get_window_property(dpy, window,\n\t\t\t\tprop_atom, 0, max_size, False, ANYPROPERTYTYPE,\n\t\t\t\tbyref(type_return), byref(format_return), byref(nitems),\n\t\t\t\tbyref(bytes_after), byref(prop)):\n\t\treturn nitems.value, prop\n\treturn -1, None\n\n\ndef get_current_window(dpy):\n\t\"\"\"\n\tReturns active window or root window if there is no active.\n\t\"\"\"\n\t# Try using WM-provided info first\n\ttrash, prop = get_window_prop(dpy,\n\t\t\tget_default_root_window(dpy), \"_NET_ACTIVE_WINDOW\")\n\tif prop is not None:\n\t\trv = cast(prop, POINTER(Atom)).contents.value\n\t\tfree(prop)\n\t\treturn rv\n\t\n\t# Fall-back to something what probably can't work anyway\n\twin, revert_to = XID(), c_int()\n\tget_input_focus(dpy, byref(win), byref(revert_to))\n\tif win == 0:\n\t\treturn get_default_root_window(dpy)\n\treturn win\n\n\ndef get_window_type(dpy, window):\n\t\"\"\"\n\tReturns _NET_WM_WINDOW_TYPE value for window specified or None if anything\n\tfails while recieving it.\n\t\"\"\"\n\ttrash, prop = get_window_prop(dpy, window, \"_NET_WM_WINDOW_TYPE\")\n\tif prop is not None and prop.value is not None:\n\t\trv = cast(prop, POINTER(Atom)).contents.value\n\t\tfree(prop)\n\t\treturn rv\n\treturn None\n\n\ndef get_window_title(dpy, window):\n\t\"\"\"\n\tReturns window title or None if title cannot be obtained.\n\t\"\"\"\n\tfor prop_name in (\"_NET_WM_NAME\", \"WM_NAME\"):\n\t\ttrash, prop = get_window_prop(dpy, window, prop_name, max_size=2048)\n\t\tif prop:\n\t\t\ttry:\n\t\t\t\tvalue = cast(prop, c_char_p).value.decode('utf-8')\n\t\t\t\tfree(prop)\n\t\t\t\treturn value\n\t\t\texcept: pass\n\t\t\tfree(prop)\n\treturn None\n\n\ndef get_window_class(dpy, window):\n\t\"\"\"\n\tReturns window class or None, None if class cannot be obtained.\n\t\"\"\"\n\ts = alloc_class_hint()\n\tif s:\n\t\tif get_class_hint(dpy, window, s):\n\t\t\tvalue = s.contents.res_name.decode('utf-8'), s.contents.res_class.decode('utf-8')\n\t\t\tfree(s)\n\t\t\treturn value\n\t\tfree(s)\n\t\n\treturn None, None\n\n\ndef get_wm_state(dpy, window):\n\t\"\"\"\n\tReturns list of _NET_WM_STATE atoms assotiated with window or empty list\n\tif list be obtained.\n\t\"\"\"\n\tcount, state = get_window_prop(dpy, window, \"_NET_WM_STATE\", 1024)\n\tif count <= 0: return []\n\treturn cast(state, POINTER(Atom))[0:count]\n"
  },
  {
    "path": "scc/macros.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Macros\n\nFrontier is my favorite.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.actions import Action, NoAction, ButtonAction, MOUSE_BUTTONS\nfrom scc.constants import FE_STICK, FE_TRIGGER, FE_PAD\nfrom scc.constants import LEFT, RIGHT, STICK, SCButtons\nfrom scc.uinput import Keys\n\n\nimport time, logging\nlog = logging.getLogger(\"Macros\")\n_ = lambda x : x\n\n\nclass Macro(Action):\n\t\"\"\"\n\tTwo or more actions executed in sequence.\n\tGenerated when parsing ';'\n\t\"\"\"\n\n\tCOMMAND = None\n\tHOLD_TIME = 0.01\n\t\n\tdef __init__(self, *parameters):\n\t\tAction.__init__(self, *parameters)\n\t\tself.actions = []\n\t\tself.repeat = False\n\t\tself.hold_time = Macro.HOLD_TIME\n\t\tself._active = False\n\t\tself._current = None\n\t\tself._release = None\n\t\tfor p in parameters:\n\t\t\tif type(p) == float and len(self.actions):\n\t\t\t\tself.actions[-1].delay_after = p\n\t\t\telif isinstance(p, Macro):\n\t\t\t\tself.actions += p.actions\n\t\t\telif isinstance(p, Action):\n\t\t\t\tself.actions.append(p)\n\t\t\telse:\n\t\t\t\tself.actions.append(ButtonAction(p))\n\t\n\t\n\tdef button_press(self, mapper):\n\t\t# Macro can be executed only by pressing button\n\t\tif len(self.actions) < 1:\n\t\t\t# Empty macro\n\t\t\treturn False\n\t\tself._active = True\n\t\tif self._current is not None:\n\t\t\t# Already executing macro\n\t\t\treturn False\n\t\tself._current = [] + self.actions\n\t\tself.timer(mapper)\n\t\n\t\n\tdef timer(self, mapper):\n\t\tif self._release is None:\n\t\t\t# Execute next action\n\t\t\tself._release, self._current = self._current[0], self._current[1:]\n\t\t\tself._release.button_press(mapper)\n\t\t\tmapper.schedule(self.hold_time, self.timer)\n\t\telse:\n\t\t\t# Finish execited action\n\t\t\tself._release.button_release(mapper)\n\t\t\tif len(self._current) == 0 and self.repeat and self._active:\n\t\t\t\t# Repeating\n\t\t\t\tself._current = [] + self.actions\n\t\t\t\tmapper.schedule(self._release.delay_after, self.timer)\n\t\t\t\tself._release = None\n\t\t\telif len(self._current) == 0:\n\t\t\t\t# Finished\n\t\t\t\tself._current = None\n\t\t\t\tself._release = None\n\t\t\telse:\n\t\t\t\t# Schedule for next action\n\t\t\t\tmapper.schedule(self._release.delay_after, self.timer)\n\t\t\t\tself._release = None\n\t\n\t\n\tdef cancel(self, mapper):\n\t\tfor a in self.actions:\n\t\t\ta.cancel(mapper)\n\t\n\t\n\tdef set_haptic(self, hapticdata):\n\t\tfor a in self.actions:\n\t\t\tif a and hasattr(a, \"set_haptic\"):\n\t\t\t\ta.set_haptic(hapticdata)\n\t\n\t\n\tdef get_haptic(self):\n\t\tfor a in self.actions:\n\t\t\tif a and hasattr(a, \"set_haptic\"):\n\t\t\t\treturn a.get_haptic()\n\t\treturn None\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tfor a in self.actions:\n\t\t\tif hasattr(a, \"set_speed\"):\n\t\t\t\ta.set_speed(x, y, z)\n\t\n\t\n\tdef get_speed(self):\n\t\tfor a in self.actions:\n\t\t\tif hasattr(a, \"set_speed\"):\n\t\t\t\treturn a.get_speed()\n\t\treturn (1.0,)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tself._active = False\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif self.repeat:\n\t\t\treturn \"repeat \" + \"; \".join([ x.describe(context) for x in self.actions ])\n\t\treturn \"; \".join([ x.describe(context) for x in self.actions ])\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tlst = \"; \".join([ x.to_string() for x in self.actions ])\n\t\tif self.repeat:\n\t\t\treturn (\" \" * pad) + (\"repeat(%s)\" % (lst,))\n\t\treturn (\" \" * pad) + lst\n\t\n\t\n\tdef __str__(self):\n\t\tif self.repeat:\n\t\t\treturn \"<[repeat %s ]>\" % (\"; \".join([ str(x) for x in self.actions ]), )\n\t\treturn \"<[ %s ]>\" % (\"; \".join([ str(x) for x in self.actions ]), )\n\t\n\t__repr__ = __str__\n\n\nclass Type(Macro):\n\t\"\"\"\n\tSpecial type of Macro where keys to press are specified as string.\n\tBasically, writing type(\"iddqd\") is same thing as\n\tbutton(KEY_I) ; button(KEY_D) ; button(KEY_D); button(KEY_Q); button(KEY_D)\n\t\n\tRecognizes only lowercase letters, uppercase letters, numbers and space.\n\tAdding anything else will make action unparseable.\n\t\"\"\"\n\tCOMMAND = \"type\"\n\tHOLD_TIME = 0.001\n\n\tdef __init__(self, string):\n\t\tparams = []\n\t\tshift = False\n\t\tfor letter in string:\n\t\t\tif (letter >= 'a' and letter <= 'z') or (letter >= '0' and letter <= '9'):\n\t\t\t\tif hasattr(Keys, (\"KEY_\" + letter).upper()):\n\t\t\t\t\tif shift:\n\t\t\t\t\t\tparams.append(ReleaseAction(Keys.KEY_LEFTSHIFT))\n\t\t\t\t\t\tshift = False\n\t\t\t\t\tparams.append(ButtonAction(getattr(Keys, (\"KEY_\" + letter).upper())))\n\t\t\t\t\tcontinue\n\t\t\tif letter == ' ':\n\t\t\t\tparams.append(ButtonAction(Keys.KEY_SPACE))\n\t\t\t\tcontinue\n\t\t\tif letter >= 'A' and letter <= 'Z':\n\t\t\t\tif hasattr(Keys, \"KEY_\" + letter):\n\t\t\t\t\tif not shift:\n\t\t\t\t\t\tparams.append(PressAction(Keys.KEY_LEFTSHIFT))\n\t\t\t\t\t\tshift = True\n\t\t\t\t\tparams.append(ButtonAction(getattr(Keys, \"KEY_\" + letter)))\n\t\t\t\t\tcontinue\n\t\t\traise ValueError(\"Invalid character for type(): '%s'\" % (letter,))\n\t\tMacro.__init__(self, *params)\n\t\tself.letters = string\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + self.COMMAND + \"(\" + repr(self.letters).strip(\"u\") + \")\"\n\n\nclass Cycle(Macro):\n\t\"\"\"\n\tMultiple actions cycling on same button.\n\tWhen button is pressed 1st time, 1st action is executed. 2nd action is\n\texecuted for 2nd press et cetera et cetera.\n\t\"\"\"\n\n\tCOMMAND = 'cycle'\n\t\n\tdef __init__(self, *parameters):\n\t\tAction.__init__(self, *parameters)\n\t\tself.actions = parameters\n\t\tself._current = 0\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tif len(self.actions) > 0:\n\t\t\tself.actions[self._current].button_press(mapper)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tif len(self.actions) > 0:\n\t\t\tself.actions[self._current].button_release(mapper)\n\t\t\tself._current += 1\n\t\t\tif self._current >= len(self.actions):\n\t\t\t\tself._current = 0\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Cycle Actions\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tlst = \", \".join([ x.to_string() for x in self.actions ])\n\t\treturn (\" \" * pad) + self.COMMAND + \"(\" + lst + \")\"\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<cycle %s >\" % (\"; \".join([ str(x) for x in self.actions ]), )\n\t\n\t__repr__ = __str__\n\n\nclass Repeat(Macro):\n\t\"\"\"\n\tRepeats specified action as long as physical button is pressed.\n\tThis is actually just Macro with 'repeat' set to True\n\t\"\"\"\n\tCOMMAND = \"repeat\"\n\tdef __new__(cls, action):\n\t\tif not isinstance(action, Macro):\n\t\t\taction = Macro(action)\n\t\taction.repeat = True\n\t\treturn action\n\n\nclass SleepAction(Action):\n\t\"\"\"\n\tDoes nothing.\n\tIf used in macro, overrides delay after itself.\n\t\"\"\"\n\tCOMMAND = \"sleep\"\n\tdef __init__(self, delay):\n\t\tAction.__init__(self, delay)\n\t\tself.delay = float(delay)\n\t\tself.delay_after = self.delay - Macro.HOLD_TIME\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif self.delay < 1.0:\n\t\t\treturn _(\"Wait %sms\") % (int(self.delay*1000),)\n\t\telse:\n\t\t\ts = (\"%0.2f\" % (self.delay,)).strip(\".0\")\n\t\t\treturn _(\"Wait %ss\") % (s,)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"%s(%0.3f)\" % (self.COMMAND, self.delay)\n\n\t\n\tdef button_press(self, mapper): pass\n\tdef button_release(self, mapper): pass\n\n\nclass PressAction(Action):\n\t\"\"\"\n\tPresses button and leaves it pressed.\n\tCan be used anywhere, but makes sense only with macro.\n\t\"\"\"\n\tCOMMAND = \"press\"\n\tPR = _(\"Press\")\n\n\tdef __init__(self, action):\n\t\tAction.__init__(self, action)\n\t\tself.action = action\n\t\n\t\n\tdef describe_short(self):\n\t\t\"\"\" Used in macro editor \"\"\"\n\t\tif isinstance(self.action, ButtonAction):\n\t\t\treturn self.action.describe_short()\n\t\tif isinstance(self.action, Keys):\n\t\t\treturn ButtonAction.describe_button(self.action)\n\t\treturn self.action.describe(Action.AC_BUTTON)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn self.PR + \" \" + self.describe_short()\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.action.button_press(mapper)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\t# This is activated only when button is pressed\n\t\tpass\n\n\nclass ReleaseAction(PressAction):\n\t\"\"\"\n\tReleases button.\n\tCan be used anywhere, but makes sense only with macro.\n\t\"\"\"\n\tCOMMAND = \"release\"\n\tPR = _(\"Release\")\n\t\n\tdef button_press(self, mapper):\n\t\tself.action.button_release(mapper)\n\n\nclass TapAction(PressAction):\n\t\"\"\"\n\tPresses button for short time.\n\tIf button is already pressed, generates release-press-release-press\n\tevents in quick sequence.\n\t\"\"\"\n\tCOMMAND = \"tap\"\n\tPR = _(\"Tap\")\n\tPAUSE = 0.1\n\tCOUNTER_VAL = 100\n\t\n\tdef __init__(self, button, count=1):\n\t\tPressAction.__init__(self, button)\n\t\tself._lst = []\n\t\tself._keep_pressed = False\n\t\tself.button = button\n\t\tself.count = count\n\n\t\n\tdef button_press(self, mapper):\n\t\tif len(self._lst):\n\t\t\t# Still executing from scheduler\n\t\t\treturn\n\t\t\n\t\t# ---\n\t\t# This thing abuses internal \"button press\" counter a little; First,\n\t\t# if button is supposedly pressed more than 1 times (because two or more\n\t\t# actions are holding it down at same time), tap is aborted. That is not\n\t\t# ideal, but shouldn't be a problem in most cases.\n\t\t#\n\t\t# 2nd, counter is manipulated before every call to _button_press / release,\n\t\t# so it always fires emulated button. Then, while scheduler is active,\n\t\t# counter is bumped to 'COUNTER_VAL', so it can be detected that other\n\t\t# action touched same button.\n\t\t# ---\n\t\tif self.button in mapper.pressed and mapper.pressed[self.button] > 1:\n\t\t\tlog.warning(\"Failed to tap, two or more actions are holding button\")\n\t\t\treturn\n\t\t\t\n\t\t# Generate as many clicks as requested\n\t\t# True is for press, False for release\n\t\tself._lst = [ True, False ] * self.count\n\t\t\n\t\tif self.button in mapper.pressed and mapper.pressed[self.button] > 0:\n\t\t\t# Surround by release - ... - press if button is currently pressed\n\t\t\tself._lst = [ False ] + self._lst + [ True ]\n\t\telif self.count > 1:\n\t\t\t# Keep button pressed if double-or-more tap was requested\n\t\t\tself._lst = self._lst[0:-1]\n\t\t\tself._keep_pressed = True\n\t\t\n\t\tmapper.pressed[self.button] = self.COUNTER_VAL\n\t\tself._rel_tap_press(mapper)\n\t\n\t\n\tdef _bailout(self):\n\t\tself._lst, self._keep_pressed = [], None\n\t\treturn None\n\t\n\t\n\tdef _rel_tap_press(self, mapper):\n\t\tif not self.button in mapper.pressed or mapper.pressed[self.button] < self.COUNTER_VAL:\n\t\t\t# Something else tried to _release_ button in meanwhile, bail out\n\t\t\tmapper.pressed[self.button] = 1\n\t\t\tButtonAction._button_release(mapper, self.button)\n\t\t\treturn self._bailout()\n\t\telif mapper.pressed[self.button] > self.COUNTER_VAL:\n\t\t\t# Something else pressed button in meanwhile, bail out\n\t\t\tmapper.pressed[self.button] = 1\n\t\t\treturn self._bailout()\n\t\t\n\t\ta, self._lst = self._lst[0], self._lst[1:]\n\t\tif a:\n\t\t\tmapper.pressed[self.button] = 0\n\t\t\tButtonAction._button_press(mapper, self.button)\n\t\telse:\n\t\t\tmapper.pressed[self.button] = 1\n\t\t\tButtonAction._button_release(mapper, self.button)\n\t\tif len(self._lst):\n\t\t\tmapper.pressed[self.button] = self.COUNTER_VAL\n\t\t\tmapper.schedule(self.PAUSE, self._rel_tap_press)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tif self._keep_pressed:\n\t\t\tself._keep_pressed = False\n\t\t\tif len(self._lst) > 0:\n\t\t\t\t# _rel_tap_press is still scheduled\n\t\t\t\tself._lst += [ False ]\n\t\t\telse:\n\t\t\t\tButtonAction._button_release(mapper, self.button)\n\t\n\t\n\tdef describe_short(self):\n\t\t\"\"\" Used in macro editor \"\"\"\n\t\tif self.count <= 1:\n\t\t\treturn \"%s %s\" % (_(\"Tap\"), ButtonAction.describe_button(self.button))\n\t\tif self.count == 2:\n\t\t\treturn \"%s %s\" % (_(\"DblTap\"), ButtonAction.describe_button(self.button))\n\t\treturn \"%s%s %s\" % (self.count, _(\"-tap\"), ButtonAction.describe_button(self.button))\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn self.describe_short()\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tif self.count <= 1:\n\t\t\treturn \"%s(%s)\" % (self.COMMAND, self.button)\n\t\treturn \"%s(%s, %s)\" % (self.COMMAND, self.button, self.count)\n"
  },
  {
    "path": "scc/mapper.py",
    "content": "#!/usr/bin/env python2\nfrom __future__ import unicode_literals\n\nfrom collections import deque\nfrom scc.lib import xwrappers as X\nfrom scc.uinput import UInput, Keyboard, Mouse, Dummy, Rels\nfrom scc.constants import FE_STICK, FE_TRIGGER, FE_PAD, GYRO, STICK, RSTICK\nfrom scc.constants import SCButtons, LEFT, RIGHT, CPAD, DPAD, HapticPos\nfrom scc.constants import STICK_PAD_MAX, STICKTILT, ControllerFlags\nfrom scc.aliases import ALL_AXES, ALL_BUTTONS\nfrom scc.actions import ButtonAction, GyroAbsAction\nfrom scc.controller import HapticData\nfrom scc.config import Config\nfrom scc.profile import Profile\n\n\nimport traceback, logging, time, os\nlog = logging.getLogger(\"Mapper\")\n\nclass Mapper(object):\n\tDEBUG = False\n\t\n\tdef __init__(self, profile, scheduler, keyboard=b\"SCController Keyboard\",\n\t\t\t\tmouse=b\"SCController Mouse\",\n\t\t\t\tgamepad=True, poller=None):\n\t\t\"\"\"\n\t\tIf any of keyboard, mouse or gamepad is set to None, that device\n\t\twill not be emulated.\n\t\tEmulated gamepad will have rumble enabled only if poller is set to\n\t\tinstance and configuration allows it.\n\t\t\"\"\"\n\t\tself.profile = profile\n\t\tself.controller = None\n\t\tself.xdisplay = None\n\t\tself.scheduler = scheduler\n\t\t\n\t\t# Create virtual devices\n\t\tlog.debug(\"Creating virtual devices\")\n\t\tself.keyboard = self.create_keyboard(keyboard) if keyboard else Dummy()\n\t\tlog.debug(\"Keyboard: %s\" % (self.keyboard, ))\n\t\tself.mouse = self.create_mouse(mouse) if mouse else Dummy()\n\t\tlog.debug(\"Mouse:    %s\" % (self.mouse, ))\n\t\tself.gamepad = self.create_gamepad(gamepad, poller) if gamepad else Dummy()\n\t\tlog.debug(\"Gamepad:  %s\" % (self.gamepad, ))\n\t\t\n\t\t# Set by SCCDaemon instance; Used to handle actions\n\t\t# from scc.special_actions\n\t\tself._sa_handler = None\n\t\t\n\t\t# Setup emulation\n\t\tself.keypress_list = []\n\t\tself.keyrelease_list = []\n\t\tself.mouse_movements = [0, 0, 0, 0, 0, 0]\t\t# mouse x, y, wheel vertical, horisontal, stick mouse x, stick mouse y\n\t\tself.feedbacks = [ None, None ]\t\t\t# left, right\n\t\tself.pressed = {}\t\t\t\t\t\t# for ButtonAction, holds number of times virtual button was pressed without releasing it first\n\t\tself.syn_list = set()\n\t\tself.buttons, self.old_buttons = 0, 0\n\t\tself.lpad_touched = False\n\t\tself.state, self.old_state = None, None\n\t\tself.force_event = set()\n\t\tself.time_elapsed = 0.0\n\t\n\t\n\tdef create_gamepad(self, enabled, poller):\n\t\t\"\"\" Parses gamepad configuration and creates apropriate unput device \"\"\"\n\t\tif not enabled or \"SCC_NOGAMEPAD\" in os.environ:\n\t\t\t# Completly undocumented and for debuging purposes only.\n\t\t\t# If set, no gamepad is emulated\n\t\t\tself.gamepad = Dummy()\n\t\t\treturn\n\t\tcfg = Config()\n\t\tkeys = ALL_BUTTONS[0:cfg[\"output\"][\"buttons\"]]\n\t\tvendor = int(cfg[\"output\"][\"vendor\"], 16)\n\t\tproduct = int(cfg[\"output\"][\"product\"], 16)\n\t\tversion = int(cfg[\"output\"][\"version\"], 16)\n\t\tname = cfg[\"output\"][\"name\"]\n\t\trumble = cfg[\"output\"][\"rumble\"] and poller != None\n\t\taxes = []\n\t\ti = 0\n\t\tfor min, max in cfg[\"output\"][\"axes\"]:\n\t\t\tfuzz, flat = 0, 0\n\t\t\tif abs(max - min) > STICK_PAD_MAX:\n\t\t\t\tfuzz, flat = 16, 128\n\t\t\ttry:\n\t\t\t\taxes.append(( ALL_AXES[i], min, max, fuzz, flat ))\n\t\t\texcept IndexError:\n\t\t\t\t# Out of axes\n\t\t\t\tbreak\n\t\t\ti += 1\n\t\t\n\t\tui = UInput(vendor=vendor, product=product, version=version,\n\t\t\tname=name, keys=keys, axes=axes, rels=[], rumble=rumble)\n\t\tif poller and rumble:\n\t\t\tpoller.register(ui.getDescriptor(), poller.POLLIN, self._rumble_ready)\n\t\treturn ui\n\t\n\t\n\tdef create_keyboard(self, name):\n\t\treturn Keyboard(name=name)\n\t\n\t\n\tdef create_mouse(self, name):\n\t\treturn Mouse(name=name)\n\t\n\t\n\tdef _rumble_ready(self, fd, event):\n\t\t# Taken from Steam Controller Singer project\n\t\t# https://gitlab.com/Pilatomic/SteamControllerSinger\n\t\tSTEAM_CONTROLLER_MAGIC_PERIOD_RATIO = 495483.0\n\t\tef = self.gamepad.ff_read()\n\t\tif ef:\t# tale of...\n\t\t\tperiod_command = 0\n\t\t\tamplitude = 0\n\t\t\tif ef.level != 0:\n\t\t\t\ttempRatio = ef.level / 32767.5\n\t\t\t\tperiod_command = ((6000 - 25000) * tempRatio + 25000)\n\t\t\t\tamplitude = ((900 - 600) * tempRatio + 600);\n\n\t\t\traw_period = period_command / STEAM_CONTROLLER_MAGIC_PERIOD_RATIO\n\t\t\t#duration_seconds = 1\n\t\t\tduration_seconds = ef.duration / 1000.0 * ef.repetitions\n\t\t\tcount = 0\n\t\t\tif raw_period != 0:\n\t\t\t\tcount = min(int(duration_seconds * 1.5 / raw_period), 0x7FFF)\n\n\t\t\t#log.debug(f\"{ef.level} {ef.duration} {ef.repetitions} {count}\")\n\t\t\tself.send_feedback(HapticData(\n\t\t\t\tHapticPos.BOTH,\n\t\t\t\tperiod = period_command,\n\t\t\t\tamplitude = amplitude,\n\t\t\t\tcount = count,\n\t\t\t\t#period = 20000,\n\t\t\t\t#amplitude = max(0, ef.level),\n\t\t\t\t#count = min(0x7FFF, ef.duration * ef.repetitions / 30)\n\t\t\t))\n\t\n\t\n\tdef get_gamepad_name(self):\n\t\t\"\"\"\n\t\tReturns name of emulated gamepad (as displayed by jstest & co)\n\t\tor None if Dummy is assigned.\n\t\t\"\"\"\n\t\tif isinstance(self.gamepad, Dummy):\n\t\t\treturn None\n\t\treturn self.gamepad.name\n\t\n\t\n\tdef sync(self):\n\t\t\"\"\" Syncs generated events \"\"\"\n\t\tif len(self.syn_list):\n\t\t\tfor dev in self.syn_list:\n\t\t\t\tdev.synEvent()\n\t\t\tself.syn_list = set()\n\t\n\t\n\tdef set_controller(self, c):\n\t\t\"\"\" Sets controller device, used by some (one so far) actions \"\"\"\n\t\tself.controller = c\n\t\n\t\n\tdef get_controller(self):\n\t\t\"\"\" Returns assigned controller device or None if no controller is set \"\"\"\n\t\treturn self.controller\n\t\n\t\n\tdef set_special_actions_handler(self, sa):\n\t\tself._sa_handler = sa\n\t\n\t\n\tdef get_special_actions_handler(self):\n\t\treturn self._sa_handler\n\t\n\t\n\tdef set_xdisplay(self, x):\n\t\tself.xdisplay = x\n\t\n\t\n\tdef get_xdisplay(self):\n\t\treturn self.xdisplay\n\t\n\t\n\tdef get_current_window(self):\n\t\t\"\"\"\n\t\tReturns window id of current window or None if xdisplay is not set\n\t\t\"\"\"\n\t\tif self.xdisplay:\n\t\t\treturn X.get_current_window(self.xdisplay)\n\t\treturn None\n\t\n\t\n\tdef schedule(self, delay, cb):\n\t\t\"\"\"\n\t\tSchedules callback to be ran no sooner than after delay.\n\t\tDelay is float number in seconds.\n\t\tCallback is called with mapper as only argument.\n\t\t\"\"\"\n\t\treturn self.scheduler.schedule(delay, cb, self)\n\t\n\t\n\tdef cancel_task(self, task):\n\t\t\"\"\" Removes scheduled task. \"\"\"\n\t\treturn self.scheduler.cancel_task(task)\n\t\n\t\n\tdef mouse_move(self, dx, dy):\n\t\t\"\"\"\n\t\tSchedules mouse movement to be done at end of processing callback.\n\t\tCalled from actions while callback is being processed.\n\t\t\"\"\"\n\t\tself.mouse_movements[0] += dx\n\t\tself.mouse_movements[1] += dy\n\n\t\n\tdef mouse_wheel(self, wx, wy):\n\t\t\"\"\"\n\t\tSchedules mouse wheel movement to be done at end of processing callback.\n\t\tCalled from actions while callback is being processed.\n\t\t\"\"\"\n\t\tself.mouse_movements[2] += wx\n\t\tself.mouse_movements[3] += wy\n\n\n\tdef mouse_move_stick(self, dx, dy):\n\t\t\"\"\"\n\t\tSchedules mouse movement to be done at end of processing callback.\n\t\tCalled from actions while callback is being processed.\n\t\t\"\"\"\n\t\tself.mouse_movements[4] += dx\n\t\tself.mouse_movements[5] += dy\n\t\n\t\n\tdef send_feedback(self, hapticdata):\n\t\t\"\"\"\n\t\tSchedules haptic feedback to be sent at end of processing callback.\n\t\tCalled from actions while callback is being processed.\n\t\t\"\"\"\n\t\tif hapticdata.get_position() == HapticPos.BOTH:\n\t\t\t# HapticPos.BOTH is special case as controller doesn't\n\t\t\t# really support doing that by itself.\n\t\t\tself.feedbacks[0]  = hapticdata.with_position(HapticPos.LEFT)\n\t\t\tself.feedbacks[1]  = hapticdata.with_position(HapticPos.RIGHT)\n\t\telse:\n\t\t\tself.feedbacks[hapticdata.get_position()] = hapticdata\n\t\n\t\n\tdef controller_flags(self):\n\t\t\"\"\"\n\t\tReturns controller flags or, if there is no controller set to\n\t\tthis mapper, sc_by_cable driver matching defaults.\n\t\t\"\"\"\n\t\treturn 0 if self.controller is None else self.controller.flags\n\t\n\t\n\tdef is_touched(self, what):\n\t\t\"\"\"\n\t\tReturns True if specified pad is being touched.\n\t\tMay randomly return False for aphephobic pads.\n\t\t\n\t\t'what' should be LEFT or RIGHT (from scc.constants)\n\t\t\"\"\"\n\t\tif what == LEFT:\n\t\t\treturn self.buttons & SCButtons.LPADTOUCH\n\t\telif what == RIGHT:\n\t\t\treturn self.buttons & SCButtons.RPADTOUCH\n\t\telif what == CPAD:\n\t\t\treturn self.buttons & SCButtons.CPADTOUCH\n\t\telse:\n\t\t\treturn False\n\t\n\t\n\tdef was_touched(self, what):\n\t\t\"\"\"\n\t\tAs is_touched, but returns True if pad *was* touched\n\t\tin previous known state.\n\t\t\n\t\tThis is used as:\n\t\tis_touched() and not was_touched() -> pad was just pressed\n\t\tnot is_touched() and was_touched() -> pad was just released\n\t\t\"\"\"\n\t\tif what == LEFT:\n\t\t\treturn self.old_buttons & SCButtons.LPADTOUCH\n\t\telif what == RIGHT:\n\t\t\treturn self.old_buttons & SCButtons.RPADTOUCH\n\t\telif what == CPAD:\n\t\t\treturn self.old_buttons & SCButtons.CPADTOUCH\n\t\telse:\n\t\t\treturn False\n\t\n\t\n\tdef is_pressed(self, button):\n\t\t\"\"\"\n\t\tReturns True if button is pressed\n\t\t\"\"\"\n\t\tif button == LEFT:\n\t\t\tbutton = SCButtons.LPAD\n\t\telif button == RIGHT:\n\t\t\tbutton = SCButtons.RPAD\n\t\treturn self.buttons & button\n\t\n\t\n\tdef was_pressed(self, button):\n\t\t\"\"\"\n\t\tReturns True if button was pressed in previous known state\n\t\t\"\"\"\n\t\tif button == LEFT:\n\t\t\tbutton = SCButtons.LPAD\n\t\telif button == RIGHT:\n\t\t\tbutton = SCButtons.RPAD\n\t\treturn self.old_buttons & button\n\t\n\t\n\tdef get_pressed_button(self):\n\t\t\"\"\"\n\t\tGets button that was pressed by very last handled event or None,\n\t\tif last event doesn't involved button pressing.\n\t\t\"\"\"\n\t\tfor x in SCButtons:\n\t\t\tif x & self.buttons & ~self.old_buttons:\n\t\t\t\treturn x\n\t\treturn None\n\t\n\t\n\tdef set_button(self, button, state):\n\t\t\"\"\"\n\t\tSets button state on input.\n\t\tSet value will stay only for durration of one event loop iteration.\n\t\t\n\t\tUsed _temporarely_ by RingAction to emulate finger lifting from pad.\n\t\t\"\"\"\n\t\tif button == LEFT:\n\t\t\tbutton = SCButtons.LPADTOUCH\n\t\telif button == RIGHT:\n\t\t\tbutton = SCButtons.RPADTOUCH\n\t\t\n\t\tif state:\n\t\t\tself.buttons |= button\n\t\telse:\n\t\t\tself.buttons &= ~button\n\t\n\t\n\tdef set_was_pressed(self, button, state):\n\t\t\"\"\"\n\t\tAs set_button, but changes value remembered\n\t\tfrom loop iteration before current.\n\t\t\n\t\tUsed _temporarely_ by RingAction to emulate finger lifting from pad.\n\t\t\"\"\"\n\t\tif button == LEFT:\n\t\t\tbutton = SCButtons.LPADTOUCH\n\t\telif button == RIGHT:\n\t\t\tbutton = SCButtons.RPADTOUCH\n\t\t\n\t\tif state:\n\t\t\tself.old_buttons |= button\n\t\telse:\n\t\t\tself.old_buttons &= ~button\n\t\n\t\n\tdef release_virtual_buttons(self):\n\t\t\"\"\"\n\t\tCalled when daemon is killed or USB dongle is disconnected.\n\t\tSends button release event for every virtual button that is still being\n\t\tpressed.\n\t\t\"\"\"\n\t\tto_release, self.pressed = self.pressed, {}\n\t\tfor x in to_release:\n\t\t\tButtonAction._button_release(self, x, True)\n\t\n\t\n\tdef cancel_all(self):\n\t\t\"\"\"\n\t\tCalled when profile is changed to let all actions to cancel\n\t\tlong-running effects they may have created\n\t\t\"\"\"\n\t\tfor a in self.profile.get_actions():\n\t\t\ta.cancel(self)\n\t\n\t\n\tdef reset_gyros(self):\n\t\tfor a in self.profile.get_all_actions():\n\t\t\tif isinstance(a, GyroAbsAction):\n\t\t\t\ta.reset()\n\t\n\t\n\tdef input(self, controller, old_state, state):\n\t\t# Store states\n\t\tself.old_state = old_state\n\t\tself.old_buttons = self.buttons\n\t\t\n\t\tself.state = state\n\t\tself.buttons = state.buttons\n\t\t\n\t\tt = time.time()\n\t\tcontroller.time_elapsed = self.time_elapsed = t - controller.lastTime\n\t\tcontroller.lastTime = t\n\n\t\tif self.buttons & SCButtons.LPAD and not self.buttons & (SCButtons.LPADTOUCH | STICKTILT):\n\t\t\tself.buttons = (self.buttons & ~SCButtons.LPAD) | SCButtons.STICKPRESS\n\t\t\n\t\tfe = self.force_event\n\t\tself.force_event = set()\n\t\t\n\t\t# Check buttons\n\t\txor = self.old_buttons ^ self.buttons\n\t\tbtn_rem = xor & self.old_buttons\n\t\tbtn_add = xor & self.buttons\n\t\t\n\t\ttry:\n\t\t\tif btn_add or btn_rem:\n\t\t\t\t# At least one button was pressed\n\t\t\t\tfor x in self.profile.buttons:\n\t\t\t\t\tif x & btn_add:\n\t\t\t\t\t\tself.profile.buttons[x].button_press(self)\n\t\t\t\t\telif x & btn_rem:\n\t\t\t\t\t\tself.profile.buttons[x].button_release(self)\n\t\t\t\n\t\t\t\n\t\t\t# Check sticks\n\t\t\tif self.controller.flags & ControllerFlags.SEPARATE_STICK:\n\t\t\t\tif FE_STICK in fe or self.old_state.stick_x != state.stick_x or self.old_state.stick_y != state.stick_y:\n\t\t\t\t\tself.profile.stick.whole(self, state.stick_x, state.stick_y, STICK)\n\t\t\telif not self.buttons & SCButtons.LPADTOUCH:\n\t\t\t\tif FE_STICK in fe or self.old_state.lpad_x != state.lpad_x or self.old_state.lpad_y != state.lpad_y:\n\t\t\t\t\tself.profile.stick.whole(self, state.lpad_x, state.lpad_y, STICK)\n\t\t\tif self.controller.flags & ControllerFlags.IS_DECK:\n\t\t\t\tif FE_STICK in fe or self.old_state.rstick_x != state.rstick_x or self.old_state.rstick_y != state.rstick_y:\n\t\t\t\t\tself.profile.rstick.whole(self, state.rstick_x, state.rstick_y, RSTICK)\n\t\t\t\n\t\t\t# Check gyro\n\t\t\tif controller.get_gyro_enabled():\n\t\t\t\tself.profile.gyro.gyro(self, state.gpitch, state.gyaw, state.groll, state.q1, state.q2, state.q3, state.q4)\n\t\t\t\n\t\t\t# Check triggers\n\t\t\tif FE_TRIGGER in fe or state.ltrig != self.old_state.ltrig:\n\t\t\t\tif LEFT in self.profile.triggers:\n\t\t\t\t\tself.profile.triggers[LEFT].trigger(self, state.ltrig, self.old_state.ltrig)\n\t\t\tif FE_TRIGGER in fe or state.rtrig != self.old_state.rtrig:\n\t\t\t\tif RIGHT in self.profile.triggers:\n\t\t\t\t\tself.profile.triggers[RIGHT].trigger(self, state.rtrig, self.old_state.rtrig)\n\t\t\t\n\t\t\t# Check pads\n\t\t\t# RPAD\n\t\t\tif controller.flags & ControllerFlags.IS_DECK:\n\t\t\t\tif FE_PAD in fe or self.old_state.rpad_x != state.rpad_x or self.old_state.rpad_y != state.rpad_y:\n\t\t\t\t\tself.profile.pads[RIGHT].whole(self, state.rpad_x, state.rpad_y, RIGHT)\n\t\t\telif controller.flags & ControllerFlags.HAS_RSTICK:\n\t\t\t\tif FE_PAD in fe or self.old_state.rpad_x != state.rpad_x or self.old_state.rpad_y != state.rpad_y:\n\t\t\t\t\tself.profile.pads[RIGHT].whole(self, state.rpad_x, state.rpad_y, RIGHT)\n\t\t\telif FE_PAD in fe or self.buttons & SCButtons.RPADTOUCH or SCButtons.RPADTOUCH & btn_rem:\n\t\t\t\tself.profile.pads[RIGHT].whole(self, state.rpad_x, state.rpad_y, RIGHT)\n\t\t\t# DPAD\n\t\t\tif controller.flags & ControllerFlags.IS_DECK:\n\t\t\t\tif FE_PAD in fe or self.old_state.dpad_x != state.dpad_x or self.old_state.dpad_y != state.dpad_y:\n\t\t\t\t\tself.profile.pads[DPAD].whole(self, state.dpad_x, state.dpad_y, DPAD)\n\t\t\t\n\t\t\t# LPAD\n\t\t\tif self.controller.flags & ControllerFlags.SEPARATE_STICK:\n\t\t\t\tif FE_PAD in fe or self.old_state.lpad_x != state.lpad_x or self.old_state.lpad_y != state.lpad_y:\n\t\t\t\t\tself.profile.pads[LEFT].whole(self, state.lpad_x, state.lpad_y, LEFT)\n\t\t\telse:\n\t\t\t\tif self.buttons & SCButtons.LPADTOUCH:\n\t\t\t\t\t# Pad is being touched now\n\t\t\t\t\tif not self.lpad_touched:\n\t\t\t\t\t\tself.lpad_touched = True\n\t\t\t\t\tself.profile.pads[LEFT].whole(self, state.lpad_x, state.lpad_y, LEFT)\n\t\t\t\t\tif self.old_state.buttons & STICKTILT and not self.buttons & STICKTILT:\n\t\t\t\t\t\t# LPAD and stick share axes and so when they are used simultaneously (by someone with 3 hands or so :)\n\t\t\t\t\t\t# this is how mapper can tell that stick was recentered\n\t\t\t\t\t\tself.profile.stick.whole(self, 0, 0, STICK)\n\t\t\t\telif not self.buttons & STICKTILT:\n\t\t\t\t\t# Pad is not being touched\n\t\t\t\t\tif self.lpad_touched:\n\t\t\t\t\t\tself.lpad_touched = False\n\t\t\t\t\t\tself.profile.pads[LEFT].whole(self, 0, 0, LEFT)\n\t\t\t\t\t\n\t\t\t# CPAD (touchpad on DS4 controller)\n\t\t\tif controller.flags & ControllerFlags.HAS_CPAD:\n\t\t\t\tif ((FE_PAD in fe)\n\t\t\t\t\t\tor (self.old_state.cpad_x != state.cpad_x)\n\t\t\t\t\t\tor (self.old_state.cpad_y != state.cpad_y)\n\t\t\t\t\t\tor ((self.old_buttons & SCButtons.CPADTOUCH) and not (self.buttons & SCButtons.CPADTOUCH))\n\t\t\t\t\t):\n\t\t\t\t\tif self.buttons & SCButtons.CPADTOUCH:\n\t\t\t\t\t\tself.profile.pads[CPAD].whole(self, state.cpad_x, state.cpad_y, CPAD)\n\t\t\t\t\telif self.old_buttons & SCButtons.CPADTOUCH:\n\t\t\t\t\t\tself.profile.pads[CPAD].whole(self, 0, 0, CPAD)\n\t\texcept Exception:\n\t\t\t# Log error but don't crash here, it breaks too many things at once\n\t\t\tif hasattr(self, \"_testing\"):\n\t\t\t\traise\n\t\t\tlog.error(\"Error while processing controller event\")\n\t\t\tlog.error(traceback.format_exc())\n\t\t\n\t\t# TODO: Is it important to run scheduled stuff before generate_events?\n\t\tself.scheduler.run()\n\t\tself.generate_events()\n\t\tself.generate_feedback()\n\t\n\t\n\tdef generate_events(self):\n\t\t# Generate events - keys\n\t\tif len(self.keypress_list):\n\t\t\tself.keyboard.pressEvent(self.keypress_list)\n\t\t\tself.keypress_list = []\n\t\tif len(self.keyrelease_list):\n\t\t\tself.keyboard.releaseEvent(self.keyrelease_list)\n\t\t\tself.keyrelease_list = []\n\t\t# Generate events - mouse\n\t\tmx, my, wx, wy, sx, sy = self.mouse_movements\n\t\tif mx != 0 or my != 0:\n\t\t\tself.mouse.moveEvent(int(mx), int(my * -1), self.time_elapsed)\n\t\t\tself.syn_list.add(self.mouse)\n\t\tif wx != 0 or wy != 0:\n\t\t\tself.mouse.scrollEvent(wx, wy)\n\t\t\tself.syn_list.add(self.mouse)\n\t\tif sx != 0 or sy != 0:\n\t\t\t#log.debug(\"STARTING\")\n\t\t\t#log.debug(f\"{sx} {sy}\")\n\t\t\tself.mouse.moveStickEvent(sx, sy * -1, self.time_elapsed)\n\t\t\tself.syn_list.add(self.mouse)\n\n\t\tself.mouse_movements = [ 0, 0, 0, 0, 0, 0 ]\n\t\tself.sync()\n\t\n\t\n\tdef generate_feedback(self):\n\t\tif self.controller:\n\t\t\tfor x in (0, 1):\n\t\t\t\tif self.feedbacks[x]:\n\t\t\t\t\tself.controller.feedback(self.feedbacks[x])\n\t\t\t\t\tself.feedbacks[x] = None\n"
  },
  {
    "path": "scc/menu_data.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Menu Data\n\nContainer for list of menu items + required parsers\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\nfrom scc.actions import Action\n\nimport json, os\n\nclass MenuData(object):\n\t\"\"\" Contains list of menu items. Indexable \"\"\"\n\tdef __init__(self, *items):\n\t\tself.__items = list(items)\n\t\n\t\n\tdef generate(self, menuhandler):\n\t\t\"\"\"\n\t\tConverts all generators into MenuItems (by calling .generate() on them)\n\t\tand returns generated MenuData.\n\t\t\n\t\tReturns new MenuData instance.\n\t\t\"\"\"\n\t\titems = []\n\t\tfor i in self:\n\t\t\tif isinstance(i, MenuGenerator):\n\t\t\t\titems.extend(i.generate(menuhandler))\n\t\t\telse:\n\t\t\t\titems.append(i)\n\t\treturn MenuData(*items)\n\t\n\t\n\tdef compress(self):\n\t\tfor i in self.__items:\n\t\t\tif i.action:\n\t\t\t\ti.action = i.action.compress()\n\t\n\t\n\tdef __len__(self):\n\t\treturn len(self.__items)\n\t\n\t\n\tdef __getitem__(self, index):\n\t\treturn self.__items[index]\n\t\n\t\n\tdef __iter__(self):\n\t\treturn iter(self.__items)\n\t\n\t\n\tdef get_all_actions(self):\n\t\t\"\"\"\n\t\tReturns generator with every action defined in this menu, including\n\t\tchild actions.\n\t\t\"\"\"\n\t\tfor item in self:\n\t\t\tif hasattr(item, \"action\") and item.action:\n\t\t\t\tfor i in item.action.get_all_actions():\n\t\t\t\t\tyield i\n\t\n\t\n\tdef get_by_id(self, id):\n\t\t\"\"\"\n\t\tReturns item with specified ID.\n\t\tThrows KeyError if there is no such item.\n\t\t\"\"\"\n\t\tfor a in self:\n\t\t\tif a.id == id:\n\t\t\t\treturn a\n\t\traise KeyError(\"No such item\")\n\t\n\t\n\tdef index(self, a):\n\t\treturn self.__items.index(a)\n\t\n\t\n\tdef encode(self):\n\t\t\"\"\" Returns menu data as dict storable in json (profile) file \"\"\"\n\t\trv = []\n\t\tfor i in self:\n\t\t\trv.append(i.encode())\n\t\treturn rv\n\t\n\t\n\t@staticmethod\n\tdef from_args(data):\n\t\t\"\"\"\n\t\tParses list of arguments in [id1, label1, id2, label2 ...] format.\n\t\tThrows ValueError if number of items in 'data' is odd.\n\t\t\"\"\"\n\t\tif len(data) % 2 != 0:\n\t\t\traise ValueError(\"Odd number of items\")\n\t\tif len(data) < 1:\n\t\t\traise ValueError(\"Not items\")\n\t\t\n\t\t# Rearange data into list of pair tuples\n\t\tdata = [\n\t\t\t(data[i * 2], data[(i * 2) + 1])\n\t\t\tfor i in range(0, len(data) / 2)\n\t\t]\n\t\t\n\t\t# Parse data\n\t\tm = MenuData()\n\t\tfor id, label in data:\n\t\t\tm.__items.append(MenuItem(id, label))\n\t\treturn m\n\t\n\t\n\t@staticmethod\n\tdef from_json_data(data, action_parser=None):\n\t\t\"\"\"\n\t\tLoads menu from parsed JSON dict.\n\t\tActions are parsed only if action_parser is set to ActionParser instance.\n\t\t\"\"\"\n\t\tm = MenuData()\n\t\tused_ids = set()\n\t\tfor i in data:\n\t\t\titem = None\n\t\t\tif \"generator\" in i and i[\"generator\"] in MENU_GENERATORS:\n\t\t\t\titem = MENU_GENERATORS[i[\"generator\"]](**i)\n\t\t\telif \"separator\" in i:\n\t\t\t\titem = Separator(i[\"name\"] if \"name\" in i else None)\n\t\t\telif \"submenu\" in i:\n\t\t\t\titem = Submenu(i[\"submenu\"],\n\t\t\t\t\ti[\"name\"] if \"name\" in i else None,\n\t\t\t\t\ticon = i[\"icon\"] if \"icon\" in i else None,\n\t\t\t\t)\n\t\t\telif \"id\" not in i:\n\t\t\t\t# Cannot add menu without ID\n\t\t\t\tcontinue\n\t\t\telse:\n\t\t\t\taction = None\n\t\t\t\tid = i[\"id\"]\n\t\t\t\tif id in used_ids:\n\t\t\t\t\t# Cannot add duplicate ID\n\t\t\t\t\tcontinue\n\t\t\t\tif action_parser:\n\t\t\t\t\taction = action_parser.from_json_data(i)\n\t\t\t\tused_ids.add(id)\n\t\t\t\tlabel, icon = id, None\n\t\t\t\tif \"name\" in i:\n\t\t\t\t\tlabel = i[\"name\"]\n\t\t\t\telif action:\n\t\t\t\t\tlabel = action.describe(Action.AC_OSD)\n\t\t\t\tif \"icon\" in i:\n\t\t\t\t\ticon = i[\"icon\"]\n\t\t\t\titem = MenuItem(id, label, action, icon=icon)\n\t\t\tm.__items.append(item)\n\t\t\n\t\treturn m\n\t\n\t\n\t@staticmethod\n\tdef from_fileobj(fileobj, action_parser=None):\n\t\t\"\"\"\n\t\tLoads menu from file-like object.\n\t\tActions are parsed only if action_parser is set to ActionParser instance.\n\t\t\"\"\"\n\t\tdata = json.loads(fileobj.read())\n\t\treturn MenuData.from_json_data(data, action_parser)\n\t\n\t\n\t@staticmethod\n\tdef from_file(filename, action_parser=None):\n\t\t\"\"\"\n\t\tLoads menu from file.\n\t\tActions are parsed only if action_parser is set to ActionParser instance.\n\t\t\"\"\"\n\t\treturn MenuData.from_fileobj(open(filename, \"r\"), action_parser)\n\t\n\t\n\t@staticmethod\n\tdef from_profile(filename, menuname, action_parser=None):\n\t\t\"\"\"\n\t\tLoads menu from JSON profile file.\n\t\tActions are parsed only if action_parser is set to ActionParser instance.\n\t\t\n\t\tMenus are stored as list under <root>/menus/<menuname>.\n\t\tThrows ValueError if specified file cannot be parsed or\n\t\tspecified menu cannot be found.\n\t\t\"\"\"\n\t\tdata = json.loads(open(filename, \"r\").read())\n\t\tif \"menus\" not in data:\n\t\t\traise ValueError(\"Menu not found\")\n\t\tif menuname not in data[\"menus\"]:\n\t\t\traise ValueError(\"Menu not found\")\n\t\t\n\t\treturn MenuData.from_json_data(data[\"menus\"][menuname], action_parser)\n\n\nclass MenuItem(object):\n\t\"\"\" Really just dummy container \"\"\"\n\tdef __init__(self, id, label, action=None, callback=None, icon=None):\n\t\tself.id = id\n\t\tself.label = label\n\t\tself.action = action\n\t\tself.icon = icon\n\t\tself.callback = callback\t# If defined, called when user chooses menu instead of using action\n\t\tself.widget = None\t\t\t# May be set by UI code\n\t\n\t\n\tdef describe(self):\n\t\t\"\"\"\n\t\tReturns user-friendly description of MenuItem or MenuGenerator.\n\t\t\"\"\"\n\t\treturn self.label\n\t\n\t\n\tdef encode(self):\n\t\t\"\"\" Returns item data as dict storable in json (profile) file \"\"\"\n\t\tif self.action and type(self.action) in (str,):\n\t\t\trv = { 'action' : self.action }\n\t\telif self.action:\n\t\t\trv = self.action.encode()\n\t\telse:\n\t\t\trv = {}\n\t\trv['id'] = self.id\n\t\trv['name'] = self.label\n\t\tif self.icon: rv['icon'] = self.icon\n\t\treturn rv\n\n\nclass Separator(MenuItem):\n\t\"\"\" Internally, separator is MenuItem without action and id \"\"\"\n\tdef __init__(self, label=None):\n\t\tMenuItem.__init__(self, None, label)\n\t\n\t\n\tdef describe(self):\n\t\tif self.label:\n\t\t\treturn _(\"----[ %s ]----\") % (self.label,)\n\t\telse:\n\t\t\treturn _(\"---- Separator ----\")\n\t\n\t\n\tdef encode(self):\n\t\tif self.label:\n\t\t\treturn { \"separator\" : True, \"name\" : self.label }\n\t\treturn { \"separator\" : True }\n\n\nclass Submenu(MenuItem):\n\t\"\"\" Internally, separator is MenuItem without action and id \"\"\"\n\tdef __init__(self, filename, label=None, icon=None):\n\t\tif not label:\n\t\t\tlabel = \".\".join(os.path.split(filename)[-1].split(\".\")[0:-1])\n\t\tself.filename = filename\n\t\tMenuItem.__init__(self, str(id(self)), label=label, icon=icon)\n\t\n\t\n\tdef describe(self):\n\t\treturn self.label + \"  \" + _(\">>\")\n\t\n\t\n\tdef encode(self):\n\t\trv = { \"submenu\" : self.filename }\n\t\tif self.label: rv[\"name\"] = self.label\n\t\tif self.icon: rv[\"icon\"] = self.icon\n\t\treturn rv\n\n\nclass MenuGenerator(object):\n\tGENERATOR_NAME = None\n\t\"\"\" Generates list of MenuItems \"\"\" \n\t\n\tdef __init__(self, **b):\n\t\t\"\"\"\n\t\tPassed are all keys loaded from json dict that defined this generator.\n\t\t__init__ of generator should ignore all unknown keys.\n\t\t\"\"\"\n\t\tself.id = None\t\t# Used only in editor\n\t\tself.icon = None\t# same\n\t\n\t\n\tdef describe(self):\n\t\t\"\"\"\n\t\tReturns user-friendly description of MenuItem or MenuGenerator.\n\t\t\"\"\"\n\t\treturn \"[ %s ] \" % (self.__class__.__name__,)\n\t\n\t\n\tdef encode(self):\n\t\t\"\"\" Returns generator data as dict storable in json (profile) file \"\"\"\n\t\treturn { \"generator\" : self.GENERATOR_NAME }\n\t\n\tdef generate(self, menuhandler):\n\t\treturn []\n\n\n# Holds dict of knowm menu ganerators, but generated elsewhere\nMENU_GENERATORS = { }\n"
  },
  {
    "path": "scc/modifiers.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Modifiers\n\nModifier is Action that just sits between input and actual action, changing\nway how resulting action works.\nFor example, click() modifier executes action only if pad is pressed.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.actions import Action, MouseAction, XYAction, AxisAction, RangeOP\nfrom scc.actions import NoAction, WholeHapticAction, HapticEnabledAction\nfrom scc.actions import GyroAbsAction\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX, STICK_PAD_MAX_HALF\nfrom scc.constants import CUT, ROUND, LINEAR, MINIMUM, FE_STICK, FE_TRIGGER\nfrom scc.constants import TRIGGER_MAX, LEFT, CPAD, RIGHT, STICK\nfrom scc.constants import FE_PAD, SCButtons, STICKTILT\nfrom scc.constants import HapticPos, ControllerFlags\nfrom scc.tools import nameof, clamp, quat2euler\nfrom scc.controller import HapticData\nfrom scc.uinput import Axes, Rels\nfrom math import pi as PI, sqrt, copysign, atan2, sin, cos\nfrom collections import OrderedDict, deque\n\nimport time, logging, inspect\nimport itertools\nlog = logging.getLogger(\"Modifiers\")\n_ = lambda x : x\n\nclass Modifier(Action):\n\tdef __init__(self, *params):\n\t\tAction.__init__(self, *params)\n\t\tparams = list(params)\n\t\tfor p in params:\n\t\t\tif isinstance(p, Action):\n\t\t\t\tself.action = p\n\t\t\t\tparams.remove(p)\n\t\t\t\tbreak\n\t\telse:\n\t\t\tself.action = NoAction()\n\t\tself._mod_init(*params)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn self.action.get_compatible_modifiers()\n\t\n\t\n\tdef cancel(self, mapper):\n\t\tself.action.cancel(mapper)\n\t\n\t\n\tdef get_child_actions(self):\n\t\treturn (self.action, )\n\t\n\t\n\tdef _mod_init(self):\n\t\t\"\"\"\n\t\tInitializes modifier with rest of parameters, after action parameter\n\t\twas taken from it and stored in self.action\n\t\t\"\"\"\n\t\tpass # not needed by default\n\t\n\t\n\tdef _mod_to_string(self, params, multiline, pad):\n\t\t\"\"\" Adds action at end of params list and generates string \"\"\"\n\t\tif multiline:\n\t\t\tchildstr = self.action.to_string(True, pad + 2)\n\t\t\tif len(params) > 0:\n\t\t\t\treturn \"%s%s(%s,%s%s)\" % (\n\t\t\t\t\t\" \" * pad,\n\t\t\t\t\tself.COMMAND,\n\t\t\t\t\t\", \".join([ nameof(s) for s in params ]),\n\t\t\t\t\t'\\n' if '\\n' in childstr else ' ',\n\t\t\t\t\tchildstr\n\t\t\t\t)\n\t\t\treturn \"%s%s(%s)\" % ( \" \" * pad, self.COMMAND, childstr.strip() )\n\t\tchildstr = self.action.to_string(False, pad)\n\t\tif len(params) > 0:\n\t\t\treturn \"%s%s(%s, %s)\" % (\n\t\t\t\t\" \" * pad,\n\t\t\t\tself.COMMAND,\n\t\t\t\t\", \".join([ nameof(s) for s in params ]),\n\t\t\t\tchildstr\n\t\t\t)\n\n\t\treturn \"%s%s(%s)\" % (\n\t\t\t\" \" * pad,\n\t\t\tself.COMMAND,\n\t\t\tchildstr\n\t\t)\n\t\n\t\n\tdef strip_defaults(self):\n\t\t\"\"\"\n\t\tOverrides Action.strip_defaults; Uses defaults from _mod_init instead\n\t\tof __init__, but does NOT include last of original parameters - action.\n\t\t\"\"\"\n\t\targspec = inspect.getfullargspec(self.__class__._mod_init)\n\t\trequired_count = len(argspec.args) - len(argspec.defaults) - 1\n\t\tl = list(self.parameters[0:-1])\n\t\td = list(argspec.defaults)[0:len(l)]\n\t\twhile len(d) and len(l) > required_count and d[-1] == l[-1]:\n\t\t\td, l = d[:-1], l[:-1]\n\t\treturn l\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\tif self.action:\n\t\t\tself.action = self.action.compress()\n\t\treturn self\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Modifier '%s', %s>\" % (self.COMMAND, self.action)\n\t\n\t__repr__ = __str__\n\n\nclass NameModifier(Modifier):\n\t\"\"\"\n\tSimple modifier that sets name for child action.\n\tUsed internally.\n\t\"\"\"\n\tCOMMAND = \"name\"\n\t\n\tdef _mod_init(self, name):\n\t\tself.name = name\n\t\tif self.action:\n\t\t\tself.action.name = name\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\treturn a.set_name(data[NameModifier.COMMAND])\n\t\n\t\n\tdef strip(self):\n\t\trv = self.action.strip()\n\t\trv.name = self.name\n\t\treturn rv\n\t\n\t\n\t@staticmethod\n\tdef unstrip(action):\n\t\t# Inversion of strip.\n\t\t# For action that has name, returns NameModifier wrapping around that\n\t\t# action. For everything else returns original action.\n\t\tif not isinstance(action, NameModifier):\n\t\t\tif action and action.name:\n\t\t\t\treturn NameModifier(action.name, action)\n\t\treturn action\n\t\n\t\n\tdef compress(self):\n\t\treturn self.strip()\n\t\n\t\n\tdef describe(self, context):\n\t\treturn self.name or self.to_string()\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn \"%s(%s, %s)\" % (\n\t\t\tself.COMMAND,\n\t\t\trepr(self.name).strip('u'),\n\t\t\tself.action.to_string(multiline, pad)\n\t\t)\n\n\nclass ClickModifier(Modifier):\n\t# TODO: Rename to 'clicked'\n\tCOMMAND = \"click\"\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\treturn ClickModifier(a)\n\t\n\t\n\tdef describe(self, context):\n\t\tif context in (Action.AC_STICK, Action.AC_PAD):\n\t\t\treturn _(\"(if pressed)\") + \"\\n\" + self.action.describe(context)\n\t\telse:\n\t\t\treturn _(\"(if pressed)\") + \" \" + self.action.describe(context)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tif multiline:\n\t\t\tchildstr = self.action.to_string(True, pad + 2)\n\t\t\tif \"\\n\" in childstr:\n\t\t\t\treturn \"%s%s(\\n%s\\n%s)\" % (\n\t\t\t\t\t\" \" * pad,\n\t\t\t\t\tself.COMMAND,\n\t\t\t\t\tchildstr,\n\t\t\t\t\t\" \" * pad\n\t\t\t\t)\n\t\treturn \"%s(%s)\" % (\n\t\t\tself.COMMAND,\n\t\t\tself.action.to_string()\n\t\t)\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\tself.action = self.action.compress()\n\t\treturn self\n\t\n\t\n\t# For button press & co it's safe to assume that they are being pressed...\n\tdef button_press(self, mapper):\n\t\treturn self.action.button_press(mapper)\n\t\n\tdef button_release(self, mapper):\n\t\treturn self.action.button_release(mapper)\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\treturn self.action.trigger(mapper, position, old_position)\n\t\n\t\n\tdef axis(self, mapper, position, what):\n\t\tif what in (STICK, LEFT) and mapper.is_pressed(SCButtons.LPAD):\n\t\t\tif what == STICK: mapper.force_event.add(FE_STICK)\n\t\t\treturn self.action.axis(mapper, position, what)\n\t\telif what in (STICK, LEFT) and mapper.was_pressed(SCButtons.LPAD):\n\t\t\t# Just released\n\t\t\treturn self.action.axis(mapper, 0, what)\n\t\telif what == CPAD and mapper.is_pressed(SCButtons.CPAD):\n\t\t\treturn self.action.axis(mapper, position, what)\n\t\telif what == CPAD and mapper.was_pressed(SCButtons.CPAD):\n\t\t\t# Just released\n\t\t\treturn self.action.axis(mapper, 0, what)\n\t\telif mapper.is_pressed(SCButtons.RPAD):\n\t\t\t# what == RIGHT, last option\n\t\t\treturn self.action.axis(mapper, position, what)\n\t\telif mapper.was_pressed(SCButtons.RPAD):\n\t\t\t# what == RIGHT, last option, Just released\n\t\t\treturn self.action.axis(mapper, 0, what)\n\t\n\t\n\tdef pad(self, mapper, position, what):\n\t\tif what == LEFT and mapper.is_pressed(SCButtons.LPAD):\n\t\t\tif what == STICK: mapper.force_event.add(FE_STICK)\n\t\t\treturn self.action.pad(mapper, position, what)\n\t\telif what == LEFT and mapper.was_pressed(SCButtons.LPAD):\n\t\t\t# Just released\n\t\t\treturn self.action.pad(mapper, 0, what)\n\t\telif what == CPAD and mapper.is_pressed(SCButtons.CPAD):\n\t\t\treturn self.action.pad(mapper, position, what)\n\t\telif what == CPAD and mapper.was_pressed(SCButtons.CPAD):\n\t\t\t# Just released\n\t\t\treturn self.action.pad(mapper, 0, what)\n\t\telif mapper.is_pressed(SCButtons.RPAD):\n\t\t\t# what == RIGHT, there are only two options\n\t\t\treturn self.action.pad(mapper, position, what)\n\t\telif mapper.was_pressed(SCButtons.RPAD):\n\t\t\t# what == RIGHT, there are only two options, Just released\n\t\t\treturn self.action.pad(mapper, 0, what)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif what in (STICK, LEFT) and mapper.is_pressed(SCButtons.LPAD):\n\t\t\tif what == STICK: mapper.force_event.add(FE_STICK)\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\t\telif (what in (STICK, LEFT) and (mapper.was_pressed(SCButtons.LPAD)\n\t\t\t\t\tor mapper.was_pressed(STICKTILT))):\n\t\t\t# Just released\n\t\t\treturn self.action.whole(mapper, 0, 0, what)\n\t\telif what == RIGHT and mapper.is_pressed(SCButtons.RPAD):\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\t\telif what == RIGHT and mapper.was_pressed(SCButtons.RPAD):\n\t\t\t# Just released\n\t\t\treturn self.action.whole(mapper, 0, 0, what)\n\t\telif what == CPAD and mapper.is_pressed(SCButtons.CPAD):\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\t\telif what == CPAD and mapper.was_pressed(SCButtons.CPAD):\n\t\t\t# Just released\n\t\t\treturn self.action.whole(mapper, 0, 0, what)\n\t\telse:\n\t\t\t# Nothing is pressed, but finger moves over pad\n\t\t\tself.action.whole_blocked(mapper, x, y, what)\n\n\nclass TouchedModifier(Modifier):\n\tCOMMAND = \"touched\"\n\t\n\t\n\tdef describe(self, context):\n\t\tif context in (Action.AC_STICK, Action.AC_PAD):\n\t\t\treturn _(\"(when %s)\" % (self.COMMAND,)) + \"\\n\" + self.action.describe(context)\n\t\telse:\n\t\t\treturn _(\"(when %s)\" % (self.COMMAND,)) + \" \" + self.action.describe(context)\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\tself.action = self.action.compress()\n\t\treturn self\n\t\n\t\n\tdef _release(self, mapper):\n\t\treturn self.action.button_release(mapper)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif mapper.is_touched(what) and not mapper.was_touched(what):\n\t\t\tself.action.button_press(mapper)\n\t\t\tmapper.schedule(0.02, self._release)\n\n\nclass UntouchedModifier(TouchedModifier):\n\tCOMMAND = \"untouched\"\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif not mapper.is_touched(what) and mapper.was_touched(what):\n\t\t\tself.action.button_press(mapper)\n\t\t\tmapper.schedule(0.02, self._release)\n\n\nclass PressedModifier(Modifier):\n\tCOMMAND = \"pressed\"\n\t\n\t\n\tdef describe(self, context):\n\t\tif context in (Action.AC_STICK, Action.AC_PAD):\n\t\t\treturn _(\"(when pressed)\") + \"\\n\" + self.action.describe(context)\n\t\telse:\n\t\t\treturn _(\"(when pressed)\") + \" \" + self.action.describe(context)\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\tself.action = self.action.compress()\n\t\treturn self\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.action.button_press(mapper)\n\t\tmapper.schedule(0.02, self._release)\n\t\n\t\n\tdef _release(self, mapper):\n\t\treturn self.action.button_release(mapper)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tpass\n\n\nclass ReleasedModifier(PressedModifier):\n\tCOMMAND = \"released\"\n\t\n\tdef describe(self, context):\n\t\tif context in (Action.AC_STICK, Action.AC_PAD):\n\t\t\treturn _(\"(when released)\") + \"\\n\" + self.action.describe(context)\n\t\telse:\n\t\t\treturn _(\"(when released)\") + \" \" + self.action.describe(context)\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tpass\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tself.action.button_press(mapper)\n\t\tmapper.schedule(0.02, self._release)\n\n\nclass BallModifier(Modifier, WholeHapticAction):\n\t\"\"\"\n\tEmulates ball-like movement with inertia and friction.\n\t\n\tReacts only to \"whole\" or \"axis\" inputs and sends generated movements as\n\t\"add\" input to child action.\n\tTarget action has to have add(x, y) method defined.\n\t\n\t\"\"\"\n\tCOMMAND = \"ball\"\n\tPROFILE_KEY_PRIORITY = -6\n\tHAPTIC_FACTOR = 60.0\t# Just magic number\n\t\n\tDEFAULT_FRICTION = 10.0\n\tDEFAULT_MEAN_LEN = 10\n\tMIN_LIFT_VELOCITY = 0.2\t# If finger is lifter after movement slower than \n\t\t\t\t\t\t\t# this, roll doesn't happens\n\t\n\tdef __init__(self, *params):\n\t\tModifier.__init__(self, *params)\n\t\tWholeHapticAction.__init__(self)\n\t\n\t\n\tdef _mod_init(self, friction=DEFAULT_FRICTION, mass=80.0,\n\t\t\tmean_len=DEFAULT_MEAN_LEN, r=0.02, ampli=65536, degree=40.0):\n\t\tself.speed = (1.0, 1.0)\n\t\tself.friction = friction\n\t\tself._xvel = 0.0\n\t\tself._yvel = 0.0\n\t\tself._ampli  = ampli\n\t\tself._degree = degree\n\t\tself._radscale = (degree * PI / 180) / ampli\n\t\tself._mass = mass\n\t\tself._roll_task = None\n\t\tself._r = r\n\t\tself._I = (2 * self._mass * self._r**2) / 5.0\n\t\tself._a = self._r * self.friction / self._I\n\t\tself._xvel_dq = deque(maxlen=mean_len)\n\t\tself._yvel_dq = deque(maxlen=mean_len)\n\t\tself._lastTime = time.time()\n\t\tself._old_pos = None\n\t\n\t\n\tdef set_speed(self, x, y, *a):\n\t\tself.speed = (x, y)\n\t\tif self.action and (isinstance(self.action, CircularModifier)\n\t\t\tand hasattr(self.action, \"set_speed\")):\n\t\t\tself.action.set_speed(x, y, *a)\n\n\t\t# Reset calculated x and y velocities\n\t\tself._xvel = 0.0\n\t\tself._yvel = 0.0\n\t\n\t\n\tdef get_speed(self):\n\t\treturn self.speed\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn ( Action.MOD_SENSITIVITY | Action.MOD_FEEDBACK\n\t\t\t| Action.MOD_SMOOTH | Action.MOD_DEADZONE\n\t\t\t| Modifier.get_compatible_modifiers(self) )\n\t\n\t\n\tdef _stop(self):\n\t\t\"\"\" Stops rolling of the 'ball' \"\"\"\n\t\tself._xvel_dq.clear()\n\t\tself._yvel_dq.clear()\n\t\tif self._roll_task:\n\t\t\tself._roll_task.cancel()\n\t\t\tself._roll_task = None\n\t\n\t\n\tdef _add(self, dx, dy):\n\t\t# Compute instant velocity\n\t\ttry:\n\t\t\tself._xvel = sum(self._xvel_dq) / len(self._xvel_dq)\n\t\t\tself._yvel = sum(self._yvel_dq) / len(self._yvel_dq)\n\t\texcept ZeroDivisionError:\n\t\t\tself._xvel = 0.0\n\t\t\tself._yvel = 0.0\n\t\t\n\t\tself._xvel_dq.append(dx * self._radscale)\n\t\tself._yvel_dq.append(dy * self._radscale)\n\t\n\t\n\tdef _roll(self, mapper):\n\t\t# Compute time step\n\t\tt = time.time()\n\t\tdt, self._lastTime = t - self._lastTime, t\n\t\t\n\t\t# Free movement update velocity and compute movement\n\t\tself._xvel_dq.clear()\n\t\tself._yvel_dq.clear()\n\t\t\n\t\t_hyp = sqrt((self._xvel**2) + (self._yvel**2))\n\t\tif _hyp != 0.0:\n\t\t\t_ax = self._a * (abs(self._xvel) / _hyp)\n\t\t\t_ay = self._a * (abs(self._yvel) / _hyp)\n\t\telse:\n\t\t\t_ax = self._a\n\t\t\t_ay = self._a\n\t\t\n\t\t# Cap friction desceleration\n\t\t_dvx = min(abs(self._xvel), _ax * dt)\n\t\t_dvy = min(abs(self._yvel), _ay * dt)\n\t\t\n\t\t# compute new velocity\n\t\t_xvel = self._xvel - copysign(_dvx, self._xvel)\n\t\t_yvel = self._yvel - copysign(_dvy, self._yvel)\n\t\t\n\t\t# compute displacement\n\t\tdx = (((_xvel + self._xvel) / 2) * dt) / self._radscale\n\t\tdy = (((_yvel + self._yvel) / 2) * dt) / self._radscale\n\t\t\n\t\tself._xvel = _xvel\n\t\tself._yvel = _yvel\n\t\t\n\t\tself.action.add(mapper, dx * self.speed[0], dy * self.speed[1])\n\t\tif dx or dy:\n\t\t\tif self.haptic:\n\t\t\t\tWholeHapticAction.add(self, mapper, dx, dy)\n\t\t\tself._roll_task = mapper.schedule(0.02, self._roll)\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\tif data[BallModifier.COMMAND] is True:\n\t\t\t# backwards compatibility\n\t\t\treturn BallModifier(a)\n\t\telse:\n\t\t\targs = list(data[BallModifier.COMMAND])\n\t\t\targs.append(a)\n\t\t\treturn BallModifier(*args)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\t# Special cases just to make GUI look pretty\n\t\tif isinstance(self.action, MouseAction):\n\t\t\treturn _(\"Trackball\")\n\t\tif isinstance(self.action, XYAction):\n\t\t\tif isinstance(self.action.x, AxisAction) and isinstance(self.action.y, AxisAction):\n\t\t\t\tx, y = self.action.x.parameters[0], self.action.y.parameters[0]\n\t\t\t\tif x == Axes.ABS_X and y == Axes.ABS_Y:\n\t\t\t\t\treturn _(\"Mouse-like LStick\")\n\t\t\t\telse:\n\t\t\t\t\treturn _(\"Mouse-like RStick\")\n\t\t\tif isinstance(self.action.x, MouseAction) and isinstance(self.action.y, MouseAction):\n\t\t\t\tx, y = self.action.x.parameters[0], self.action.y.parameters[0]\n\t\t\t\tif x in (Rels.REL_HWHEEL, Rels.REL_WHEEL) and y in (Rels.REL_HWHEEL, Rels.REL_WHEEL):\n\t\t\t\t\treturn _(\"Mouse Wheel\")\n\t\t\n\t\treturn _(\"Ball(%s)\") % (self.action.describe(context))\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn self._mod_to_string(self.strip_defaults(), multiline, pad)\n\t\n\t\n\tdef cancel(self, mapper):\n\t\tModifier.cancel(self, mapper)\n\t\tself._stop()\n\t\n\t\n\tdef pad(self, mapper, position, what):\n\t\tself.whole(mapper, position, 0, what)\n\t\n\t\n\tdef change(self, mapper, dx, dy, what):\n\t\tif what in (None, STICK) or (mapper.controller_flags() & ControllerFlags.HAS_RSTICK and what == RIGHT):\n\t\t\treturn self.action.change(mapper, dx, dy, what)\n\t\tif mapper.is_touched(what):\n\t\t\tif mapper.was_touched(what):\n\t\t\t\tt = time.time()\n\t\t\t\tdt = t - self._lastTime\n\t\t\t\tif dt < 0.0075: return\n\t\t\t\tself._lastTime = t\n\t\t\t\tself._add(dx / dt, dy / dt)\n\t\t\t\tself.action.add(mapper, dx, dy)\n\t\t\telse:\n\t\t\t\tself._stop()\n\t\telif mapper.was_touched(what):\n\t\t\tvelocity = sqrt(self._xvel * self._xvel + self._yvel * self._yvel)\n\t\t\tif velocity > BallModifier.MIN_LIFT_VELOCITY:\n\t\t\t\tself._roll(mapper)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif mapper.controller_flags() & ControllerFlags.HAS_RSTICK and what == RIGHT:\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\t\tif mapper.is_touched(what):\n\t\t\tif mapper.is_touched(what) and not mapper.was_touched(what):\n\t\t\t\tmapper.mouse.clearRemainders()\n\n\t\t\tif self._old_pos and mapper.was_touched(what):\n\t\t\t\tt = time.time()\n\t\t\t\tdt = t - self._lastTime\n\t\t\t\tif dt < 0.0075: return\n\t\t\t\tself._lastTime = t\n\t\t\t\tdx, dy = x - self._old_pos[0], self._old_pos[1] - y\n\t\t\t\tself._add(dx / dt, dy / dt)\n\t\t\t\tself.action.add(mapper, dx * self.speed[0], dy * self.speed[1])\n\t\t\telse:\n\t\t\t\tself._stop()\n\t\t\tself._old_pos = x, y\n\t\telif mapper.was_touched(what):\n\t\t\tself._old_pos = None\n\t\t\tvelocity = sqrt(self._xvel * self._xvel + self._yvel * self._yvel)\n\t\t\tif velocity > BallModifier.MIN_LIFT_VELOCITY:\n\t\t\t\tself._roll(mapper)\n\t\telif what == STICK:\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\t\n\t\n\tdef set_haptic(self, hd):\n\t\tif self.action and hasattr(self.action, \"set_haptic\"):\n\t\t\tself.action.set_haptic(hd)\n\t\telse:\n\t\t\tWholeHapticAction.set_haptic(self, hd)\n\t\n\t\n\tdef get_haptic(self):\n\t\tif self.action and hasattr(self.action, \"get_haptic\"):\n\t\t\treturn self.action.get_haptic()\n\t\telse:\n\t\t\treturn WholeHapticAction.get_haptic(self)\n\t\n\t\n\tdef compress(self):\n\t\t# ball(circular(...) has to be turned around\n\t\tif isinstance(self.action, CircularModifier):\n\t\t\tcm = self.action\n\t\t\tself.action = cm.action\n\t\t\tcm.action = self\n\t\t\treturn cm\n\t\treturn self\n\n\nclass DeadzoneModifier(Modifier):\n\tCOMMAND = \"deadzone\"\n\tJUMP_HARDCODED_LIMIT = 5\n\t\n\tdef _mod_init(self, *params):\n\t\tif len(params) < 1: raise TypeError(\"Not enough parameters\")\n\t\tif type(params[0]) is str:\n\t\t\tself.mode = params[0]\n\t\t\tif hasattr(self, \"mode_\" + self.mode):\n\t\t\t\tself._convert = getattr(self, \"mode_\" + self.mode)\n\t\t\telse:\n\t\t\t\traise ValueError(\"Invalid deadzone mode\")\n\t\t\tparams = params[1:]\n\t\t\tif len(params) < 1: raise TypeError(\"Not enough parameters\")\n\t\telse:\n\t\t\t# 'cut' mode is default\n\t\t\tself.mode = CUT\n\t\t\tself._convert = self.mode_CUT\n\t\t\n\t\tself.lower = int(params[0])\n\t\tself.upper = int(params[1]) if len(params) == 2 else STICK_PAD_MAX\n\t\n\t\n\tdef mode_CUT(self, x, y, range):\n\t\t\"\"\"\n\t\tIf input value is out of deadzone range, output value is zero\n\t\t\"\"\"\n\t\tif y == 0:\n\t\t\t# Small optimalization for 1D input, for example trigger\n\t\t\treturn (0 if abs(x) < self.lower or abs(x) > self.upper else x), 0\n\t\tdistance = sqrt(x*x + y*y)\n\t\tif distance < self.lower or distance > self.upper:\n\t\t\treturn 0, 0\n\t\treturn x, y\n\t\n\t\n\tdef mode_ROUND(self, x, y, range):\n\t\t\"\"\"\n\t\tIf input value bellow deadzone range, output value is zero\n\t\tIf input value is above deadzone range,\n\t\toutput value is 1 (or maximum allowed)\n\t\t\"\"\"\n\t\tif y == 0:\n\t\t\t# Small optimalization for 1D input, for example trigger\n\t\t\tif abs(x) > self.upper:\n\t\t\t\treturn copysign(range, x), 0\n\t\t\treturn (0 if abs(x) < self.lower else x), 0\n\t\tdistance = sqrt(x*x + y*y)\n\t\tif distance < self.lower:\n\t\t\treturn 0, 0\n\t\tif distance > self.upper:\n\t\t\tangle = atan2(x, y)\n\t\t\treturn range * sin(angle), range * cos(angle)\n\t\treturn x, y\n\t\n\t\n\tdef mode_LINEAR(self, x, y, range):\n\t\t\"\"\"\n\t\tInput value is scaled, so entire output range is covered by\n\t\treduced input range of deadzone.\n\t\t\"\"\"\n\t\tif y == 0:\n\t\t\t# Small optimalization for 1D input, for example trigger\n\t\t\treturn copysign(\n\t\t\t\tclamp(\n\t\t\t\t\t0,\n\t\t\t\t\t((x - self.lower) / (self.upper - self.lower)) * range,\n\t\t\t\t\trange),\n\t\t\t\tx\n\t\t\t), 0\n\t\tdistance = clamp(self.lower, sqrt(x*x + y*y), self.upper)\n\t\tdistance = (distance - self.lower) / (self.upper - self.lower) * range\n\t\t\n\t\tangle = atan2(x, y)\n\t\treturn distance * sin(angle), distance * cos(angle)\n\t\n\t\n\tdef mode_MINIMUM(self, x, y, range):\n\t\t\"\"\"\n\t\thttps://github.com/kozec/sc-controller/issues/356\n\t\tInversion of LINEAR; input value is scaled so entire input range is\n\t\tmapped to range of deadzone.\n\t\t\"\"\"\n\t\tif y == 0:\n\t\t\t# Small optimalization for 1D input, for example trigger\n\t\t\tif abs(x) < DeadzoneModifier.JUMP_HARDCODED_LIMIT:\n\t\t\t\treturn 0, 0\n\t\t\treturn (copysign(\n\t\t\t\t\t\t(float(abs(x)) / range * (self.upper - self.lower))\n\t\t\t\t\t\t+ self.lower, x), 0)\n\t\tdistance = sqrt(x*x + y*y)\n\t\tif distance < DeadzoneModifier.JUMP_HARDCODED_LIMIT:\n\t\t\treturn 0, 0\n\t\tdistance = (distance / range * (self.upper - self.lower)) + self.lower\n\t\t\n\t\tangle = atan2(x, y)\n\t\treturn distance * sin(angle), distance * cos(angle)\n\n\tdef _convert_trigger_stick_range(self, position, trigger_range):\n\t\tresult = clamp(0,\n\t\t    (position / trigger_range) * STICK_PAD_MAX,\n\t\t    STICK_PAD_MAX)\n\n\t\treturn result\n\n\tdef _convert_stick_trigger_range(self, position):\n\t\tresult = clamp(0,\n\t\t    (position / STICK_PAD_MAX) * TRIGGER_MAX,\n\t\t    TRIGGER_MAX)\n\n\t\treturn result\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\treturn DeadzoneModifier(\n\t\t\tdata[\"deadzone\"][\"mode\"] if \"mode\" in data[\"deadzone\"] else CUT,\n\t\t\tdata[\"deadzone\"][\"lower\"] if \"lower\" in data[\"deadzone\"] else STICK_PAD_MIN,\n\t\t\tdata[\"deadzone\"][\"upper\"] if \"upper\" in data[\"deadzone\"] else STICK_PAD_MAX,\n\t\t\ta\n\t\t)\n\t\n\t\n\tdef compress(self):\n\t\tself.action = self.action.compress()\n\t\tif isinstance(self.action, BallModifier) and self.mode == MINIMUM:\n\t\t\t# Special case where BallModifier has to be applied before\n\t\t\t# deadzone is computed\n\t\t\tballmod = self.action\n\t\t\tself.action, ballmod.action = ballmod.action, self\n\t\t\treturn ballmod\n\t\telif isinstance(self.action, GyroAbsAction):\n\t\t\t# Another special case, GyroAbs has to handle deadzone\n\t\t\t# only after math is finished\n\t\t\tself.action._deadzone_fn = self._convert\n\t\t\treturn self.action\n\t\treturn self\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Modifier '%s', %s>\" % (self.COMMAND, self.action)\n\t\n\t__repr__ = __str__\n\t\n\t\n\tdef describe(self, context):\n\t\tdsc = self.action.describe(context)\n\t\tif \"\\n\" in dsc:\n\t\t\treturn \"%s\\n(with deadzone)\" % (dsc,)\n\t\telse:\n\t\t\treturn \"%s (with deadzone)\" % (dsc,)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tparams = []\n\t\tif self.mode != CUT:\n\t\t\tparams.append(self.mode)\n\t\tparams.append(str(self.lower))\n\t\tif self.upper != STICK_PAD_MAX:\n\t\t\tparams.append(str(self.upper))\n\t\tparams.append(self.action.to_string(multiline))\n\t\n\t\treturn \"deadzone(%s)\" % ( \", \".join(params), )\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\t# Need to convert trigger value to stick range for deadzone modifier\n\t\t# calcs to work as intended\n\t\tposition = self._convert_trigger_stick_range(position, TRIGGER_MAX)\n\n\t\t# Perform dead zone calculations\n\t\tposition = self._convert(position, 0, STICK_PAD_MAX)\n\n\t\t# Convert calculated stick position value back to applicable\n\t\t# position in trigger range\n\t\tposition = self._convert_stick_trigger_range(position[0])\n\t\t# Invoke trigger action with new value\n\t\treturn self.action.trigger(mapper, position, old_position)\n\n\t\n\tdef axis(self, mapper, position, what):\n\t\tposition = self._convert(position, 0, STICK_PAD_MAX)\n\t\treturn self.action.axis(mapper, position, what)\n\t\n\t\n\tdef pad(self, mapper, position, what):\n\t\tposition = self._convert(position, 0, STICK_PAD_MAX)\n\t\treturn self.action.pad(mapper, position, what)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tx, y = self._convert(x, y, STICK_PAD_MAX)\n\t\treturn self.action.whole(mapper, x, y, what)\n\t\n\t\n\tdef gyro(self, mapper, pitch, yaw, roll, q1, q2, q3, q4):\n\t\treturn self.action.gyro(mapper, pitch, yaw, roll, q1, q2, q3, q4)\n\n\nclass ModeModifier(Modifier):\n\tCOMMAND = \"mode\"\n\tPROFILE_KEYS = (\"modes\",)\n\tMIN_TRIGGER = 2\t\t# When trigger is bellow this position, list of held_triggers is cleared\n\tMIN_STICK = 2\t\t# When abs(stick) < MIN_STICK, stick is considered released and held_sticks is cleared\n\tPROFILE_KEY_PRIORITY = 2\n\t\n\tdef __init__(self, *stuff):\n\t\t# TODO: Better documentation for this. For now, using shell\n\t\t# TODO: and range as condition is not documented\n\t\tModifier.__init__(self)\n\t\tself.default = None\n\t\tself.mods = OrderedDict()\n\t\tself.held_buttons = set()\n\t\tself.held_sticks = set()\n\t\tself.held_triggers = {}\n\t\tself.old_action = None\n\t\tself.shell_commands = {}\n\t\tself.shell_timeout = 0.5\n\t\tself.timeout = DoubleclickModifier.DEAFAULT_TIMEOUT\n\t\t\n\t\t# ShellCommandAction cannot be imported normally, it would create\n\t\t# import cycle of hell\n\t\tShellCommandAction = Action.ALL['shell']\n\t\tbutton = None\n\t\tfor i in stuff:\n\t\t\tif self.default is not None:\n\t\t\t\t# Default has to be last parameter\n\t\t\t\traise ValueError(\"Invalid parameters for 'mode'\")\n\t\t\tif isinstance(i, ShellCommandAction) and button is None:\n\t\t\t\t# 'shell' can be used instead of button\n\t\t\t\tbutton = i\n\t\t\telif isinstance(i, Action) and button is None:\t\n\t\t\t\tself.default = i\n\t\t\telif isinstance(i, Action):\n\t\t\t\tself.mods[button] = i\n\t\t\t\tbutton = None\n\t\t\telif isinstance(i, RangeOP) or i in SCButtons.__members__.values():\n\t\t\t\tbutton = i\n\t\t\telse:\n\t\t\t\traise ValueError(\"Invalid parameter for 'mode': %s\" % (i,))\n\t\tself.make_checks()\n\t\tif self.default is None:\n\t\t\tif isinstance(button, ShellCommandAction):\n\t\t\t\tself.default = button\n\t\t\telse:\n\t\t\t\tself.default = NoAction()\n\t\n\t\n\tdef make_checks(self):\n\t\tself.checks = []\n\t\tself.shell_commands = {}\n\t\tShellCommandAction = Action.ALL['shell']\n\t\tfor c, action in self.mods.items():\n\t\t\tif isinstance(c, RangeOP):\n\t\t\t\tself.checks.append(( c, action ))\n\t\t\telif isinstance(c, ShellCommandAction):\n\t\t\t\tself.shell_commands[c.command] = c\n\t\t\t\tself.checks.append(( self.make_shell_check(c), action ))\n\t\t\telse:\n\t\t\t\tself.checks.append(( self.make_button_check(c), action ))\n\t\n\t\n\tdef get_child_actions(self):\n\t\trv = list(self.mods.values()) + list(self.shell_commands.values())\n\t\tif self.default is not None:\n\t\t\trv += [ self.default ]\n\t\treturn rv\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\targs = []\n\t\tfor button in data[ModeModifier.PROFILE_KEYS[0]]:\n\t\t\tif hasattr(SCButtons, button):\n\t\t\t\targs += [ getattr(SCButtons, button), parser.from_json_data(data[ModeModifier.PROFILE_KEYS[0]][button]) ]\n\t\tif a:\n\t\t\targs += [ a ]\n\t\tmm = ModeModifier(*args)\n\t\tif \"name\" in data:\n\t\t\tmm.name = data[\"name\"]\n\t\treturn mm\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\trv = 0\n\t\tfor action in self.mods.values():\n\t\t\trv |= action.get_compatible_modifiers()\n\t\tif self.default:\n\t\t\trv |= self.default.get_compatible_modifiers()\n\t\treturn rv\n\t\n\t\n\tdef strip(self):\n\t\t# Returns default action or action assigned to first modifier\n\t\tif self.default:\n\t\t\treturn self.default.strip()\n\t\tif len(self.mods):\n\t\t\treturn next(itertools.islice(self.mods.values(), 0, 1)).strip()\n\t\t# Empty ModeModifier\n\t\treturn NoAction()\n\t\n\t\n\tdef compress(self):\n\t\tif self.default:\n\t\t\tself.default = self.default.compress()\n\t\tfor check in self.mods:\n\t\t\tself.mods[check] = self.mods[check].compress()\n\t\tself.make_checks()\n\t\treturn self\n\t\n\t\n\tdef __str__(self):\n\t\trv = [ ]\n\t\tfor check in self.mods:\n\t\t\trv += [ nameof(check), self.mods[check] ]\n\t\tif self.default is not None:\n\t\t\trv += [ self.default ]\n\t\treturn \"<Modifier '%s', %s>\" % (self.COMMAND, rv)\n\t\n\t__repr__ = __str__\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tl = []\n\t\tif self.default : l.append(self.default)\n\t\tfor check in self.mods:\n\t\t\tl.append(self.mods[check])\n\t\treturn \"\\n\".join([ x.describe(context) for x in l ])\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tif multiline:\n\t\t\trv = [ (\" \" * pad) + \"mode(\" ]\n\t\t\tfor check in self.mods:\n\t\t\t\ta_str = NameModifier.unstrip(self.mods[check]).to_string(True).split(\"\\n\")\n\t\t\t\ta_str[0] = (\" \" * pad) + \"  \" + (nameof(check) + \",\").ljust(11) + a_str[0]\t# Key has to be one of SCButtons\n\t\t\t\tfor i in range(1, len(a_str)):\n\t\t\t\t\ta_str[i] = (\" \" * pad) + \"  \" + a_str[i]\n\t\t\t\ta_str[-1] = a_str[-1] + \",\"\n\t\t\t\trv += a_str\n\t\t\tif self.default is not None:\n\t\t\t\ta_str = [\n\t\t\t\t\t(\" \" * pad) + \"  \" + x\n\t\t\t\t\tfor x in NameModifier.unstrip(self.default).to_string(True).split(\"\\n\")\n\t\t\t\t]\n\t\t\t\trv += a_str\n\t\t\tif rv[-1][-1] == \",\":\n\t\t\t\trv[-1] = rv[-1][0:-1]\n\t\t\trv += [ (\" \" * pad) + \")\" ]\n\t\t\treturn \"\\n\".join(rv)\n\t\telse:\n\t\t\trv = [ ]\n\t\t\tfor check in self.mods:\n\t\t\t\trv += [ nameof(check), NameModifier.unstrip(self.mods[check]).to_string(False) ]\n\t\t\tif self.default is not None:\n\t\t\t\trv += [ NameModifier.unstrip(self.default).to_string(False) ]\n\t\t\treturn \"mode(\" + \", \".join(rv) + \")\"\n\t\n\t\n\tdef cancel(self, mapper):\n\t\tfor action in self.mods.values():\n\t\t\taction.cancel(mapper)\n\t\tself.default.cancel(mapper)\n\t\n\t\n\tdef select(self, mapper):\n\t\t\"\"\"\n\t\tSelects action by pressed button.\n\t\t\"\"\"\n\t\tfor check, action in self.checks:\n\t\t\tif check(mapper):\n\t\t\t\treturn action\n\t\treturn self.default\n\t\n\t\n\tdef select_w_check(self, mapper):\n\t\t\"\"\"\n\t\tAs select, but returns matched check as well.\n\t\t\"\"\"\n\t\tfor check, action in self.checks:\n\t\t\tif check(mapper):\n\t\t\t\treturn check, action\n\t\treturn lambda *a:True, self.default\n\t\n\t\n\t@staticmethod\n\tdef make_button_check(button):\n\t\tdef cb(mapper):\n\t\t\treturn mapper.is_pressed(button)\n\t\t\n\t\tcb.name = button.name\t# So nameof() still works on keys in self.mods\n\t\treturn cb\n\t\n\t\n\t@staticmethod\n\tdef make_shell_check(c):\n\t\tdef cb(mapper):\n\t\t\ttry:\n\t\t\t\treturn c.__proc.poll() == 0\n\t\t\texcept:\n\t\t\t\treturn False\n\t\t\n\t\tc.name = cb.name = c.to_string()\t# So nameof() still works on keys in self.mods\n\t\tc.__proc = None\n\t\treturn cb\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tif len(self.shell_commands) > 0:\n\t\t\t# https://github.com/kozec/sc-controller/issues/427\n\t\t\t# If 'shell' is used as any condition, all shell commands\n\t\t\t# are executed and ModeShift waits up to 500ms for them\n\t\t\t# to terminate. Then, if command returned zero exit code\n\t\t\t# it's considered as 'true' condition.\n\t\t\tfor c in self.shell_commands.values():\n\t\t\t\tc.__proc = c.button_press(mapper)\n\t\t\tself.shell_timeout = 0.5\n\t\t\tmapper.schedule(0, self.check_shell_commands)\n\t\t\treturn\n\t\t\n\t\tsel = self.select(mapper)\n\t\tself.held_buttons.add(sel)\n\t\treturn sel.button_press(mapper)\n\t\n\t\n\tdef check_shell_commands(self, mapper):\n\t\tfor c in self.shell_commands.values():\n\t\t\tif c.__proc and c.__proc.poll() == 0:\n\t\t\t\tsel = self.select(mapper)\n\t\t\t\tself.kill_shell_commands()\n\t\t\t\tself.held_buttons.add(sel)\n\t\t\t\treturn sel.button_press(mapper)\n\t\t\n\t\tself.shell_timeout -= 0.05\n\t\tif self.shell_timeout > 0:\n\t\t\tmapper.schedule(0.05, self.check_shell_commands)\n\t\telse:\n\t\t\t# time is up, kill all processes and execute what's left\n\t\t\tself.kill_shell_commands()\n\t\t\tsel = self.select(mapper)\n\t\t\tself.held_buttons.add(sel)\n\t\t\treturn sel.button_press(mapper)\n\t\n\t\n\tdef kill_shell_commands(self):\n\t\tfor c in self.shell_commands.values():\n\t\t\ttry:\n\t\t\t\tif c.__proc: c.__proc.kill()\n\t\t\texcept: pass\n\t\t\tc.__proc = None\n\t\n\t\n\tdef button_release(self, mapper):\n\t\t# Releases all held buttons, not just button that matches\n\t\t# currently pressed modifier\n\t\tfor b in self.held_buttons:\n\t\t\tb.button_release(mapper)\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\tif position < ModeModifier.MIN_TRIGGER:\n\t\t\tfor b in self.held_triggers:\n\t\t\t\tb.trigger(mapper, 0, self.held_triggers[b])\n\t\t\tself.held_triggers = {}\n\t\t\treturn False\n\t\telse:\n\t\t\tsel = self.select(mapper)\n\t\t\tself.held_triggers[sel] = position\n\t\t\treturn sel.trigger(mapper, position, old_position)\n\t\n\t\n\tdef axis(self, mapper, position, what):\n\t\treturn self.select(mapper).axis(mapper, position, what)\n\t\n\t\n\tdef gyro(self, mapper, pitch, yaw, roll, *q):\n\t\tsel = self.select(mapper)\n\t\tif sel is not self.old_action:\n\t\t\tif self.old_action:\n\t\t\t\tself.old_action.gyro(mapper, 0, 0, 0, *q)\n\t\t\tself.old_action = sel\n\t\treturn sel.gyro(mapper, pitch, yaw, roll, *q)\n\t\n\t\n\tdef pad(self, mapper, position, what):\n\t\treturn self.select(mapper).pad(mapper, position, what)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif what == STICK:\n\t\t\tif abs(x) < ModeModifier.MIN_STICK and abs(y) < ModeModifier.MIN_STICK:\n\t\t\t\tfor check, action in self.held_sticks:\n\t\t\t\t\taction.whole(mapper, 0, 0, what)\n\t\t\t\tself.held_sticks.clear()\n\t\t\telse:\n\t\t\t\tac, active = self.select_w_check(mapper)\n\t\t\t\tself.held_sticks.add(( ac, active ))\n\t\t\t\tfor check, action in list(self.held_sticks):\n\t\t\t\t\tif check == ac or check(mapper):\n\t\t\t\t\t\taction.whole(mapper, x, y, what)\n\t\t\t\t\telse:\n\t\t\t\t\t\taction.whole(mapper, 0, 0, what)\n\t\t\t\t\t\tself.held_sticks.remove(( check, action ))\n\t\t\tmapper.force_event.add(FE_STICK)\n\t\telse:\n\t\t\tsel = self.select(mapper)\n\t\t\tif sel is not self.old_action:\n\t\t\t\tmapper.set_button(what, False)\n\t\t\t\tif self.old_action:\n\t\t\t\t\tself.old_action.whole(mapper, 0, 0, what)\n\t\t\t\tself.old_action = sel\n\t\t\t\trv = sel.whole(mapper, x, y, what)\n\t\t\t\tmapper.set_button(what, True)\n\t\t\t\treturn rv\n\t\t\telse:\n\t\t\t\treturn sel.whole(mapper, x, y, what)\n\n\nclass DoubleclickModifier(Modifier, HapticEnabledAction):\n\tCOMMAND = \"doubleclick\"\n\tDEAFAULT_TIMEOUT = 0.2\n\tTIMEOUT_KEY = \"time\"\n\tPROFILE_KEY_PRIORITY = 3\n\t\n\tdef __init__(self, doubleclickaction, normalaction=None, time=None):\n\t\tModifier.__init__(self)\n\t\tHapticEnabledAction.__init__(self)\n\t\tself.action = doubleclickaction\n\t\tself.normalaction = normalaction or NoAction()\n\t\tself.holdaction = NoAction()\n\t\tself.actions = ( self.action, self.normalaction, self.holdaction )\n\t\tself.timeout = time or DoubleclickModifier.DEAFAULT_TIMEOUT\n\t\tself.waiting_task = None\n\t\tself.pressed = False\n\t\tself.active = None\n\t\n\t\n\tdef get_child_actions(self):\n\t\treturn self.action, self.normalaction, self.holdaction\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\targs = [ parser.from_json_data(data[DoubleclickModifier.COMMAND]), a ]\n\t\ta = DoubleclickModifier(*args)\n\t\tif DoubleclickModifier.TIMEOUT_KEY in data:\n\t\t\ta.timeout = data[DoubleclickModifier.TIMEOUT_KEY]\n\t\treturn a\n\t\n\t\n\tdef strip(self):\n\t\tif self.holdaction:\n\t\t\treturn self.holdaction.strip()\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\tself.action = self.action.compress()\n\t\tself.holdaction = self.holdaction.compress()\n\t\tself.normalaction = self.normalaction.compress()\n\t\t\n\t\tfor a in (self.holdaction, self.normalaction):\n\t\t\tif isinstance(a, HoldModifier):\n\t\t\t\tself.holdaction = a.holdaction or self.holdaction\n\t\t\t\tself.normalaction = a.normalaction or self.normalaction\n\t\t\n\t\tif isinstance(self.action, HoldModifier):\n\t\t\tself.holdaction = self.action.holdaction\n\t\t\tself.action = self.action.normalaction\n\t\treturn self\n\t\n\t\n\tdef __str__(self):\n\t\tl = [ self.action ]\n\t\tif self.normalaction:\n\t\t\tl += [ self.normalaction ]\n\t\treturn \"<Modifier %s dbl='%s' hold='%s' normal='%s'>\" % (\n\t\t\tself.COMMAND, self.action, self.holdaction, self.normalaction )\n\t\n\t__repr__ = __str__\n\t\n\t\n\tdef describe(self, context):\n\t\tl = [ ]\n\t\tif self.action:\n\t\t\tl += [ self.action ]\n\t\tif self.holdaction:\n\t\t\tl += [ self.holdaction ]\n\t\tif self.normalaction:\n\t\t\tl += [ self.normalaction ]\n\t\treturn \"\\n\".join([ x.describe(context) for x in l ])\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\ttimeout = \"\"\n\t\tif DoubleclickModifier.DEAFAULT_TIMEOUT != self.timeout:\n\t\t\ttimeout = \", %s\" % (self.timeout)\n\t\tif self.action and self.normalaction and self.holdaction:\n\t\t\treturn \"doubleclick(%s, hold(%s, %s)%s)\" % (\n\t\t\t\tNameModifier.unstrip(self.action).to_string(multiline, pad),\n\t\t\t\tNameModifier.unstrip(self.holdaction).to_string(multiline, pad),\n\t\t\t\tNameModifier.unstrip(self.normalaction).to_string(multiline, pad),\n\t\t\t\ttimeout\n\t\t\t)\n\t\telif self.action and self.normalaction and not self.holdaction:\n\t\t\treturn \"doubleclick(%s, %s%s)\" % (\n\t\t\t\tNameModifier.unstrip(self.action).to_string(multiline, pad),\n\t\t\t\tNameModifier.unstrip(self.normalaction).to_string(multiline, pad),\n\t\t\t\ttimeout\n\t\t\t)\n\t\telif not self.action and self.normalaction and self.holdaction:\n\t\t\treturn \"hold(%s, %s%s)\" % (\n\t\t\t\tNameModifier.unstrip(self.holdaction).to_string(multiline, pad),\n\t\t\t\tNameModifier.unstrip(self.normalaction).to_string(multiline, pad),\n\t\t\t\ttimeout\n\t\t\t)\n\t\telif not self.action and not self.normalaction and self.holdaction:\n\t\t\treturn \"hold(None, %s%s)\" % (\n\t\t\t\tNameModifier.unstrip(self.holdaction).to_string(multiline, pad),\n\t\t\t\ttimeout\n\t\t\t)\n\t\telif self.action and not self.normalaction and not self.holdaction:\n\t\t\treturn \"doubleclick(None, %s%s)\" % (\n\t\t\t\tNameModifier.unstrip(self.action).to_string(multiline, pad),\n\t\t\t\ttimeout\n\t\t\t)\n\t\treturn NameModifier.unstrip(\n\t\t\t\tself.action or self.normalaction or self.holdaction\n\t\t\t).to_string(multiline, pad)\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.pressed = True\n\t\tif self.waiting_task:\n\t\t\t# Double-click happened\n\t\t\tmapper.cancel_task(self.waiting_task)\n\t\t\tself.waiting_task = None\n\t\t\tself.active = self.action\n\t\t\tself.active.button_press(mapper)\n\t\telse:\n\t\t\t# First click, start the timer\n\t\t\tself.waiting_task = mapper.schedule(self.timeout, self.on_timeout)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tself.pressed = False\n\t\tif self.waiting_task and self.active is None and not self.action:\n\t\t\t# In HoldModifier, button released before timeout\n\t\t\tmapper.cancel_task(self.waiting_task)\n\t\t\tself.waiting_task = None\n\t\t\tif self.normalaction:\n\t\t\t\tself.normalaction.button_press(mapper)\n\t\t\t\tmapper.schedule(0.02, self.normalaction.button_release)\n\t\telif self.active:\n\t\t\t# Released held button\n\t\t\tself.active.button_release(mapper)\n\t\t\tself.active = None\n\t\n\t\n\tdef on_timeout(self, mapper, *a):\n\t\tif self.waiting_task:\n\t\t\tself.waiting_task = None\n\t\t\tif self.pressed:\n\t\t\t\t# Timeouted while button is still pressed\n\t\t\t\tself.active = self.holdaction if self.holdaction else self.normalaction\n\t\t\t\tif self.haptic:\n\t\t\t\t\tmapper.send_feedback(self.haptic)\n\t\t\t\tself.active.button_press(mapper)\n\t\t\telif self.normalaction:\n\t\t\t\t# User did short click and nothing else\n\t\t\t\tself.normalaction.button_press(mapper)\n\t\t\t\tmapper.schedule(0.02, self.normalaction.button_release)\n\n\nclass HoldModifier(DoubleclickModifier):\n\t# Hold modifier is implemented as part of DoubleclickModifier, because\n\t# situation when both are assigned to same button needs to be treated\n\t# specially.\n\tCOMMAND = \"hold\"\n\tPROFILE_KEY_PRIORITY = 4\n\n\tdef __init__(self, holdaction, normalaction=None, time=None):\n\t\tDoubleclickModifier.__init__(self, NoAction(), normalaction, time)\n\t\tself.holdaction = holdaction\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\tif isinstance(a, DoubleclickModifier):\n\t\t\ta.holdaction = parser.from_json_data(data[HoldModifier.COMMAND])\n\t\telse:\n\t\t\targs = [ parser.from_json_data(data[HoldModifier.COMMAND]), a ]\n\t\t\ta = HoldModifier(*args)\n\t\tif DoubleclickModifier.TIMEOUT_KEY in data:\n\t\t\ta.timeout = data[DoubleclickModifier.TIMEOUT_KEY]\n\t\tif isinstance(a.normalaction, FeedbackModifier):\n\t\t\t# Ugly hack until profile file is redone\n\t\t\tmod = a.normalaction\n\t\t\ta.normalaction = mod.action\n\t\t\tif hasattr(a.normalaction, \"set_haptic\"):\n\t\t\t\ta.normalaction.set_haptic(None)\n\t\t\tmod.action = a\n\t\t\tmod.action.set_haptic(mod.haptic)\n\t\t\ta = mod\n\t\treturn a\n\t\n\t\n\tdef compress(self):\n\t\tself.action = self.action.compress()\n\t\tself.holdaction = self.holdaction.compress()\n\t\tself.normalaction = self.normalaction.compress()\n\t\t\n\t\tfor a in (self.action, self.normalaction):\n\t\t\tif isinstance(a, DoubleclickModifier):\n\t\t\t\tself.action = a.action or self.action\n\t\t\t\tself.normalaction = a.normalaction or self.normalaction\n\t\t\n\t\tif isinstance(self.holdaction, DoubleclickModifier):\n\t\t\tself.action = self.holdaction.action\n\t\t\tself.holdaction = self.holdaction.normalaction\n\t\treturn self\n\n\nclass SensitivityModifier(Modifier):\n\t\"\"\"\n\tSets action sensitivity, if action supports it.\n\tAction that supports such setting has set_speed(x, y, z)\n\tand get_speed() methods defined.\n\t\n\tDoes nothing otherwise.\n\t\"\"\"\n\tCOMMAND = \"sens\"\n\tPROFILE_KEYS = (\"sensitivity\",)\n\tPROFILE_KEY_PRIORITY = -5\n\t\n\tdef _mod_init(self, *speeds):\n\t\tself.speeds = []\n\t\tfor s in speeds:\n\t\t\tif type(s) in (int, float):\n\t\t\t\tself.speeds.append(float(s))\n\t\twhile len(self.speeds) < 3:\n\t\t\tself.speeds.append(1.0)\n\t\tif self.action:\n\t\t\ta = self.action\n\t\t\twhile a:\n\t\t\t\tif hasattr(a, \"set_speed\"):\n\t\t\t\t\ta.set_speed(*self.speeds)\n\t\t\t\t\tbreak\n\t\t\t\tif hasattr(a, \"action\"):\n\t\t\t\t\ta = a.action\n\t\t\t\telse:\n\t\t\t\t\tbreak\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\tif a:\n\t\t\targs = list(data[\"sensitivity\"])\n\t\t\targs.append(a)\n\t\t\treturn SensitivityModifier(*args)\n\t\t# Adding sensitivity to NoAction makes no sense\n\t\treturn a\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\treturn self.action.compress()\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn self.action.describe(context)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tspeeds = [] + self.speeds\n\t\twhile len(speeds) > 1 and speeds[-1] == 1.0:\n\t\t\tspeeds = speeds[0:-1]\n\t\treturn self._mod_to_string(speeds, multiline, pad)\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Sensitivity=%s, %s>\" % (self.speeds, self.action)\n\n\nclass FeedbackModifier(Modifier):\n\t\"\"\"\n\tEnables feedback for action, action supports it.\n\tAction that supports feedback has to have set_haptic(hapticdata)\n\tmethod defined.\n\n\tDoes nothing otherwise.\n\t\"\"\"\n\tCOMMAND = \"feedback\"\n\tPROFILE_KEY_PRIORITY = -4\n\t\n\tdef _mod_init(self, position, amplitude=512, frequency=4, period=1024, count=1):\n\t\tself.haptic = HapticData(position, amplitude, frequency, period, count)\n\t\tif self.action:\n\t\t\ta = self.action\n\t\t\twhile a:\n\t\t\t\tif hasattr(a, \"set_haptic\"):\n\t\t\t\t\ta.set_haptic(self.haptic)\n\t\t\t\t\tbreak\n\t\t\t\tif hasattr(a, \"action\"):\n\t\t\t\t\ta = a.action\n\t\t\t\telse:\n\t\t\t\t\tbreak\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\targs = list(data[FeedbackModifier.COMMAND])\n\t\tif hasattr(HapticPos, args[0]):\n\t\t\targs[0] = getattr(HapticPos, args[0])\n\t\targs.append(a)\n\t\treturn FeedbackModifier(*args)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn self.action.describe(context)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn self._mod_to_string(self.strip_defaults(), multiline, pad)\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<with Feedback %s>\" % (self.action,)\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\treturn self.action.compress()\n\n\nclass RotateInputModifier(Modifier):\n\t\"\"\" Rotates ball or stick input along axis \"\"\"\n\tCOMMAND = \"rotate\"\n\t\n\tdef _mod_init(self, angle):\n\t\tself.angle = angle\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\treturn RotateInputModifier(float(data['rotate']), a)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn self.action.describe(context)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn self._mod_to_string((self.angle,), multiline, pad)\n\t\n\t\n\tdef strip(self):\n\t\treturn self.action.strip()\n\t\n\t\n\tdef compress(self):\n\t\tif hasattr(self.action, \"set_rotation\"):\n\t\t\tself.action.set_rotation(self.angle * PI / -180.0)\n\t\t\treturn self.action\n\t\tself.action = self.action.compress()\n\t\treturn self\n\t\n\t\n\t# This doesn't make sense with anything but 'whole' as input.\n\tdef whole(self, mapper, x, y, what):\n\t\tangle = self.angle * PI / -180.0\n\t\trx = x * cos(angle) - y * sin(angle)\n\t\try = x * sin(angle) + y * cos(angle)\n\t\treturn self.action.whole(mapper, rx, ry, what)\n\n\nclass SmoothModifier(Modifier):\n\t\"\"\"\n\tSmooths pad movements\n\t\"\"\"\n\tCOMMAND = \"smooth\"\n\tPROFILE_KEY_PRIORITY = 11\t# Before sensitivity\n\t\n\tdef _mod_init(self, level=8, multiplier=0.75, filter=2.0):\n\t\tself.level = level\n\t\tself.multiplier = multiplier\n\t\tself.filter = filter\n\t\tself._deq_x = deque([ 0.0 ] * level, maxlen=level)\n\t\tself._deq_y = deque([ 0.0 ] * level, maxlen=level)\n\t\tself._range = list(range(level))\n\t\tself._weights = [ multiplier ** x for x in reversed(self._range) ]\n\t\tself._w_sum = sum(self._weights)\n\t\tself._last_pos = None\n\t\tself._moving = False\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Smooth %s>\" % (self.action,)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn \"%s (smooth)\" % (self.action.describe(context),)\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\tpars = data[SmoothModifier.COMMAND] + [ a ]\n\t\treturn SmoothModifier(*pars)\n\t\n\t\n\tdef _get_pos(self):\n\t\t\"\"\" Computes average x,y from all accumulated positions \"\"\"\n\t\tx = sum(( self._deq_x[i] * self._weights[i] for i in self._range ))\n\t\ty = sum(( self._deq_y[i] * self._weights[i] for i in self._range ))\n\t\treturn int(x / self._w_sum), int(y / self._w_sum)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif mapper.controller_flags() & ControllerFlags.HAS_RSTICK and what == RIGHT:\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\t\tif mapper.is_touched(what):\n\t\t\tif self._last_pos is None:\n\t\t\t\t# Just pressed - fill deque with current position\n\t\t\t\tfor i in self._range:\n\t\t\t\t\tself._deq_x.append(x)\n\t\t\t\t\tself._deq_y.append(y)\n\t\t\t\tx, y = self._get_pos()\n\t\t\t\tself._last_pos = 0\n\t\t\telse:\n\t\t\t\t# Pressed for longer time\n\t\t\t\tself._deq_x.append(x)\n\t\t\t\tself._deq_y.append(y)\n\t\t\t\tx, y = self._get_pos()\n\t\t\tif abs(x + y - self._last_pos) > self.filter:\n\t\t\t\tself.action.whole(mapper, x, y, what)\n\t\t\tself._last_pos = x + y\n\t\telif what == STICK:\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\t\telse:\n\t\t\t# Pad was just released\n\t\t\tx, y = self._get_pos()\n\t\t\tself.action.whole(mapper, x, y, what)\n\t\t\tself._last_pos = None\n\n\nclass CircularModifier(Modifier, HapticEnabledAction):\n\t\"\"\"\n\tDesigned to translate rotating finger over pad to mouse wheel movement.\n\tCan also be used to translate same thing into movement of Axis.\n\t\"\"\"\n\tCOMMAND = \"circular\"\n\tPROFILE_KEY_PRIORITY = -6\n\t\n\tdef __init__(self, *params):\n\t\t# Piece of backwards compatibility\n\t\tif len(params) >= 1 and params[0] in Rels.__members__.values():\n\t\t\tparams = [ MouseAction(params[0]) ]\n\t\tself._haptic_counter = 0\n\t\tModifier.__init__(self, *params)\n\t\tHapticEnabledAction.__init__(self)\n\t\n\t\n\tdef _mod_init(self):\n\t\tself.angle = None\t\t# Last known finger position\n\t\tself.speed = 1.0\n\t\n\t\n\tdef set_haptic(self, hd):\n\t\tif isinstance(self.action, HapticEnabledAction):\n\t\t\tself.action.set_haptic(hd)\n\t\telse:\n\t\t\tHapticEnabledAction.set_haptic(self, hd)\n\t\n\t\n\tdef get_haptic(self):\n\t\tif isinstance(self.action, HapticEnabledAction):\n\t\t\treturn self.action.get_haptic()\n\t\telse:\n\t\t\treturn HapticEnabledAction.get_haptic(self)\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\treturn CircularModifier(a)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Circular %s\") % (self.action.describe(context))\n\t\n\t\n\tdef set_speed(self, x, *a):\n\t\tself.speed = x\n\t\n\t\n\tdef get_speed(self):\n\t\treturn (self.speed,)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn ( Action.MOD_FEEDBACK | Action.MOD_SENSITIVITY\n\t\t\t| Modifier.get_compatible_modifiers(self) )\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tdistance = sqrt(x*x + y*y)\n\t\tif distance < STICK_PAD_MAX_HALF:\n\t\t\t# Finger lifted or too close to middle\n\t\t\tself.angle = None\n\t\t\tif mapper.was_touched(what):\n\t\t\t\tself.action.change(mapper, 0, 0, what)\n\t\telse:\n\t\t\t# Compute current angle\n\t\t\tangle = atan2(x, y)\n\t\t\t# Compute movement\n\t\t\tif self.angle is None:\n\t\t\t\t# Finger just touched the pad\n\t\t\t\tself.angle, angle = angle, 0\n\t\t\t\tself._haptic_counter = 0\n\t\t\telse:\n\t\t\t\tself.angle, angle = angle, self.angle - angle\n\t\t\t\t# Ensure we don't wrap from pi to -pi creating a large delta\n\t\t\t\tif angle > PI:\n\t\t\t\t\t# Subtract a full rotation to counter the wrapping\n\t\t\t\t\tangle -= 2 * PI\n\t\t\t\t# And same from -pi to pi\n\t\t\t\telif angle < -PI:\n\t\t\t\t\t# Add a full rotation to counter the wrapping\n\t\t\t\t\tangle += 2 * PI\n\t\t\t# Apply bulgarian constant\n\t\t\tangle *= 10000.0\n\t\t\t# Generate feedback, if enabled\n\t\t\tif self.haptic:\n\t\t\t\tself._haptic_counter += angle * self.speed / self.haptic.frequency\n\t\t\t\tif abs(self._haptic_counter) > 0.5:\n\t\t\t\t\tif self._haptic_counter > 0.5:\n\t\t\t\t\t\tself._haptic_counter -= 0.5\n\t\t\t\t\telse:\n\t\t\t\t\t\tself._haptic_counter += 0.5\n\t\t\t\t\tmapper.send_feedback(self.haptic)\n\t\t\t# Apply movement to child action\n\t\t\t# Keep event from activating if no angle change\n\t\t\tif angle != 0.0:\n\t\t\t\tself.action.change(mapper, -angle * self.speed, 0, what)\n\t\t\t\tmapper.force_event.add(FE_PAD)\n\n\nclass CircularAbsModifier(Modifier, WholeHapticAction):\n\t\"\"\"\n\tWorks similary to CircularModifier, but instead of counting with finger\n\tmovements movements, translates exact position on dpad to axis value.\n\t\"\"\"\n\tCOMMAND = \"circularabs\"\n\tPROFILE_KEY_PRIORITY = -6\n\t\n\tdef __init__(self, *params):\n\t\tModifier.__init__(self, *params)\n\t\tWholeHapticAction.__init__(self)\n\t\n\t\n\tdef _mod_init(self):\n\t\tself.angle = None\t\t# Last known finger position\n\t\tself.speed = 1.0\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\treturn CircularAbsModifier(a)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Absolute Circular %s\") % (self.action.describe(context))\n\t\n\t\n\tdef set_speed(self, x, *a):\n\t\tself.speed = x\n\t\n\t\n\tdef get_speed(self):\n\t\treturn (self.speed,)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn ( Action.MOD_FEEDBACK | Action.MOD_SENSITIVITY | Action.MOD_ROTATE\n\t\t\t| Modifier.get_compatible_modifiers(self) )\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tdistance = sqrt(x*x + y*y)\n\t\tif distance < STICK_PAD_MAX_HALF:\n\t\t\t# Finger lifted or too close to middle\n\t\t\tself.angle = None\n\t\telse:\n\t\t\t# Compute current angle\n\t\t\tangle = atan2(x, y) + PI / 4\n\t\t\t# Compute movement\n\t\t\tif self.haptic:\n\t\t\t\tif self.angle is not None:\n\t\t\t\t\tdiff = self.angle - angle\n\t\t\t\t\t# Ensure we don't wrap from pi to -pi creating a large delta\n\t\t\t\t\tif angle > PI:\n\t\t\t\t\t\t# Subtract a full rotation to counter the wrapping\n\t\t\t\t\t\tangle -= 2 * PI\n\t\t\t\t\t# And same from -pi to pi\n\t\t\t\t\telif angle < -PI:\n\t\t\t\t\t\t# Add a full rotation to counter the wrapping\n\t\t\t\t\t\tangle += 2 * PI\n\t\t\t\t\tif abs(diff) < 6:# Prevents crazy feedback burst when finger cross 360' angle\n\t\t\t\t\t\tWholeHapticAction.change(self, mapper, diff * 10000, 0, what)\n\t\t\t\tself.angle = angle\n\t\t\t# Apply actual constant\n\t\t\tangle *= STICK_PAD_MAX / PI\n\t\t\t# Set axis on child action\n\t\t\tself.action.axis(mapper, angle * self.speed, 0)\n\t\t\tmapper.force_event.add(FE_PAD)\n"
  },
  {
    "path": "scc/osd/__init__.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD\n\nCommon methods for OSD-related stuff\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, Gdk, GLib, GObject, GdkX11\nfrom scc.constants import STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.osd.timermanager import TimerManager\nfrom scc.paths import get_share_path\nfrom scc.lib import xwrappers as X\nfrom scc.config import Config\n\nimport cairo\nimport os, argparse, traceback, logging\nlog = logging.getLogger(\"osd\")\n\n\nclass OSDWindow(Gtk.Window):\n\t# TODO: Get rid of CSS_3_20, maybe just by dropping support\n\tCSS_3_20 = \"\"\"\n\t\t#osd-menu-item-big-icon, #osd-menu-item-big-icon-selected {\n\t\t\tmin-width: 48pt;\n\t\t\tmin-height: 48pt;\n\t\t}\n\t\n\t\t#osd-dialog-buttons #osd-menu-item,\n\t\t#osd-dialog-buttons #osd-menu-item-selected {\n\t\t\tmin-width: 100px;\n\t\t\tmargin: 0px 5px 0px 5px;\n\t\t}\n\t\"\"\"\n\t\n\tEPILOG = \"\"\n\tcss_provider = None\t\t\t# Used by staticmethods\n\t\n\tdef __init__(self, wmclass, layer = None):\n\t\tGtk.Window.__init__(self)\n\t\tOSDWindow._apply_css(Config())\n\t\t\n\t\tself.argparser = argparse.ArgumentParser(description=__doc__,\n\t\t\tformatter_class=argparse.RawDescriptionHelpFormatter,\n\t\t\tepilog=self.EPILOG)\n\t\tself._add_arguments()\n\t\tself.exit_code = -1\n\t\tself.position = (20, -20)\n\t\tself.mainloop = None\n\t\tself._controller = None\n\t\tself.set_name(wmclass)\n\t\tself.set_wmclass(wmclass, wmclass)\n\t\tself.using_wlroots = False\n\t\ttry:\n\t\t\tfrom gi.repository import GtkLayerShell\n\t\t\tif GtkLayerShell.is_supported():\n\t\t\t\tself.using_wlroots=True\n\t\t\t\tself.x_layer_anchor = GtkLayerShell.Edge.LEFT\n\t\t\t\tself.y_layer_anchor = GtkLayerShell.Edge.BOTTOM\n\t\t\t\tGtkLayerShell.init_for_window(self)\n\t\t\t\tGtkLayerShell.set_layer(self, layer if layer is not None else GtkLayerShell.Layer.TOP)\n\t\t\t\tGtkLayerShell.set_anchor(self, self.x_layer_anchor, True)\n\t\t\t\tGtkLayerShell.set_anchor(self, self.y_layer_anchor, True)\n\t\t\t\tself.layer_shell = GtkLayerShell\n\t\texcept (ImportError):\n\t\t\tpass\n\t\tif not self.using_wlroots:\n\t\t\tself.set_decorated(False)\n\t\t\tself.stick()\n\t\t\tself.set_skip_taskbar_hint(True)\n\t\t\tself.set_skip_pager_hint(True)\n\t\t\tself.set_keep_above(True)\n\t\t\tself.set_type_hint(Gdk.WindowTypeHint.NOTIFICATION)\n\t\n\t\n\t@staticmethod\n\tdef _apply_css(config):\n\t\tif OSDWindow.css_provider:\n\t\t\tGtk.StyleContext.remove_provider_for_screen(\n\t\t\t\tGdk.Screen.get_default(), OSDWindow.css_provider)\n\t\t\n\t\tcolors = {}\n\t\tfor x in config['osk_colors'] : colors[\"osk_%s\" % (x,)] = config['osk_colors'][x]\n\t\tfor x in config['osd_colors'] : colors[x] = config['osd_colors'][x]\n\t\tcolors = OSDCssMagic(colors)\n\t\ttry:\n\t\t\tcss_file = os.path.join(get_share_path(), \"osd-styles\", config[\"osd_style\"])\n\t\t\tcss = open(css_file, \"r\").read()\n\t\t\tif ((Gtk.get_major_version(), Gtk.get_minor_version()) > (3, 20)):\n\t\t\t\tcss += OSDWindow.CSS_3_20\n\t\t\tOSDWindow.css_provider = Gtk.CssProvider()\n\t\t\tOSDWindow.css_provider.load_from_data((css % colors).encode(\"utf-8\"))\n\t\t\tGtk.StyleContext.add_provider_for_screen(\n\t\t\t\t\tGdk.Screen.get_default(),\n\t\t\t\t\tOSDWindow.css_provider,\n\t\t\t\t\tGtk.STYLE_PROVIDER_PRIORITY_USER)\n\t\texcept GLib.Error as e:\n\t\t\tlog.error(\"Failed to apply css with user settings:\")\n\t\t\tlog.error(e)\n\t\t\tlog.error(\"Retrying with default values\")\n\t\t\t\n\t\t\tOSDWindow.css_provider = Gtk.CssProvider()\n\t\t\tcss_file = os.path.join(get_share_path(), \"osd-styles\", \"Classic.gtkstyle.css\")\n\t\t\tcss = open(css_file, \"r\").read()\n\t\t\tif ((Gtk.get_major_version(), Gtk.get_minor_version()) > (3, 20)):\n\t\t\t\tcss += OSDWindow.CSS_3_20\n\t\t\tOSDWindow.css_provider.load_from_data((css % colors).encode(\"utf-8\"))\n\t\t\tGtk.StyleContext.add_provider_for_screen(\n\t\t\t\t\tGdk.Screen.get_default(),\n\t\t\t\t\tOSDWindow.css_provider,\n\t\t\t\t\tGtk.STYLE_PROVIDER_PRIORITY_USER)\n\t\n\t\n\tdef _add_arguments(self):\n\t\t\"\"\" Should be overriden AND called by child class \"\"\"\n\t\tself.argparser.add_argument('-x', type=int, metavar=\"pixels\", default=20,\n\t\t\thelp=\"\"\"horizontal position in pixels, from left side of screen.\n\t\t\tUse negative value to specify as distance from right side (default: 20)\"\"\")\n\t\tself.argparser.add_argument('-y', type=int, metavar=\"pixels\", default=-20,\n\t\t\thelp=\"\"\"vertical position in pixels, from top side of screen.\n\t\t\tUse negative value to specify as distance from bottom side (default: -20)\"\"\")\n\t\tself.argparser.add_argument('--controller', type=str,\n\t\t\thelp=\"\"\"id of controller to use\"\"\")\n\t\tself.argparser.add_argument('-d', action='store_true',\n\t\t\thelp=\"\"\"display debug messages\"\"\")\n\t\n\t\n\tdef choose_controller(self, daemonmanager):\n\t\t\"\"\"\n\t\tReturns first available controller, or, if --controller argument\n\t\twas specified, controller with matching ID.\n\t\t\"\"\"\n\t\tif self.args.controller:\n\t\t\tself._controller = self.daemon.get_controller(self.args.controller)\n\t\telif self.daemon.has_controller():\n\t\t\tself._controller = self.daemon.get_controllers()[0]\n\t\treturn self._controller\n\t\n\t\n\tdef get_controller(self):\n\t\t\"\"\" Returns controller chosen by choose_controller \"\"\"\n\t\treturn self._controller\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\t\"\"\" Returns True on success \"\"\"\n\t\ttry:\n\t\t\tself.args = self.argparser.parse_args(argv[1:])\n\t\texcept SystemExit:\n\t\t\treturn False\n\t\texcept BaseException as e:\t# Includes SystemExit\n\t\t\tlog.error(traceback.format_exc())\n\t\t\treturn False\n\t\tdel self.argparser\n\t\tself.position = (self.args.x, self.args.y)\n\t\tif self.args.d:\n\t\t\tset_logging_level(True, True)\n\t\treturn True\n\t\n\t\n\tdef make_window_clicktrough(self):\n\t\t(width, height) = self.get_size()\n\t\tsurface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)\n\t\tsurface_ctx = cairo.Context(surface)\n\t\tsurface_ctx.set_source_rgba(0.0, 0.0, 0.0, 0.0)\n\t\tsurface_ctx.set_operator(cairo.OPERATOR_SOURCE)\n\t\tsurface_ctx.paint()\n\t\treg = Gdk.cairo_region_create_from_surface(surface)\n\t\tself.input_shape_combine_region(reg)\n\n\t\n\t\n\tdef get_active_screen_geometry(self):\n\t\t\"\"\"\n\t\tReturns geometry of active screen or None if active screen\n\t\tcannot be determined.\n\t\t\"\"\"\n\t\tscreen = self.get_window().get_screen()\n\t\tactive_window = screen.get_active_window()\n\t\tif active_window:\n\t\t\tmonitor = screen.get_monitor_at_window(active_window)\n\t\t\tif monitor is not None:\n\t\t\t\treturn screen.get_monitor_geometry(monitor)\n\t\treturn None\n\t\n\t\n\tdef compute_position(self):\n\t\t\"\"\" Adjusts position for currently active screen (display) \"\"\"\n\t\tx, y = self.position\n\t\twidth, height = self.get_window_size()\n\t\tgeometry = self.get_active_screen_geometry()\n\t\tif geometry:\n\t\t\tif x < 0:\n\t\t\t\tx = x + geometry.x + geometry.width - width\n\t\t\telse:\n\t\t\t\tx = x + geometry.x\n\t\t\tif y < 0:\n\t\t\t\ty = y + geometry.y + geometry.height - height\n\t\t\telse:\n\t\t\t\ty = geometry.y + y\n\t\t\n\t\treturn x, y\n\t\n\t\n\tdef get_window_size(self):\n\t\treturn self.get_window().get_width(), self.get_window().get_height()\n\t\n\t\n\tdef show(self):\n\t\tself.get_children()[0].show_all()\n\t\tself.realize()\n\t\tself.get_window().set_override_redirect(True)\n\t\t\n\t\tx, y = self.compute_position()\n\t\tif self.using_wlroots:\n\t\t\tif x < 0:\n\t\t\t\tself.layer_shell.set_anchor(self, self.x_layer_anchor, False)\n\t\t\t\tself.x_layer_anchor = self.layer_shell.Edge.RIGHT\n\t\t\t\tself.layer_shell.set_anchor(self, self.x_layer_anchor, True)\n\t\t\t\tx = -x\n\t\t\tif y < 0:\n\t\t\t\tself.layer_shell.set_anchor(self, self.y_layer_anchor, False)\n\t\t\t\tself.y_layer_anchor = self.layer_shell.Edge.BOTTOM\n\t\t\t\tself.layer_shell.set_anchor(self, self.y_layer_anchor, True)\n\t\t\t\ty = -y\n\t\t\tself.layer_shell.set_margin(self, self.x_layer_anchor, x)\n\t\t\tself.layer_shell.set_margin(self, self.y_layer_anchor, y)\n\t\telse: # X11\n\t\t\tif x < 0:\t# Negative X position is counted from right border\n\t\t\t\tx = Gdk.Screen.width() - self.get_allocated_width() + x + 1\n\t\t\tif y < 0:\t# Negative Y position is counted from bottom border\n\t\t\t\ty = Gdk.Screen.height() - self.get_allocated_height() + y + 1\n\t\t\tself.move(x, y)\n\n\t\tGtk.Window.show(self)\n\t\tself.make_window_clicktrough()\n\t\n\t\n\tdef on_controller_lost(self, *a):\n\t\tlog.error(\"Controller lost\")\n\t\tself.quit(2)\n\t\n\t\n\tdef on_daemon_died(self, *a):\n\t\tlog.error(\"Daemon died\")\n\t\tself.quit(2)\n\t\n\t\n\tdef on_failed_to_lock(self, error):\n\t\tlog.error(\"Failed to lock input: %s\", error)\n\t\tself.quit(3)\n\t\n\t\n\tdef get_exit_code(self):\n\t\treturn self.exit_code\n\t\n\t\n\tdef run(self):\n\t\tself.mainloop = GLib.MainLoop()\n\t\tself.show()\n\t\tself.mainloop.run()\n\t\n\t\n\tdef quit(self, code=-1):\n\t\tself.exit_code = code\n\t\tif self.mainloop:\n\t\t\tself.mainloop.quit()\n\t\telse:\n\t\t\tself.destroy()\n\n\nclass OSDCssMagic(dict):\n\t\"\"\"\n\tBasically, I reinvented templating.\n\tThis is passed to string.format, allowing to use some simple expressions in\n\taddition to normal %(placeholder)s.\n\t\n\tSupported magic:\n\t\t%(background)s\t\t\t- just color\n\t\t%(background+10)s\t\t- color, 10 values brighter\n\t\t%(background-10)s\t\t- color, 10 values darker\n\t\"\"\"\n\t\n\tdef __init__(self, dict_to_wrap):\n\t\tself._dict = dict_to_wrap\n\t\n\t\n\tdef __getitem__(self, a):\n\t\tif \"+\" in a:\n\t\t\tkey, number = a.rsplit(\"+\", 1)\n\t\t\trgba = parse_rgba(self[key])\n\t\t\tnumber = float(number) / 255.0\n\t\t\trgba.red = min(1.0, rgba.red + number)\n\t\t\trgba.green = min(1.0, rgba.green + number)\n\t\t\trgba.blue = min(1.0, rgba.blue + number)\n\t\t\treturn \"%s%s%s\" % (\n\t\t\t\thex(int(rgba.red * 255)).split(\"x\")[-1].zfill(2),\n\t\t\t\thex(int(rgba.green * 255)).split(\"x\")[-1].zfill(2),\n\t\t\t\thex(int(rgba.blue * 255)).split(\"x\")[-1].zfill(2))\n\t\telif \"-\" in a:\n\t\t\tkey, number = a.rsplit(\"-\", 1)\n\t\t\trgba = parse_rgba(self[key])\n\t\t\tnumber = float(number) / 255.0\n\t\t\trgba.red = max(0.0, rgba.red - number)\n\t\t\trgba.green = max(0.0, rgba.green - number)\n\t\t\trgba.blue = max(0.0, rgba.blue - number)\n\t\t\treturn \"%s%s%s\" % (\n\t\t\t\thex(int(rgba.red * 255)).split(\"x\")[-1].zfill(2),\n\t\t\t\thex(int(rgba.green * 255)).split(\"x\")[-1].zfill(2),\n\t\t\t\thex(int(rgba.blue * 255)).split(\"x\")[-1].zfill(2))\n\t\treturn self._dict[a]\n\n\nclass StickController(GObject.GObject, TimerManager):\n\t\"\"\"\n\tSimple utility class that gets fed by with position and emits\n\t'direction' signal that can be used as input for menu navigation.\n\t\n\tSignals:\n\t  direction(horisontal, vertical)\n\t  \n\t  Both values are one of -1, 0, 1 for left/none/right.\n\t\"\"\"\n\t__gsignals__ = {\n\t\t\t\"direction\"\t\t\t: (GObject.SignalFlags.RUN_FIRST, None, (int, int)),\n\t}\n\tREPEAT_DELAY = 0.2\n\tDIRECTION_TO_XY = {\n\t\t0 : (0, 0),\n\t\t4 : (1, 0),\n\t\t6 : (-1, 0),\n\t\t2 : (0, 1),\n\t\t8 : (0, -1),\n\t}\n\t\n\tdef __init__(self):\n\t\tGObject.GObject.__init__(self)\n\t\tTimerManager.__init__(self)\n\t\tself._direction = 0\n\t\n\t\n\tdef _move(self, *a):\n\t\tself.emit(\"direction\", *self.DIRECTION_TO_XY[self._direction])\n\t\tif self._direction != 0:\n\t\t\tself.timer(\"move\", self.REPEAT_DELAY, self._move)\n\t\telse:\n\t\t\tself.cancel_timer(\"move\")\n\t\n\t\n\tdef set_stick(self, *data):\n\t\tdirection = 0\n\t\t# Y\n\t\tif data[1] < STICK_PAD_MIN / 2:\n\t\t\tdirection = 2\n\t\telif data[1] > STICK_PAD_MAX / 2:\n\t\t\tdirection = 8\n\t\t# X\n\t\telif data[0] < STICK_PAD_MIN / 2:\n\t\t\tdirection = 4\n\t\telif data[0] > STICK_PAD_MAX / 2:\n\t\t\tdirection = 6\n\t\t\n\t\tif direction != self._direction:\n\t\t\tself._direction = direction\n\t\t\tself._move()\n\n\ndef parse_rgba(col):\n\t\"\"\"\n\tParses color specified by #RRGGBBAA string.\n\t'#' and 'AA' is optional.\n\t\"\"\"\n\t# Because GTK can parse everything but theese :(\n\talpha = \"FF\"\n\tif not col.startswith(\"#\"):\n\t\tcol = \"#\" + col\n\tif len(col) > 7:\n\t\tcol, alpha = col[0:7], col[7:]\n\trgba = Gdk.RGBA()\n\tif not rgba.parse(col):\n\t\tlog.warning(\"Failed to parse RGBA color: %s\", col)\n\trgba.alpha = float(int(alpha, 16)) / 255.0\n\treturn rgba\n"
  },
  {
    "path": "scc/osd/area.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Menu\n\nDisplays border around area.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, GLib, GdkX11\nfrom scc.constants import LEFT, RIGHT, STICK, STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.tools import point_in_gtkrect\nfrom scc.paths import get_share_path\nfrom scc.lib import xwrappers as X\nfrom scc.menu_data import MenuData\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.osd.timermanager import TimerManager\nfrom scc.osd import OSDWindow\n\nimport os, sys, json, logging\nlog = logging.getLogger(\"osd.area\")\n\n\nclass Area(OSDWindow, TimerManager):\n\tBORDER_WIDTH = 2\n\t\n\tdef __init__(self):\n\t\tOSDWindow.__init__(self, \"osd-area\")\n\t\tTimerManager.__init__(self)\n\t\tself.size = (100, 100)\n\t\tself.add(Gtk.Fixed())\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('--width', type=int, metavar=\"pixels\", default=20,\n\t\t\thelp=\"\"\"area width in pixels\"\"\")\n\t\tself.argparser.add_argument('--height', type=int, metavar=\"pixels\", default=-20,\n\t\t\thelp=\"\"\"area height in pixels\"\"\")\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\tself.position = (self.position[0] - self.BORDER_WIDTH,\n\t\t\tself.position[1] - self.BORDER_WIDTH)\n\t\tself.size = (self.args.width + 2 * self.BORDER_WIDTH,\n\t\t\tself.args.height + 2 * self.BORDER_WIDTH)\n\t\treturn True\n\t\n\t\n\tdef compute_position(self):\n\t\t# Overrides compute_position as Area is requested with exact position\n\t\t# on X screen.\n\t\treturn self.position\n\t\n\t\n\tdef show(self):\n\t\tOSDWindow.show(self)\n\t\tself.realize()\n\t\tself.resize(*self.size)\n\t\tself.make_hole(self.BORDER_WIDTH)\n\t\n\t\n\tdef update(self, x, y, width, height):\n\t\t\"\"\" Updates area size and position \"\"\"\n\t\tself.position = x, y\n\t\tself.size = max(1, width), max(1, height) # Size can't be <1 or GTK will crash\n\t\tself.move(*self.position)\n\t\tself.resize(*self.size)\n\t\tself.make_hole(self.BORDER_WIDTH)\n\t\n\t\n\tdef make_hole(self, border_width):\n\t\t\"\"\"\n\t\tUses shape extension to create hole in window...\n\t\tArea needs only border, rest should be transparent.\n\t\t\"\"\"\n\t\twidth, height = self.size\n\t\tdpy = X.Display(hash(GdkX11.x11_get_default_xdisplay()))\t\t# I have no idea why this works...\n\t\twid = X.XID(self.get_window().get_xid())\n\t\t\n\t\tmask = X.create_pixmap(dpy, wid, width, height, 1)\n\t\txgcv = X.c_void_p()\n\t\tgc = X.create_gc(dpy, mask, 0, xgcv)\n\t\t\n\t\tX.set_foreground(dpy, gc, 1)\n\t\tX.fill_rectangle(dpy, mask, gc, 0, 0, width, height)\n\t\t\n\t\tX.set_foreground(dpy, gc, 0)\n\t\tX.fill_rectangle(dpy, mask, gc, border_width, border_width,\n\t\t\twidth - 2 * border_width, height - 2 * border_width)\n\t\t\n\t\tSHAPE_BOUNDING = 0\n\t\tSHAPE_SET = 0\n\t\tX.shape_combine_mask(dpy, wid, SHAPE_BOUNDING, 0, 0, mask, SHAPE_SET)\n\t\t\n\t\tX.free_gc(dpy, gc)\n\t\tX.free_pixmap(dpy, mask)\n"
  },
  {
    "path": "scc/osd/binding_display.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Launcher\n\nDisplay launcher with phone-like keyboard that user can use to select\napplication (list is generated using xdg) and start it.\n\nReuses styles from OSD Menu and OSD Dialog\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk\nfrom scc.actions import DPadAction, AxisAction, MouseAction\nfrom scc.actions import Action, MultiAction, XYAction\nfrom scc.modifiers import ModeModifier, DoubleclickModifier\nfrom scc.paths import get_share_path, get_config_path\nfrom scc.menu_data import MenuData, MenuItem\nfrom scc.lib import xwrappers as X, IntEnum\nfrom scc.special_actions import MenuAction\nfrom scc.parser import TalkingActionParser\nfrom scc.constants import SCButtons\nfrom scc.profile import Profile\nfrom scc.config import Config\nfrom scc.tools import nameof\nfrom scc.uinput import Rels\nfrom scc.gui.svg_widget import SVGWidget, SVGEditor\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.osd import OSDWindow\nimport os, sys, re, base64, logging\nlog = logging.getLogger(\"osd.binds\")\n\n\nclass BindingDisplay(OSDWindow):\n\t\n\tdef __init__(self, config=None):\n\t\tself.bdisplay = os.path.join(get_config_path(), 'binding-display.svg')\n\t\tif not os.path.exists(self.bdisplay):\n\t\t\t# Prefer image in ~/.config/scc, but load default one as fallback\n\t\t\tself.bdisplay = os.path.join(get_share_path(), \"images\", 'binding-display.svg')\n\t\t\n\t\tOSDWindow.__init__(self, \"osd-keyboard\")\n\t\tself.daemon = None\n\t\tself.config = config or Config()\n\t\tself.group = None\n\t\tself.limits = {}\n\t\tself.background = None\n\t\t\n\t\tself._eh_ids = []\n\t\tself._stick = 0, 0\n\t\t\n\t\tself.c = Gtk.Box()\n\t\tself.c.set_name(\"osd-keyboard-container\")\n\t\n\t\n\tdef on_profile_changed(self, daemon, filename):\n\t\tprofile = Profile(TalkingActionParser()).load(filename)\n\t\tGenerator(SVGEditor(self.background), profile)\n\t\n\t\n\tdef use_daemon(self, d):\n\t\t\"\"\"\n\t\tAllows (re)using already existing DaemonManager instance in same process\n\t\t\"\"\"\n\t\tself.daemon = d\n\t\tself._cononect_handlers()\n\t\tself.on_daemon_connected(self.daemon)\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('image', type=str, nargs=\"?\",\n\t\t\tdefault = self.bdisplay, help=\"keyboard image to use\")\n\t\tself.argparser.add_argument('--cancel-with', type=str,\n\t\t\tmetavar=\"button\", default='B',\n\t\t\thelp=\"button used to close display (default: B)\")\n\t\n\t\n\tdef compute_position(self):\n\t\t\"\"\"\n\t\tUnlike other OSD windows, this one is scaled to 80% of screen size\n\t\tand centered in on active screen.\n\t\t\"\"\"\n\t\tx, y = 10, 10\n\t\tiw, ih = self.background.image_width, self.background.image_height\n\t\tgeometry = self.get_active_screen_geometry()\n\t\tif geometry:\n\t\t\twidth, height = iw, ih\n\t\t\tif width > geometry.width * 0.8:\n\t\t\t\twidth = geometry.width * 0.8\n\t\t\t\theight = int(float(ih) / float(iw) * float(width))\n\t\t\t\tself.background.resize(width, height)\n\t\t\t\tself.background.hilight({})\n\t\t\tx = geometry.x + ((geometry.width - width) / 2)\n\t\t\ty = geometry.y + ((geometry.height - height) / 2)\n\t\treturn x, y\t\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\tself._cancel_with = self.args.cancel_with\n\t\treturn True\n\t\n\t\n\tdef _cononect_handlers(self):\n\t\tself._eh_ids += [\n\t\t\t( self.daemon, self.daemon.connect('dead', self.on_daemon_died) ),\n\t\t\t( self.daemon, self.daemon.connect('error', self.on_daemon_died) ),\n\t\t\t( self.daemon, self.daemon.connect('profile-changed', self.on_profile_changed) ),\n\t\t\t( self.daemon, self.daemon.connect('alive', self.on_daemon_connected) ),\n\t\t]\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself._cononect_handlers()\n\t\tOSDWindow.run(self)\n\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tdef success(*a):\n\t\t\tlog.info(\"Sucessfully locked input\")\n\t\t\tpass\n\t\t\n\t\tc = self.choose_controller(self.daemon)\n\t\tif c is None or not c.is_connected():\n\t\t\t# There is no controller connected to daemon\n\t\t\tself.on_failed_to_lock(\"Controller not connected\")\n\t\t\treturn\n\t\t\n\t\tself._eh_ids += [\n\t\t\t(c, c.connect('event', self.on_event)),\n\t\t\t(c, c.connect('lost', self.on_controller_lost)),\n\t\t]\n\t\t\n\t\t# Lock everything\n\t\tlocks = [ \"RB\", \"LB\", self.args.cancel_with ]\n\t\tc.lock(success, self.on_failed_to_lock, *locks)\n\t\n\t\n\tdef quit(self, code=-1):\n\t\tif self.get_controller():\n\t\t\tself.get_controller().unlock_all()\n\t\tfor source, eid in self._eh_ids:\n\t\t\tsource.disconnect(eid)\n\t\tself._eh_ids = []\n\t\tOSDWindow.quit(self, code)\n\t\n\t\n\tdef show(self, *a):\n\t\tif self.background is None:\n\t\t\tself.realize()\n\t\t\tself.background = SVGWidget(self.args.image, init_hilighted=True)\n\t\t\tself.c.add(self.background)\n\t\t\tself.add(self.c)\n\t\t\n\t\tOSDWindow.show(self, *a)\n\t\tself.move(*self.compute_position())\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\t\"\"\"\n\t\tCalled when button press, button release or stick / pad update is\n\t\tsend by daemon.\n\t\t\"\"\"\n\t\tif what == self._cancel_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tself.quit(-1)\n\n\nclass Align(IntEnum):\n\tTOP  =    1 << 0\n\tBOTTOM =  1 << 1\n\tLEFT =    1 << 2\n\tRIGHT =   1 << 3\n\n\ndef find_image(name):\n\t# TODO: This\n\tfilename = \"images/\" + name + \".svg\"\n\tif os.path.exists(filename):\n\t\treturn filename\n\treturn None\n\n\nclass Line(object):\n\t\n\tdef __init__(self, icon, text):\n\t\tself.icons = [ icon ]\n\t\tself.text = text\n\t\n\t\n\tdef get_size(self, gen):\n\t\t# TODO: This\n\t\treturn gen.char_width * len(self.text), gen.line_height\n\t\n\t\n\tdef add_icon(self, icon):\n\t\tself.icons.append(icon)\n\t\treturn self\n\t\n\t\n\tdef to_string(self):\n\t\treturn \"%-10s: %s\" % (\",\".join([ x for x in self.icons if x ]), self.text)\n\n\nclass LineCollection(object):\n\t\"\"\" Allows calling add_icon on multiple lines at once \"\"\"\n\t\n\tdef __init__(self, *lines):\n\t\tself.lines = lines\n\t\n\t\n\tdef add_icon(self, icon):\n\t\tfor line in self.lines:\n\t\t\tline.add_icon(icon)\n\t\treturn self\n\n\nclass Box(object):\n\tPADDING = 5\n\tSPACING = 2\n\tMIN_WIDTH = 100\n\tMIN_HEIGHT = 50\n\t\n\tdef __init__(self, anchor_x, anchor_y, align, name,\n\t\t\tmin_width = MIN_WIDTH, min_height = MIN_HEIGHT, max_width = 999999):\n\t\tself.name = name\n\t\tself.lines = []\n\t\tself.anchor = anchor_x, anchor_y\n\t\tself.align = align\n\t\tself.min_height = min_height\n\t\tself.x, self.y = 0, 0\n\t\tself.min_width = min_width\n\t\tself.max_width = max_width\n\t\tself.min_height = min_height\n\t\n\t\n\tdef to_string(self):\n\t\treturn \"--- %s ---\\n%s\\n\" % (\n\t\t\tself.name,\n\t\t\t\"\\n\".join([ x.to_string() for x in self.lines ])\n\t\t)\n\t\n\t\n\tdef add(self, icon, context, action):\n\t\tif not action: return LineCollection()\n\t\tif isinstance(action, MultiAction):\n\t\t\tif not action.is_key_combination():\n\t\t\t\treturn LineCollection([\n\t\t\t\t\tself.add(icon, context, child)\n\t\t\t\t\tfor child in action.actions\n\t\t\t\t])\n\t\telif isinstance(action, ModeModifier):\n\t\t\tlines = [ self.add(icon, context, action.default) ]\n\t\t\tfor x in action.mods:\n\t\t\t\tlines.append( self.add(nameof(x), context, action.mods[x])\n\t\t\t\t\t\t.add_icon(icon) )\n\t\t\treturn LineCollection(*lines)\n\t\telif isinstance(action, DoubleclickModifier):\n\t\t\tlines = []\n\t\t\tif action.normalaction:\n\t\t\t\tlines.append( self.add(icon, context, action.normalaction) )\n\t\t\tif action.action:\n\t\t\t\tlines.append( self.add(\"DOUBLECLICK\", context, action.action)\n\t\t\t\t\t\t.add_icon(icon) )\n\t\t\tif action.holdaction:\n\t\t\t\tlines.append( self.add(\"HOLD\", context, action.holdaction)\n\t\t\t\t\t\t.add_icon(icon) )\n\t\t\treturn LineCollection(*lines)\n\t\t\n\t\taction = action.strip()\n\t\tif isinstance(action, MenuAction):\n\t\t\tif self.name == \"bcs\" and action.menu_id == \"Default.menu\":\n\t\t\t\t# Special case, this action is expected in every profile,\n\t\t\t\t# so there is no need to draw it here\n\t\t\t\treturn LineCollection()\n\t\telif isinstance(action, DPadAction):\n\t\t\treturn LineCollection(\n\t\t\t\tself.add(\"DPAD_UP\",    Action.AC_BUTTON, action.actions[0]),\n\t\t\t\tself.add(\"DPAD_DOWN\",  Action.AC_BUTTON, action.actions[1]),\n\t\t\t\tself.add(\"DPAD_LEFT\",  Action.AC_BUTTON, action.actions[2]),\n\t\t\t\tself.add(\"DPAD_RIGHT\", Action.AC_BUTTON, action.actions[3])\n\t\t\t)\n\t\telif isinstance(action, XYAction):\n\t\t\tif isinstance(action.x, MouseAction) and isinstance(action.y, MouseAction):\n\t\t\t\tif action.x.get_axis() in (Rels.REL_HWHEEL, Rels.REL_WHEEL):\n\t\t\t\t\t# Special case, pad bound to wheel\n\t\t\t\t\tline = Line(icon, _(\"Mouse Wheel\"))\n\t\t\t\t\tself.lines.append(line)\n\t\t\t\t\treturn line\t\n\t\t\tif isinstance(action.x, AxisAction) and isinstance(action.y, AxisAction):\n\t\t\t\tif action.x.axis and action.y.axis:\n\t\t\t\t\tline = Line(icon, action.x.describe(Action.AC_BUTTON))\n\t\t\t\t\tself.lines.append(line)\n\t\t\t\t\treturn line\n\t\t\treturn LineCollection(\n\t\t\t\tself.add(\"AXISX\",  Action.AC_BUTTON, action.x),\n\t\t\t\tself.add(\"AXISY\",  Action.AC_BUTTON, action.y)\n\t\t\t)\n\t\tline = Line(icon, action.describe(context))\n\t\tself.lines.append(line)\n\t\treturn line\n\t\n\t\n\tdef calculate(self, gen):\n\t\tself.width, self.height = self.min_width, 2 * self.PADDING\n\t\tself.icount = 0\n\t\tfor line in self.lines:\n\t\t\tlw, lh = line.get_size(gen)\n\t\t\tself.width, self.height = max(self.width, lw), self.height + lh + self.SPACING\n\t\t\tself.icount = max(self.icount, len(line.icons))\n\t\tself.width += 2 * self.PADDING + self.icount * (gen.line_height + self.SPACING)\n\t\tself.width = min(self.width, self.max_width)\n\t\tself.height = max(self.height, self.min_height)\n\t\t\n\t\tanchor_x, anchor_y = self.anchor\n\t\tif (self.align & Align.TOP) != 0:\n\t\t\tself.y = anchor_y\n\t\telif (self.align & Align.BOTTOM) != 0:\n\t\t\tself.y = gen.full_height - self.height - anchor_y\n\t\telse:\n\t\t\tself.y = (gen.full_height - self.height) / 2\n\t\t\n\t\tif (self.align & Align.LEFT) != 0:\n\t\t\tself.x = anchor_x\n\t\telif (self.align & Align.RIGHT) != 0:\n\t\t\tself.x = gen.full_width - self.width - anchor_x\n\t\telse:\n\t\t\tself.x = (gen.full_width - self.width) / 2\n\t\n\t\n\tdef place(self, gen, root):\n\t\te = SVGEditor.add_element(root, \"rect\",\n\t\t\tstyle = \"opacity:1;fill-opacity:0.1;stroke-width:2.0;\",\n\t\t\tfill=\"#00FF00\",\n\t\t\tstroke=\"#06a400\",\n\t\t\tid = \"box_%s\" % (self.name,),\n\t\t\twidth = self.width, height = self.height,\n\t\t\tx = self.x, y = self.y,\n\t\t)\n\t\t\n\t\ty = self.y + self.PADDING\n\t\tfor line in self.lines:\n\t\t\th = gen.line_height\n\t\t\tx = self.x + self.PADDING\n\t\t\tfor icon in line.icons:\n\t\t\t\timage = find_image(icon)\n\t\t\t\tif image:\n\t\t\t\t\t# Fix: here stuff goes from weird to awfull, as rsvg\n\t\t\t\t\t# (library that gnome uses to render SVGs) can't render\n\t\t\t\t\t# linked images. Embeding is used instead.\n\t\t\t\t\timage = 'data:image/svg+xml;base64,%s' % (\n\t\t\t\t\t\tbase64.b64encode(open(image, \"rb\").read())\n\t\t\t\t\t)\n\t\t\t\t\t# Another problem: rsvg will NOT draw image unless href\n\t\t\t\t\t# tag uses namespace. No idea why is that, but I spent\n\t\t\t\t\t# 3 hours finding this, so I'm willing to murder.\n\t\t\t\t\tSVGEditor.add_element(root, \"image\", x = x, y = y,\n\t\t\t\t\t\tstyle = \"filter:url(#filterInvert)\",\n\t\t\t\t\t\twidth = h, height = h, **{\"href\" : image} )\n\t\t\t\tx += h + self.SPACING\n\t\t\tx = self.x + self.PADDING + self.icount * (h + self.SPACING)\n\t\t\ty += h\n\t\t\ttxt = SVGEditor.add_element(root, \"text\", x = x, y = y,\n\t\t\t\tstyle = gen.label_template.attrib['style']\n\t\t\t)\n\t\t\tmax_line_width = self.max_width - gen.line_height - self.PADDING\n\t\t\twhile line.text and line.get_size(gen)[0] > max_line_width:\n\t\t\t\tline.text = line.text[:-1]\n\t\t\tSVGEditor.set_text(txt, line.text)\n\t\t\ty += self.SPACING\n\t\n\t\n\tdef place_marker(self, gen, root):\n\t\tx1, y1 = self.x, self.y\n\t\tx2, y2 = x1 + self.width, y1 + self.height\n\t\tif self.align & (Align.LEFT | Align.RIGHT) == 0:\n\t\t\tedges = [ [ x2, y2 ], [ x1, y2 ] ]\n\t\telif self.align & Align.BOTTOM == Align.BOTTOM:\n\t\t\tif self.align & Align.LEFT != 0:\n\t\t\t\tedges = [ [ x2, y2 ], [ x1, y1 ] ]\n\t\t\telif self.align & Align.RIGHT != 0:\n\t\t\t\tedges = [ [ x2, y1 ], [ x1, y2 ] ]\n\t\telif self.align & Align.TOP == Align.TOP:\n\t\t\tif self.align & Align.LEFT != 0:\n\t\t\t\tedges = [ [ x2, y1 ], [ x2, y2 ] ]\n\t\t\telif self.align & Align.RIGHT != 0:\n\t\t\t\tedges = [ [ x1, y1 ], [ x1, y2 ] ]\n\t\telse:\n\t\t\tif self.align & Align.LEFT != 0:\n\t\t\t\tedges = [ [ x2, y1 ], [ x2, y2 ] ]\n\t\t\telif self.align & Align.RIGHT != 0:\n\t\t\t\tedges = [ [ x1, y1 ], [ x2, y2 ] ]\n\t\t\n\t\ttargets = SVGEditor.get_element(root, \"markers_%s\" % (self.name,))\n\t\tif targets is None:\n\t\t\treturn\n\t\ti = 0\n\t\tfor target in targets:\n\t\t\ttx, ty = float(target.attrib[\"cx\"]), float(target.attrib[\"cy\"])\n\t\t\ttry:\n\t\t\t\tedges[i] += [ tx, ty ]\n\t\t\t\ti += 1\n\t\t\texcept IndexError:\n\t\t\t\tbreak\n\t\tedges = [ i for i in edges if len(i) == 4]\n\t\t\n\t\tfor x1, y1, x2, y2 in edges:\n\t\t\te = SVGEditor.add_element(root, \"line\",\n\t\t\t\tstyle = \"opacity:1;stroke:#06a400;stroke-width:0.5;\",\n\t\t\t\t# id = \"box_%s_line0\" % (self.name,),\n\t\t\t\tx1 = x1, y1 = y1, x2 = x2, y2 = y2\n\t\t\t)\n\n\nclass Generator(object):\n\tPADDING = 10\n\t\n\tdef __init__(self, editor, profile):\n\t\tbackground = SVGEditor.get_element(editor, \"background\")\n\t\tself.label_template = SVGEditor.get_element(editor, \"label_template\")\n\t\tself.line_height = int(float(self.label_template.attrib.get(\"height\") or 8))\n\t\tself.char_width = int(float(self.label_template.attrib.get(\"width\") or 8))\n\t\tself.full_width = int(float(background.attrib.get(\"width\") or 800))\n\t\tself.full_height = int(float(background.attrib.get(\"height\") or 800))\n\t\t\n\t\tboxes = []\n\t\tbox_bcs = Box(0, self.PADDING, Align.TOP, \"bcs\")\n\t\tbox_bcs.add(\"BACK\", Action.AC_BUTTON, profile.buttons.get(SCButtons.BACK))\n\t\tbox_bcs.add(\"C\", Action.AC_BUTTON, profile.buttons.get(SCButtons.C))\n\t\tbox_bcs.add(\"START\", Action.AC_BUTTON, profile.buttons.get(SCButtons.START))\n\t\tboxes.append(box_bcs)\n\t\t\n\t\t\n\t\tbox_left = Box(self.PADDING, self.PADDING, Align.LEFT | Align.TOP, \"left\",\n\t\t\tmin_height = self.full_height * 0.5,\n\t\t\tmin_width = self.full_width * 0.2,\n\t\t\tmax_width = self.full_width * 0.275\n\t\t\t)\n\t\tbox_left.add(\"LEFT\", Action.AC_TRIGGER, profile.triggers.get(profile.LEFT))\n\t\tbox_left.add(\"LB\", Action.AC_BUTTON, profile.buttons.get(SCButtons.LB))\n\t\tbox_left.add(\"LGRIP\", Action.AC_BUTTON, profile.buttons.get(SCButtons.LGRIP))\n\t\tbox_left.add(\"LPAD\", Action.AC_PAD, profile.pads.get(profile.LEFT))\n\t\tboxes.append(box_left)\n\t\t\n\t\t\n\t\tbox_right = Box(self.PADDING, self.PADDING, Align.RIGHT | Align.TOP, \"right\",\n\t\t\tmin_height = self.full_height * 0.5,\n\t\t\tmin_width = self.full_width * 0.2,\n\t\t\tmax_width = self.full_width * 0.275\n\t\t\t)\n\t\tbox_right.add(\"RIGHT\", Action.AC_TRIGGER, profile.triggers.get(profile.RIGHT))\n\t\tbox_right.add(\"RB\", Action.AC_BUTTON, profile.buttons.get(SCButtons.RB))\n\t\tbox_right.add(\"RGRIP\", Action.AC_BUTTON, profile.buttons.get(SCButtons.RGRIP))\n\t\tbox_right.add(\"RPAD\", Action.AC_PAD, profile.pads.get(profile.RIGHT))\n\t\tboxes.append(box_right)\n\t\t\n\t\t\n\t\tbox_abxy = Box(4 * self.PADDING, self.PADDING, Align.RIGHT | Align.BOTTOM, \"abxy\",\n\t\t\tmax_width = self.full_width * 0.45\n\t\t\t)\n\t\tbox_abxy.add(\"A\", Action.AC_BUTTON, profile.buttons.get(SCButtons.A))\n\t\tbox_abxy.add(\"B\", Action.AC_BUTTON, profile.buttons.get(SCButtons.B))\n\t\tbox_abxy.add(\"X\", Action.AC_BUTTON, profile.buttons.get(SCButtons.X))\n\t\tbox_abxy.add(\"Y\", Action.AC_BUTTON, profile.buttons.get(SCButtons.Y))\n\t\tboxes.append(box_abxy)\n\t\t\n\t\t\n\t\tbox_stick = Box(4 * self.PADDING, self.PADDING, Align.LEFT | Align.BOTTOM, \"stick\",\n\t\t\tmax_width = self.full_width * 0.45\n\t\t\t)\n\t\tbox_stick.add(\"STICK\", Action.AC_STICK, profile.stick)\n\t\tboxes.append(box_stick)\n\t\t\n\t\t\n\t\tw = int(float(background.attrib.get(\"width\") or 800))\n\t\th = int(float(background.attrib.get(\"height\") or 800))\n\t\t\n\t\troot = SVGEditor.get_element(editor, \"root\")\n\t\tfor b in boxes:\n\t\t\tb.calculate(self)\n\t\t\n\t\t# Set ABXY and Stick size & position\n\t\tbox_abxy.height = box_stick.height = self.full_height * 0.25\n\t\tbox_abxy.width = box_stick.width = self.full_width * 0.3\n\t\tbox_abxy.y = self.full_height - self.PADDING - box_abxy.height\n\t\tbox_stick.y = self.full_height - self.PADDING - box_stick.height\n\t\tbox_abxy.x = self.full_width - self.PADDING - box_abxy.width\n\t\t\n\t\tself.equal_width(box_left, box_right)\n\t\tself.equal_height(box_left, box_right)\n\t\t\n\t\tfor b in boxes:\n\t\t\tb.place_marker(self, root)\n\t\tfor b in boxes:\n\t\t\tb.place(self, root)\n\t\t\n\t\teditor.commit()\n\t\n\t\n\tdef equal_width(self, *boxes):\n\t\t\"\"\" Sets width of all passed boxes to width of widest box \"\"\"\n\t\twidth = 0\n\t\tfor b in boxes: width = max(width, b.width)\n\t\tfor b in boxes:\n\t\t\tb.width = width\n\t\t\tif b.align & Align.RIGHT:\n\t\t\t\tb.x = self.full_width - b.width - self.PADDING\n\t\n\t\n\tdef equal_height(self, *boxes):\n\t\t\"\"\" Sets height of all passed boxes to height of tallest box \"\"\"\n\t\theight = 0\n\t\tfor b in boxes: height = max(height, b.height)\n\t\tfor b in boxes:\n\t\t\tb.height = height\n\n\n\ndef main():\n\tm = BindingDisplay()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tsys.exit(m.get_exit_code())\n\n\nif __name__ == \"__main__\":\n\tfrom scc.tools import init_logging\n\tinit_logging()\n\tmain()\n"
  },
  {
    "path": "scc/osd/dialog.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Dialog\n\nDisplay dialog with text and set of items that user can navigate through and\nprints chosen item id to stdout\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom gi.repository import Gtk, GdkX11\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.osd import OSDWindow, StickController\nfrom scc.lib import xwrappers as X\nfrom scc.constants import DEFAULT, STICK\nfrom scc.menu_data import MenuData\nfrom scc.config import Config\n\nimport sys, logging\nlog = logging.getLogger(\"osd.dialog\")\n\n\nclass Dialog(OSDWindow):\n\tEPILOG=\"\"\"Exit codes:\n   0  - clean exit, user selected option\n  -1  - clean exit, user canceled dialog\n   1  - error, invalid arguments\n   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.\n   3  - erorr, failed to lock input stick, pad or button(s)\n\t\"\"\"\n\t\n\tdef __init__(self, cls=\"osd-menu\"):\n\t\tself._buttons = None\n\t\tself._text = None\n\t\t\n\t\tOSDWindow.__init__(self, cls)\n\t\tself.daemon = None\n\t\tself.config = None\n\t\tself.feedback = None\n\t\tself.controller = None\n\t\tself.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))\t# Magic\n\t\t\n\t\tself.parent = self.create_parent()\n\t\tself.f = Gtk.Fixed()\n\t\tself.f.add(self.parent)\n\t\tself.add(self.f)\n\t\t\n\t\tself._scon = StickController()\n\t\tself._scon.connect(\"direction\", self.on_stick_direction)\n\t\tself._selected = None\n\t\tself._eh_ids = []\n\t\n\t\n\tdef create_parent(self):\n\t\tself._text = Gtk.Label()\n\t\tself._buttons = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)\n\t\tdialog = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)\n\t\tdialog.pack_start(self._text, True, True, 0)\n\t\tdialog.pack_start(self._buttons, True, True, 0)\n\t\t\n\t\tdialog.set_name(\"osd-dialog\")\n\t\tself._buttons.set_name(\"osd-dialog-buttons\")\n\t\tself._text.set_name(\"osd-dialog-text\")\n\t\treturn dialog\n\t\n\t\n\tdef pack_items(self, parent, items):\n\t\tfor item in items:\n\t\t\tif hasattr(item.widget, \"set_alignment\"):\n\t\t\t\titem.widget.set_alignment(0.5, 0.5)\n\t\t\tself._buttons.pack_end(item.widget, True, True, 0)\n\t\n\t\n\tdef use_daemon(self, d):\n\t\t\"\"\"\n\t\tAllows (re)using already existing DaemonManager instance in same process.\n\t\tuse_config() should be be called before parse_argumets() if this is used.\n\t\t\"\"\"\n\t\tself.daemon = d\n\t\tself._connect_handlers()\n\t\tself.on_daemon_connected(self.daemon)\n\t\n\t\n\tdef use_config(self, c):\n\t\t\"\"\"\n\t\tAllows reusing already existin Config instance in same process.\n\t\tHas to be called before parse_argumets()\n\t\t\"\"\"\n\t\tself.config = c\n\t\n\t\n\tdef get_menuid(self):\n\t\t# Just to be compatibile with menus when called from scc-osd-daemon\n\t\treturn None\n\t\n\t\n\tdef get_selected_item_id(self):\n\t\t\"\"\"\n\t\tReturns ID of selected item or None if nothing is selected.\n\t\t\"\"\"\n\t\tif self._selected:\n\t\t\treturn self._selected.id\n\t\treturn None\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('--confirm-with', type=str,\n\t\t\tmetavar=\"button\", default=DEFAULT,\n\t\t\thelp=\"button used to confirm choice\")\n\t\tself.argparser.add_argument('--cancel-with', type=str,\n\t\t\tmetavar=\"button\", default=DEFAULT,\n\t\t\thelp=\"button used to cancel dialog\")\n\t\tself.argparser.add_argument('--feedback-amplitude', type=int,\n\t\t\thelp=\"enables and sets power of feedback effect generated when active menu option is changed\")\n\t\tself.argparser.add_argument('--text', type=str, metavar='text',\n\t\t\thelp=\"Dialog text\")\n\t\tself.argparser.add_argument('items', type=str, nargs='*', metavar='id text',\n\t\t\thelp=\"Dialog buttons\")\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\t\n\t\ttry:\n\t\t\tself.items = MenuData.from_args(self.args.items)\n\t\t\tself._menuid = None\n\t\texcept ValueError:\n\t\t\tprint('%s: error: invalid number of arguments' % (sys.argv[0]), file=sys.stderr)\n\t\t\treturn False\n\t\t\n\t\tself._text.set_label(self.args.text)\n\t\t\n\t\tif self.args.feedback_amplitude:\n\t\t\tside = \"LEFT\"\n\t\t\tself.feedback = side, int(self.args.feedback_amplitude)\n\t\t\n\t\t# Create buttons that are displayed on screen\n\t\titems = self.items.generate(self)\n\t\tself.items = []\n\t\tfor item in items:\n\t\t\titem.widget = self.generate_widget(item)\n\t\t\tif item.widget is not None:\n\t\t\t\tself.items.append(item)\n\t\tself.pack_items(self.parent, self.items)\n\t\tif len(self.items) == 0:\n\t\t\tprint('%s: error: no items in menu' % (sys.argv[0]), file=sys.stderr)\n\t\t\treturn False\n\t\t\n\t\treturn True\n\t\n\t\n\tdef generate_widget(self, item):\n\t\t\"\"\" Generates gtk widget for specified menutitem \"\"\"\n\t\twidget = Gtk.Button.new_with_label(item.label)\n\t\twidget.set_relief(Gtk.ReliefStyle.NONE)\n\t\tif hasattr(widget.get_children()[0], \"set_xalign\"):\n\t\t\twidget.get_children()[0].set_xalign(0)\n\t\telse:\n\t\t\twidget.get_children()[0].set_halign(Gtk.Align.START)\n\t\twidget.set_name(\"osd-menu-item\")\n\t\t\n\t\treturn widget\n\t\n\t\n\tdef select(self, index):\n\t\tif self._selected:\n\t\t\tself._selected.widget.set_name(self._selected.widget.get_name()\n\t\t\t\t.replace(\"-selected\", \"\"))\n\t\tif self.items[index].id:\n\t\t\tif self._selected != self.items[index]:\n\t\t\t\tif self.feedback and self.controller:\n\t\t\t\t\tself.controller.feedback(*self.feedback)\n\t\t\tself._selected = self.items[index]\n\t\t\tself._selected.widget.set_name(\n\t\t\t\t\tself._selected.widget.get_name() + \"-selected\")\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef _connect_handlers(self):\n\t\tself._eh_ids += [\n\t\t\t(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('error', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),\n\t\t]\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself._connect_handlers()\n\t\tOSDWindow.run(self)\n\t\n\t\n\tdef show(self, *a):\n\t\tif not self.select(0):\n\t\t\tself.next_item(1)\n\t\tOSDWindow.show(self, *a)\n\t\n\t\n\tdef on_daemon_died(self, *a):\n\t\tlog.error(\"Daemon died\")\n\t\tself.quit(2)\n\t\n\t\n\tdef on_failed_to_lock(self, error):\n\t\tlog.error(\"Failed to lock input: %s\", error)\n\t\tself.quit(3)\n\t\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tdef success(*a):\n\t\t\tlog.error(\"Sucessfully locked input\")\n\t\t\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\tself.controller = self.choose_controller(self.daemon)\n\t\tif self.controller is None or not self.controller.is_connected():\n\t\t\t# There is no controller connected to daemon\n\t\t\tself.on_failed_to_lock(\"Controller not connected\")\n\t\t\treturn\n\t\t\n\t\tccfg = self.config.get_controller_config(self.controller.get_id())\n\t\tself._control_with = ccfg[\"menu_control\"]\n\t\tself._confirm_with = ccfg[\"menu_confirm\"] if self.args.confirm_with == DEFAULT else self.args.confirm_with\n\t\tself._cancel_with = ccfg[\"menu_cancel\"] if self.args.cancel_with == DEFAULT else self.args.cancel_with\n\t\t\n\t\tself._eh_ids += [ (self.controller, self.controller.connect('event', self.on_event)) ]\n\t\tlocks = [ self._control_with, self._confirm_with, self._cancel_with ]\n\t\tself.controller.lock(success, self.on_failed_to_lock, *locks)\n\t\n\t\n\tdef quit(self, code=-2):\n\t\tif self.get_controller():\n\t\t\tself.get_controller().unlock_all()\n\t\tfor source, eid in self._eh_ids:\n\t\t\tsource.disconnect(eid)\n\t\tself._eh_ids = []\n\t\tOSDWindow.quit(self, code)\n\t\n\t\n\tdef next_item(self, direction):\n\t\t\"\"\" Selects next menu item, based on self._direction \"\"\"\n\t\tstart, i = -1, 0\n\t\ttry:\n\t\t\tstart = self.items.index(self._selected)\n\t\t\ti = start + direction\n\t\texcept: pass\n\t\twhile True:\n\t\t\tif i == start:\n\t\t\t\t# Cannot find valid menu item\n\t\t\t\tself.select(start)\n\t\t\t\tbreak\n\t\t\tif i >= len(self.items):\n\t\t\t\ti = 0\n\t\t\t\tcontinue\n\t\t\tif i < 0:\n\t\t\t\ti = len(self.items) - 1\n\t\t\t\tcontinue\n\t\t\tif self.select(i): break\n\t\t\ti += direction\n\t\t\tif start < 0: start = 0\n\t\n\t\n\tdef on_stick_direction(self, trash, x, y):\n\t\tif x != 0:\n\t\t\tself.next_item(x)\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\tif what == self._control_with:\n\t\t\tself._scon.set_stick(*data)\n\t\telif what == self._cancel_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tself.quit(-1)\n\t\telif what == self._confirm_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tif self._selected and self._selected.callback:\n\t\t\t\t\tself._selected.callback(self, self.daemon, self.controller, self._selected)\n\t\t\t\telif self._selected:\n\t\t\t\t\tself.quit(0)\n\t\t\t\telse:\n\t\t\t\t\tself.quit(-1)\n"
  },
  {
    "path": "scc/osd/gesture_display.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Grid OSD Menu\n\nWorks as OSD menu, but displays item in (as rectangluar as possible - and\nthat's usually not very much) grid.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, GObject\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.gui.gestures import GestureDraw\nfrom scc.constants import LEFT, RIGHT, CPAD\nfrom scc.config import Config\nfrom scc.osd import OSDWindow\nfrom scc.gestures import GestureDetector\nBOTH = \"BOTH\"\n\nimport logging\nlog = logging.getLogger(\"osd.gesture\")\n\n\nclass GestureDisplay(OSDWindow):\n\t\"\"\"\n\tOSD Window that displays gesture as it is being generated.\n\t\n\tSignals:\n\t  gesture-updated(gesture)\t\tEmited repeadedly while gesture is being drawn.\n\t  \t\t\t\t\t\t\t\tMay be emited multiple times with same gesture.\n\t\"\"\"\n\t\n\tEPILOG=\"\"\"Exit codes:\n   0  - clean exit, user created gesture\n  -1  - clean exit, user canceled gesture\n   1  - error, invalid arguments\n   2  - error, failed to access sc-daemon, sc-daemon reported error or died while menu is displayed.\n   3  - erorr, failed to lock input\n\t\"\"\"\n\t__gsignals__ = {\n\t\t\"gesture-updated\"                    : (GObject.SignalFlags.RUN_FIRST, None, (str,)),\n\t}\n\n\tSIZE = 128\t# times two horizontaly + borders\n\t\n\tdef __init__(self, config=None):\n\t\tOSDWindow.__init__(self, \"osd-gesture\")\n\t\tself.daemon = None\n\t\tself._left_detector  = GestureDetector(0, self._on_gesture_finished)\n\t\t# self._right_detector = GestureDetector(0, self._on_gesture_finished)\n\t\tself._control_with = LEFT\n\t\tself._eh_ids = []\n\t\tself._gesture = None\n\t\t\n\t\tself.setup_widgets()\n\t\tself.use_config(config or Config())\n\t\n\t\n\tdef setup_widgets(self):\n\t\tself.parent = Gtk.Grid()\n\t\tself.parent.set_name(\"osd-gesture\")\n\t\t\n\t\tself._left_draw  = GestureDraw(self.SIZE, self._left_detector)\n\t\t# self._right_draw = GestureDraw(self.SIZE, self._right_detector)\n\t\tsep = Gtk.VSeparator()\n\t\tsep.set_name(\"osd-gesture-separator\")\n\t\t\n\t\tself.parent.attach(self._left_draw,  0, 0, 1, 1)\n\t\t# self.parent.attach(sep,              1, 0, 1, 1)\n\t\t# self.parent.attach(self._right_draw, 2, 0, 1, 1)\n\t\t\n\t\tself.add(self.parent)\n\t\n\t\n\tdef use_daemon(self, d):\n\t\t\"\"\"\n\t\tAllows (re)using already existing DaemonManager instance in same process.\n\t\tIf this is used, parse_argumets() should be called before.\n\t\t\"\"\"\n\t\tself.daemon = d\n\t\tself.on_daemon_connected()\n\t\n\t\n\tdef use_config(self, c):\n\t\t\"\"\"\n\t\tAllows reusing already existin Config instance in same process.\n\t\tHas to be called before parse_argumets()\n\t\t\"\"\"\n\t\tself.config = c\n\t\t# for x in (self._left_draw, self._right_draw):\n\t\tfor x in (self._left_draw, ):\n\t\t\tx.set_colors(**self.config[\"gesture_colors\"])\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('--control-with', '-c', type=str,\n\t\t\tmetavar=\"option\", default=LEFT, choices=(LEFT, RIGHT, CPAD),\n\t\t\thelp=\"which pad should be used to generate gesture menu (default: %s)\" % (LEFT,))\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\tif not self.config:\n\t\t\tself.use_config(Config())\n\t\t\n\t\t# Parse simpler arguments\n\t\tself._control_with = self.args.control_with\n\t\t\n\t\treturn True\n\t\n\t\n\tdef _connect_handlers(self):\n\t\tself._eh_ids += [\n\t\t\t(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('error', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),\n\t\t]\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself._connect_handlers()\n\t\tOSDWindow.run(self)\n\t\n\t\n\tdef show(self, *a):\n\t\tOSDWindow.show(self, *a)\n\t\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tdef success(*a):\n\t\t\tlog.error(\"Sucessfully locked %s pad\", self._control_with)\n\t\t\tself._left_detector.enable()\n\t\t\t# self._right_detector.enable()\n\t\t\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\tlocks = [ self._control_with ]\n\t\tc = self.choose_controller(self.daemon)\n\t\tif c is None or not c.is_connected():\n\t\t\t# There is no controller connected to daemon\n\t\t\tself.on_failed_to_lock(\"Controller not connected\")\n\t\t\treturn\n\t\t\n\t\tself._eh_ids += [\n\t\t\t(c, c.connect('event', self.on_event)),\n\t\t\t(c, c.connect('lost', self.on_controller_lost)),\n\t\t]\n\t\tc.lock(success, self.on_failed_to_lock, *locks)\n\t\n\t\n\tdef quit(self, code=-2):\n\t\tif self.get_controller():\n\t\t\tself.get_controller().unlock_all()\n\t\tfor source, eid in self._eh_ids:\n\t\t\tsource.disconnect(eid)\n\t\tself._eh_ids = []\n\t\tOSDWindow.quit(self, code)\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\tif what == self._control_with:\n\t\t\tx, y = data\n\t\t\tself._left_draw.add(x, y)\n\t\t\tself._left_detector.whole(None, x, y, what)\n\t\t\t# TODO: self._right_detector, if there is any use for it later\n\t\t\tself.emit('gesture-updated', self._left_detector.get_string())\n\t\n\t\n\tdef get_gesture(self):\n\t\t\"\"\" Returns recognized gesture or None if there is not any \"\"\"\n\t\tif self._gesture:\n\t\t\treturn self._gesture\n\t\t# self._gesture is None or empty\n\t\treturn None\n\t\n\t\n\tdef _on_gesture_finished(self, detector, gesture):\n\t\tself._gesture = gesture\n\t\tlog.debug(\"Recognized gesture: %s\", gesture)\n\t\tself.quit(0)\n\n\ndef main():\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tgd = GestureDisplay()\n\tif not gd.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tgd.run()\n\tif gd.get_exit_code() == 0:\n\t\tprint(gd.get_gesture())\n\telse:\n\t\tsys.exit(gd.get_exit_code())\n\n\nif __name__ == \"__main__\":\n\timport os, sys, signal\n\n\tdef sigint(*a):\n\t\tprint(\"\\n*break*\")\n\t\tsys.exit(-1)\n\t\n\tsignal.signal(signal.SIGINT, sigint)\n\tmain()\n"
  },
  {
    "path": "scc/osd/grid_menu.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Grid OSD Menu\n\nWorks as OSD menu, but displays item in (as rectangluar as possible - and\nthat's usually not very much) grid.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk\nfrom scc.menu_data import Separator, Submenu\nfrom scc.osd.menu import Menu, MenuIcon\nfrom scc.osd import OSDWindow\nfrom scc.tools import find_icon\n\nimport math, logging\nlog = logging.getLogger(\"osd.gridmenu\")\n\n\nclass GridMenu(Menu):\n\tPREFER_BW_ICONS = True\n\t\n\tdef __init__(self, cls=\"osd-menu\"):\n\t\tMenu.__init__(self, cls)\n\t\tself.ipr = 1\t# items per row\n\t\n\t\n\tdef create_parent(self):\n\t\tg = Gtk.Grid()\n\t\tg.set_name(\"osd-menu\")\n\t\treturn g\n\t\n\t\n\tdef pack_items(self, parent, items):\n\t\tif self._size > 0:\n\t\t\tself.ipr = self._size\n\t\telse:\n\t\t\tself.ipr = int(math.sqrt(max(1, len(items)-1))+1)\n\t\t\tif len(items) == 6 : self.ipr = 3\t# Special (common) cases\n\t\t\tif len(items) == 8 : self.ipr = 4\t# Special (common) cases\n\t\tx, y = 0, 0\n\t\tfor item in items:\n\t\t\tparent.attach(item.widget, x, y, 1, 1)\n\t\t\tx += 1\n\t\t\tif x >= self.ipr:\n\t\t\t\tx = 0\n\t\t\t\ty += 1\n\t\n\t\n\tdef on_stick_direction(self, trash, x, y):\n\t\tif x != 0:\n\t\t\tself.next_item(-x)\n\t\telif y != 0:\n\t\t\tfor i in range(0, self.ipr):\n\t\t\t\tself.next_item(y)\n\t\n\t\n\tdef generate_widget(self, item):\n\t\tif isinstance(item, Separator):\n\t\t\t# Ignored here\n\t\t\treturn None\n\t\telif item.id is None:\n\t\t\t# Dummies are ignored as well\n\t\t\treturn None\n\t\telse:\n\t\t\ticon_file, has_colors = find_icon(item.icon, False)\n\t\t\tif icon_file:\n\t\t\t\t# Gridmenu hides label when icon is displayed\n\t\t\t\twidget = Gtk.Button()\n\t\t\t\twidget.set_relief(Gtk.ReliefStyle.NONE)\n\t\t\t\twidget.set_name(\"osd-menu-item-big-icon\")\n\t\t\t\tif isinstance(item, Submenu):\n\t\t\t\t\titem.callback = self.show_submenu\n\t\t\t\ticon = MenuIcon(icon_file, has_colors)\n\t\t\t\twidget.add(icon)\n\t\t\t\treturn widget\n\t\t\telse:\n\t\t\t\treturn Menu.generate_widget(self, item)\n"
  },
  {
    "path": "scc/osd/hmenu.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Horisontal OSD Menu\n\nWorks as OSD menu, but displays all items in one row.\n\nDesigned mainly as RPG numeric pad display and looks\nawfull with larger number of items.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk\nfrom scc.menu_data import Separator, Submenu\nfrom scc.constants import STICK_PAD_MIN\nfrom scc.osd.grid_menu import GridMenu\nfrom scc.osd.menu import MenuIcon\n\nimport logging\nlog = logging.getLogger(\"osd.hmenu\")\n\n\nclass HorizontalMenu(GridMenu):\n\tdef __init__(self, cls=\"osd-menu\"):\n\t\tGridMenu.__init__(self, cls)\n\t\n\t\n\tdef create_parent(self):\n\t\tg = Gtk.Grid()\n\t\tg.set_name(\"osd-menu\")\n\t\treturn g\n\t\n\t\n\tdef generate_widget(self, item):\n\t\t\"\"\"\n\t\tGenerates gtk widget for specified menutitem\n\t\tIgnores Submenus and Separators but applies icon size\n\t\t\"\"\"\n\t\tif isinstance(item, (Separator, Submenu)) or item.id is None:\n\t\t\treturn None\n\t\telse:\n\t\t\twidget = GridMenu.generate_widget(self, item)\n\t\t\ticon = widget.get_children()[-1]\n\t\t\tif self._size > 1 and isinstance(icon, MenuIcon):\n\t\t\t\twidget.set_size_request(-1, 32 + self._size * 3)\n\t\t\treturn widget\n\t\n\t\n\tdef pack_items(self, parent, items):\n\t\tx = 0\n\t\tfor item in items:\n\t\t\tparent.attach(item.widget, x, 0, 1, 1)\n\t\t\tx += 1\n\t\n\t\n\tdef on_stick_direction(self, trash, x, y):\n\t\tif x != 0:\n\t\t\tself.next_item(-x)\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\t# Restricts Y axis to dead center, as nothing\n\t\t# else makes sense in this kind of menu\n\t\tif self._submenu:\n\t\t\treturn self._submenu.on_event(daemon, what, data)\n\t\tif what == self._control_with and self._use_cursor:\n\t\t\tdata = data[0], STICK_PAD_MIN\n\t\tGridMenu.on_event(self, daemon, what, data)\n"
  },
  {
    "path": "scc/osd/inputdisplay.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Input Display\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, GLib\nfrom scc.constants import SCButtons, STICK, LEFT, RIGHT, STICK_PAD_MAX\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.gui.svg_widget import SVGWidget\nfrom scc.osd import OSDWindow\n\nimport os, sys, logging, signal, argparse\nlog = logging.getLogger(\"osd.InputDisplay\")\n\n\nclass InputDisplay(OSDWindow):\n\tIMAGE = \"inputdisplay.svg\"\n\tHILIGHT_COLOR = \"#FF00FF00\"\t\t# ARGB\n\tOBSERVE_COLOR = \"#00007FFF\"\t\t# ARGB\n\t\n\tdef __init__(self, imagepath=\"/usr/share/scc/images\"):\n\t\tOSDWindow.__init__(self, \"osd-menu\")\n\t\tself.daemon = None\n\t\tself.config = None\n\t\tself.hilights = { self.HILIGHT_COLOR : set(), self.OBSERVE_COLOR : set() }\n\t\tself.imagepath = imagepath\n\t\t\n\t\tself._eh_ids = []\n\t\n\t\n\tdef show(self):\n\t\tself.main_area = Gtk.Fixed()\n\t\tself.background = SVGWidget(os.path.join(self.imagepath, self.IMAGE))\n\t\tself.lpadTest = Gtk.Image.new_from_file(os.path.join(self.imagepath, \"inputdisplay-cursor.svg\"))\n\t\tself.rpadTest = Gtk.Image.new_from_file(os.path.join(self.imagepath, \"inputdisplay-cursor.svg\"))\n\t\tself.stickTest = Gtk.Image.new_from_file(os.path.join(self.imagepath, \"inputdisplay-cursor.svg\"))\n\t\t\n\t\tself.main_area.set_property(\"margin-left\", 10)\n\t\tself.main_area.set_property(\"margin-right\", 10)\n\t\tself.main_area.set_property(\"margin-top\", 10)\n\t\tself.main_area.set_property(\"margin-bottom\", 10)\n\t\t\n\t\tself.main_area.put(self.background, 0, 0)\n\t\tself.main_area.put(self.lpadTest, 40, 40)\n\t\tself.main_area.put(self.rpadTest, 290, 90)\n\t\tself.main_area.put(self.stickTest, 150, 40)\n\t\t\n\t\tself.add(self.main_area)\n\t\t\n\t\tOSDWindow.show(self)\n\t\tself.lpadTest.hide()\n\t\tself.rpadTest.hide()\n\t\tself.stickTest.hide()\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself._connect_handlers()\n\t\tOSDWindow.run(self)\n\t\n\t\n\tdef use_daemon(self, d):\n\t\t\"\"\"\n\t\tAllows (re)using already existing DaemonManager instance in same process.\n\t\tuse_config() should be be called before parse_argumets() if this is used.\n\t\t\"\"\"\n\t\tself.daemon = d\n\t\tself._connect_handlers()\n\t\tself.on_daemon_connected(self.daemon)\t\n\t\n\t\n\tdef _connect_handlers(self):\n\t\tself._eh_ids += [\n\t\t\t(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('error', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),\n\t\t]\n\t\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tc = self.daemon.get_controllers()[0]\n\t\tc.unlock_all()\n\t\tc.observe(DaemonManager.nocallback, self.on_observe_failed,\n\t\t\t'A', 'B', 'C', 'X', 'Y', 'START', 'BACK', 'LB', 'RB',\n\t\t\t'LPAD', 'RPAD', 'LGRIP', 'RGRIP', 'LT', 'RT', 'LEFT',\n\t\t\t'RIGHT', 'STICK', 'STICKPRESS')\t\n\t\tc.connect('event', self.on_daemon_event_observer)\n\t\tc.connect('lost', self.on_controller_lost)\n\t\n\t\n\tdef on_observe_failed(self, error):\n\t\tlog.error(\"Failed to enable test mode: %s\", error)\n\t\tif \"Sniffing\" in error:\n\t\t\tlog.error(\"\")\n\t\t\tlog.error(\"=================================================================================\")\n\t\t\tlog.error(\"[!!] Please, enable 'Input Test Mode' on 'Advanced' tab in SC-Controller settings\")\n\t\t\tlog.error(\"=================================================================================\")\n\t\tself.quit(3)\n\t\n\t\n\tdef on_daemon_event_observer(self, daemon, what, data):\n\t\tif what in (LEFT, RIGHT, STICK):\n\t\t\twidget, area = {\n\t\t\t\tLEFT  : (self.lpadTest,  \"LPADTEST\"),\n\t\t\t\tRIGHT : (self.rpadTest,  \"RPADTEST\"),\n\t\t\t\tSTICK : (self.stickTest, \"STICKTEST\"),\n\t\t\t}[what]\n\t\t\t# Check if stick or pad is released\n\t\t\tif data[0] == data[1] == 0:\n\t\t\t\twidget.hide()\n\t\t\t\treturn\n\t\t\tif not widget.is_visible():\n\t\t\t\twidget.show()\n\t\t\t# Grab values\n\t\t\tax, ay, aw, trash = self.background.get_area_position(area)\n\t\t\tcw = widget.get_allocation().width\n\t\t\t# Compute center\n\t\t\tx, y = ax + aw * 0.5 - cw * 0.5, ay + 1.0 - cw * 0.5\n\t\t\t# Add pad position\n\t\t\tx += data[0] * aw / STICK_PAD_MAX * 0.5\n\t\t\ty -= data[1] * aw / STICK_PAD_MAX * 0.5\n\t\t\t# Move circle\n\t\t\tself.main_area.move(widget, x, y)\n\t\telif what in (\"LT\", \"RT\", \"STICKPRESS\"):\n\t\t\twhat = {\n\t\t\t\t\"LT\" : \"LEFT\",\n\t\t\t\t\"RT\" : \"RIGHT\",\n\t\t\t\t\"STICKPRESS\" : \"STICK\"\n\t\t\t}[what]\n\t\t\tif data[0]:\n\t\t\t\tself.hilights[self.OBSERVE_COLOR].add(what)\n\t\t\telse:\n\t\t\t\tself.hilights[self.OBSERVE_COLOR].remove(what)\n\t\t\tself._update_background()\n\t\telif hasattr(SCButtons, what):\n\t\t\ttry:\n\t\t\t\tif data[0]:\n\t\t\t\t\tself.hilights[self.OBSERVE_COLOR].add(what)\n\t\t\t\telse:\n\t\t\t\t\tself.hilights[self.OBSERVE_COLOR].remove(what)\n\t\t\t\tself._update_background()\n\t\t\texcept KeyError as e:\n\t\t\t\t# Non fatal\n\t\t\t\tpass\n\t\telse:\n\t\t\tprint(\"event\", what)\n\t\n\t\n\tdef _update_background(self):\n\t\th = {}\n\t\tfor color in self.hilights:\n\t\t\tfor i in self.hilights[color]:\n\t\t\t\th[i] = color\n\t\tself.background.hilight(h)\n\n\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(0)\n\n\nif __name__ == \"__main__\":\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tm = InputDisplay()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tsys.exit(m.get_exit_code())\n"
  },
  {
    "path": "scc/osd/keyboard.py",
    "content": "#!/usr/bin/env python2\n# -*- coding: utf-8 -*-\n\"\"\"\nSC-Controller - OSD Menu\n\nDisplay menu that user can navigate through and print chosen item id to stdout\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, Gdk, GdkX11, GObject, GLib, GdkPixbuf, cairo\nfrom xml.etree import ElementTree as ET\nfrom scc.constants import LEFT, RIGHT, STICK, STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.constants import STICK_PAD_MIN_HALF, STICK_PAD_MAX_HALF, CPAD\nfrom scc.constants import SCButtons, ControllerFlags\nfrom scc.tools import point_in_gtkrect, circle_to_square, clamp\nfrom scc.tools import find_profile, find_button_image\nfrom scc.paths import get_share_path, get_config_path\nfrom scc.parser import TalkingActionParser\nfrom scc.modifiers import ModeModifier\nfrom scc.menu_data import MenuData\nfrom scc.actions import Action\nfrom scc.profile import Profile\nfrom scc.config import Config\nfrom scc.uinput import Keys\nfrom scc.lib import xwrappers as X\nfrom scc.gui.svg_widget import SVGWidget, SVGEditor\nfrom scc.gui.keycode_to_key import KEY_TO_KEYCODE\nfrom scc.gui.daemon_manager import DaemonManager, ControllerManager\nfrom scc.gui.gdk_to_key import KEY_TO_GDK\nfrom scc.osd.timermanager import TimerManager\nfrom scc.osd.slave_mapper import SlaveMapper\nfrom scc.osd import OSDWindow\nimport scc.osd.osk_actions\n\nimport os, sys, json, logging\nlog = logging.getLogger(\"osd.keyboard\")\n\nSPECIAL_KEYS = {\n\t# Maps keycode to unicode character representing some\n\t# very special keys\n\t8: \"←\",\n\t9: \"⇥\",\n\t13: \"↲\",\n\t27: \"␛\",\n\t32: \"␣\",\n}\n\n\nclass KeyboardImage(Gtk.DrawingArea):\n\tLINE_WIDTH = 2\n\t\n\t__gsignals__ = {}\n\t\n\t\n\tdef __init__(self, image):\n\t\tGtk.DrawingArea.__init__(self)\n\t\tself.connect('size-allocate', self.on_size_allocate)\n\t\tself.connect('draw', self.on_draw)\n\t\t\n\t\tareas = []\n\t\tself.color_button1 = 0.8, 0, 0, 1\t\t\t# Just random mess,\n\t\tself.color_button1_border = 1, 0, 0, 1\t\t# config overrides it anyway\n\t\tself.color_button2 = 0.8, 0.8, 0, 1\n\t\tself.color_button2_border = 1, 1, 0, 1\n\t\tself.color_hilight = 0, 1, 1, 1\n\t\tself.color_pressed = 1, 1, 1, 1\n\t\tself.color_text = 1, 1, 1, 1\n\t\t\n\t\tself.overlay = SVGWidget(image, False)\n\t\tself.tree = ET.fromstring(self.overlay.current_svg.encode(\"utf-8\"))\n\t\tSVGWidget.find_areas(self.tree, None, areas, get_colors=True)\n\t\t\n\t\tself._hilight = ()\n\t\tself._pressed = ()\n\t\tself._button_images = {}\n\t\tself._help_areas = [ self.get_limit(\"HELP_LEFT\"), self.get_limit(\"HELP_RIGHT\") ]\n\t\tself._help_lines = ( [], [] )\n\t\t\n\t\t# TODO: It would be cool to use user-set font here, but cairo doesn't\n\t\t# have glyph replacement and most of default fonts (Ubuntu, Cantarell,\n\t\t# similar shit) misses pretty-much everything but letters, notably ↲\n\t\t#\n\t\t# For that reason, DejaVu Sans is hardcoded for now. On systems\n\t\t# where DejaVu Sans is not available, Cairo will automatically fallback\n\t\t# to default font.\n\t\tself.font_face = \"DejaVu Sans\"\n\t\t# self.font_face = Gtk.Label(label=\"X\").get_style().font_desc.get_family()\n\t\tlog.debug(\"Using font %s\", self.font_face)\n\t\t\n\t\tself.buttons = [ Button(self.tree, area) for area in areas ]\n\t\tbackground = SVGEditor.find_by_id(self.tree, \"BACKGROUND\")\n\t\tself.set_size_request(*SVGEditor.get_size(background))\n\t\tself.overlay.edit().keep(\"overlay\").commit()\n\t\tself.overlay.hilight({})\n\t\t# open(\"/tmp/a.svg\", \"w\").write(self.overlay.current_svg.encode(\"utf-8\"))\n\t\n\t\n\tdef hilight(self, hilight, pressed):\n\t\tself._hilight = hilight\n\t\tself._pressed = pressed\n\t\tself.queue_draw()\n\t\n\t\n\tdef set_help(self, left, right):\n\t\tself._help_lines = ( left, right )\n\t\tself.queue_draw()\n\t\n\t\n\tdef set_labels(self, labels):\n\t\tfor b in self.buttons:\n\t\t\tlabel = labels.get(b)\n\t\t\tif type(label) in (int,):\n\t\t\t\tpass\n\t\t\telif label:\n\t\t\t\t#b.label = label.encode(\"utf-8\")\n\t\t\t\tb.label = label\n\t\tself.queue_draw()\n\t\n\t\n\tdef get_limit(self, id):\n\t\ta = SVGEditor.find_by_id(self.tree, id)\n\t\twidth, height = 0, 0\n\t\t#if not hasattr(a, \"parent\"): a.parent = None\n\n\t\tx, y = SVGEditor.get_translation(a, absolute=True)\n\t\tif 'width' in a.attrib:  width = float(a.attrib['width'])\n\t\tif 'height' in a.attrib: height = float(a.attrib['height'])\n\t\treturn x, y, width, height\n\t\n\t\n\t@staticmethod\n\tdef increase_contrast(buf):\n\t\t\"\"\"\n\t\tTakes input image, which is assumed to be grayscale RGBA and turns it\n\t\tinto \"symbolic\" image by inverting colors of pixels where opacity is\n\t\tgreater than threshold.\n\t\t\"\"\"\n\t\tpixels = [ x for x in buf.get_pixels() ]\n\t\tbpp = 4 if buf.get_has_alpha() else 3\n\t\tw, h = buf.get_width(), buf.get_height()\n\t\tstride = buf.get_rowstride()\n\t\tfor i in range(0, len(pixels), bpp):\n\t\t\tif pixels[i + 3] > 64:\n\t\t\t\tpixels[i + 0] = 255 - pixels[i + 0]\n\t\t\t\tpixels[i + 1] = 255 - pixels[i + 1]\n\t\t\t\tpixels[i + 2] = 255 - pixels[i + 2]\n\t\t\n\t\tpixels = b\"\".join([ chr(x).encode('latin-1') for x in pixels])\n\t\trv = GdkPixbuf.Pixbuf.new_from_data(\n\t\t\tpixels,\n\t\t\tbuf.get_colorspace(),\n\t\t\tbuf.get_has_alpha(),\n\t\t\tbuf.get_bits_per_sample(),\n\t\t\tw, h, stride,\n\t\t\tNone\n\t\t)\n\t\trv.pixels = pixels\t# Has to be kept in memory\n\t\treturn rv\n\t\n\t\n\tdef get_button_image(self, x, size):\n\t\t\"\"\"\n\t\tLoads and returns button image as pixbuf.\n\t\tPixbufs are cached.\n\t\t\"\"\"\n\t\tif x not in self._button_images:\n\t\t\tpath, bw = find_button_image(x, prefer_bw=True)\n\t\t\tif path is None:\n\t\t\t\tself._button_images[x] = None\n\t\t\t\treturn None\n\t\t\tbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, size, size)\n\t\t\tbuf = self.increase_contrast(buf)\n\t\t\tself._button_images[x] = buf\n\t\ti = self._button_images[x]\n\t\treturn i\n\t\n\t\n\tdef on_draw(self, self2, ctx):\n\t\tctx.select_font_face(self.font_face, 0, 0)\n\t\t\n\t\tctx.set_line_width(self.LINE_WIDTH)\n\t\tctx.set_font_size(48)\n\t\tascent, descent, height, max_x_advance, max_y_advance = ctx.font_extents()\n\t\t\n\t\t# Buttons\n\t\tfor button in self.buttons:\n\t\t\tif button in self._pressed:\n\t\t\t\tctx.set_source_rgba(*self.color_pressed)\n\t\t\telif button in self._hilight:\n\t\t\t\tctx.set_source_rgba(*self.color_hilight)\n\t\t\telif button.dark:\n\t\t\t\tctx.set_source_rgba(*self.color_button2)\n\t\t\telse:\n\t\t\t\tctx.set_source_rgba(*self.color_button1)\n\t\t\t# filled rectangle\n\t\t\tx, y, w, h = button\n\t\t\tctx.move_to(x, y)\n\t\t\tctx.line_to(x + w, y)\n\t\t\tctx.line_to(x + w, y + h)\n\t\t\tctx.line_to(x, y + h)\n\t\t\tctx.line_to(x, y)\n\t\t\tctx.fill()\n\t\t\t\n\t\t\t# border\n\t\t\tctx.set_source_rgba(*self.color_button1_border)\n\t\t\tctx.move_to(x, y)\n\t\t\tctx.line_to(x + w, y)\n\t\t\tctx.line_to(x + w, y + h)\n\t\t\tctx.line_to(x, y + h)\n\t\t\tctx.line_to(x, y)\n\t\t\tctx.stroke()\n\t\t\t\n\t\t\t# label\n\t\t\tif button.label:\n\t\t\t\tctx.set_source_rgba(*self.color_text)\n\t\t\t\textents = ctx.text_extents(button.label)\n\t\t\t\tx_bearing, y_bearing, width, trash, x_advance, y_advance = extents\n\t\t\t\tctx.move_to(x + w * 0.5 - width * 0.5 - x_bearing, y + h * 0.5 + height * 0.3)\n\t\t\t\tctx.show_text(button.label)\n\t\t\t\tctx.stroke()\n\t\t\n\t\t# Overlay\n\t\tGdk.cairo_set_source_pixbuf(ctx, self.overlay.get_pixbuf(), 0, 0)\n\t\tctx.paint()\n\t\t\n\t\t# Help\n\t\tctx.set_source_rgba(*self.color_text)\n\t\tctx.set_font_size(16)\n\t\tascent, descent, height, max_x_advance, max_y_advance = ctx.font_extents()\n\t\tfor left_right in (0, 1):\n\t\t\tx, y, w, h = self._help_areas[left_right]\n\t\t\tlines = self._help_lines[left_right]\n\t\t\txx = x if left_right == 0 else x + w\n\t\t\tyy = y\n\t\t\tfor (icon, line) in lines:\n\t\t\t\tyy += height\n\t\t\t\tif yy > y + h:\n\t\t\t\t\tbreak\n\t\t\t\timage = self.get_button_image(icon, height * 0.9)\n\t\t\t\tif image is None: continue\n\t\t\t\tiw, ih = image.get_width(), image.get_height()\n\t\t\t\t\n\t\t\t\tif left_right == 1:\t# Right align\n\t\t\t\t\textents = ctx.text_extents(line)\n\t\t\t\t\tx_bearing, y_bearing, width, trash, x_advance, y_advance = extents\n\t\t\t\t\tctx.save()\n\t\t\t\t\tctx.translate(xx - height + (height - iw) * 0.5,\n\t\t\t\t\t\t1 + yy - (ascent + ih) * 0.5)\n\t\t\t\t\tGdk.cairo_set_source_pixbuf(ctx, image, 0, 0)\n\t\t\t\t\tctx.paint()\n\t\t\t\t\tctx.restore()\n\t\t\t\t\tctx.move_to(xx - x_bearing - width - 5 - height, yy)\n\t\t\t\telse:\n\t\t\t\t\tctx.save()\n\t\t\t\t\tctx.translate(1 + xx + (height - iw) * 0.5,\n\t\t\t\t\t\t1 + yy - (ascent + ih) * 0.5)\n\t\t\t\t\tGdk.cairo_set_source_pixbuf(ctx, image, 0, 0)\n\t\t\t\t\tctx.paint()\n\t\t\t\t\tctx.restore()\n\t\t\t\t\tctx.move_to(xx + 5 + height, yy)\n\t\t\t\t\t\n\t\t\t\tctx.show_text(line)\n\t\t\t\tctx.stroke()\n\t\n\t\n\tdef on_size_allocate(self, *a):\n\t\tpass\n\n\nclass Button:\n\t\n\tdef __init__(self, tree, area):\n\t\tself.contains = area.contains\n\t\tself.name = area.name\n\t\tself.label = None\n\t\tself.x, self.y = area.x, area.y\n\t\tself.w, self.h = area.w, area.h\n\t\tself.dark = area.color[2] < 0.5\t\t# Dark button is less than 50% blue\n\t\n\t\n\tdef __iter__(self):\n\t\treturn iter(( self.x, self.y, self.w, self.h ))\n\n\nclass Keyboard(OSDWindow, TimerManager):\n\tEPILOG=\"\"\"Exit codes:\n   0  - clean exit, user closed keyboard\n   1  - error, invalid arguments\n   2  - error, failed to access sc-daemon, sc-daemon reported error or died while keyboard is displayed.\n   3  - erorr, failed to lock input stick, pad or button(s)\n\t\"\"\"\n\tOSK_PROF_NAME = \".scc-osd.keyboard\"\n\t\n\tBUTTON_MAP = {\n\t\tSCButtons.A.name : Keys.KEY_ENTER,\n\t\tSCButtons.B.name : Keys.KEY_ESC,\n\t\tSCButtons.LB.name : Keys.KEY_BACKSPACE,\n\t\tSCButtons.RB.name : Keys.KEY_SPACE,\n\t\tSCButtons.LGRIP.name : Keys.KEY_LEFTSHIFT,\n\t\tSCButtons.RGRIP.name : Keys.KEY_RIGHTALT,\n\t}\n\t\n\tdef __init__(self, config=None):\n\t\tself.kbimage = os.path.join(get_config_path(), 'keyboard.svg')\n\t\tif not os.path.exists(self.kbimage):\n\t\t\t# Prefer image in ~/.config/scc, but load default one as fallback\n\t\t\tself.kbimage = os.path.join(get_share_path(), \"images\", 'keyboard.svg')\n\t\t\n\t\tTimerManager.__init__(self)\n\t\tOSDWindow.__init__(self, \"osd-keyboard\")\n\t\tself.daemon = None\n\t\tself.mapper = None\n\t\tself.keymap = Gdk.Keymap.get_default()\n\t\tself.keymap.connect('state-changed', self.on_keymap_state_changed)\n\t\tAction.register_all(sys.modules['scc.osd.osk_actions'], prefix=\"OSK\")\n\t\tself.profile = Profile(TalkingActionParser())\n\t\tself.config = config or Config()\n\t\tself.dpy = X.Display(hash(GdkX11.x11_get_default_xdisplay()))\n\t\tself.group = None\n\t\tself.limits = {}\n\t\tself.background = None\n\t\t\n\t\tcursor = os.path.join(get_share_path(), \"images\", 'menu-cursor.svg')\n\t\tself.cursors = {}\n\t\tself.cursors[LEFT] = Gtk.Image.new_from_file(cursor)\n\t\tself.cursors[LEFT].set_name(\"osd-keyboard-cursor\")\n\t\tself.cursors[RIGHT] = Gtk.Image.new_from_file(cursor)\n\t\tself.cursors[RIGHT].set_name(\"osd-keyboard-cursor\")\n\t\tself.cursors[CPAD] = Gtk.Image.new_from_file(cursor)\n\t\tself.cursors[CPAD].set_name(\"osd-keyboard-cursor\")\n\t\t\n\t\tself._eh_ids = []\n\t\tself._controller = None\n\t\tself._stick = 0, 0\n\t\tself._hovers = { self.cursors[LEFT]: None, self.cursors[RIGHT]: None }\n\t\tself._pressed = { self.cursors[LEFT]: None, self.cursors[RIGHT]: None }\n\t\tself._pressed_areas = {}\n\t\t\n\t\tself.c = Gtk.Box()\n\t\tself.c.set_name(\"osd-keyboard-container\")\n\t\t\n\t\tself.f = Gtk.Fixed()\n\t\n\t\n\tdef _create_background(self):\n\t\tself.background = KeyboardImage(self.args.image)\n\t\tself.recolor()\n\t\t\n\t\tself.limits = {}\n\t\tself.limits[LEFT]  = self.background.get_limit(\"LIMIT_LEFT\")\n\t\tself.limits[RIGHT] = self.background.get_limit(\"LIMIT_RIGHT\")\n\t\tself.limits[CPAD] = self.background.get_limit(\"LIMIT_CPAD\")\n\t\tself._pack()\n\t\n\t\n\tdef _pack(self):\n\t\tself.f.add(self.background)\n\t\tself.f.add(self.cursors[LEFT])\n\t\tself.f.add(self.cursors[RIGHT])\n\t\tself.f.add(self.cursors[CPAD])\n\t\tself.c.add(self.f)\n\t\tself.add(self.c)\n\t\n\t\n\tdef recolor(self):\n\t\t# TODO: keyboard description is probably not needed anymore\n\t\t_get = lambda a: SVGWidget.color_to_float(self.config['osk_colors'].get(a, \"\"))\n\t\tself.background.color_button1 = _get(\"button1\")\n\t\tself.background.color_button1_border = _get(\"button1_border\")\n\t\tself.background.color_button2 = _get(\"button2\")\n\t\tself.background.color_button2_border = _get(\"button2_border\")\n\t\tself.background.color_hilight = _get(\"hilight\")\n\t\tself.background.color_pressed = _get(\"pressed\")\n\t\tself.background.color_text = _get(\"text\")\n\t\n\t\n\tdef use_daemon(self, d):\n\t\t\"\"\"\n\t\tAllows (re)using already existing DaemonManager instance in same process\n\t\t\"\"\"\n\t\tself.daemon = d\n\t\tself._cononect_handlers()\n\t\tself.on_daemon_connected(self.daemon)\n\t\n\t\n\tdef on_keymap_state_changed(self, x11keymap):\n\t\tif not self.timer_active('labels'):\n\t\t\tself.timer('labels', 0.1, self.update_labels)\n\t\n\t\n\tdef set_help(self):\n\t\t\"\"\"\n\t\tUpdates help shown on keyboard image.\n\t\tKeyboard bindings don't change on the fly, so this is done only\n\t\tright after start or when daemon is reconfigured.\n\t\t\"\"\"\n\t\tif self._controller is None:\n\t\t\t# Not yet connected\n\t\t\treturn\n\t\tgui_config = self._controller.load_gui_config(os.path.join(get_share_path(), \"images\"))\n\t\tl_lines, r_lines, used = [], [], set()\n\t\t\n\t\tdef add_action(side, button, a):\n\t\t\tif not a:\n\t\t\t\treturn\n\t\t\tif isinstance(a, scc.osd.osk_actions.OSKCursorAction):\n\t\t\t\tif a.side != CPAD: return\n\t\t\tif isinstance(a, ModeModifier):\n\t\t\t\tfor x in a.get_child_actions():\n\t\t\t\t\tadd_action(side, button, x)\n\t\t\t\treturn\n\t\t\tdesc = a.describe(Action.AC_OSK)\n\t\t\tif desc in used:\n\t\t\t\tif isinstance(a, scc.osd.osk_actions.OSKPressAction):\n\t\t\t\t\t# Special case, both triggers are set to \"press a key\"\n\t\t\t\t\tpass\n\t\t\t\telse:\n\t\t\t\t\treturn\n\t\t\ticon = self._controller.get_button_name(gui_config, button)\n\t\t\tside.append(( icon, desc ))\n\t\t\tused.add(desc)\n\t\t\n\t\tdef add_button(side, b):\n\t\t\tadd_action(side, b, self.profile.buttons[b])\n\t\t\n\t\tif self._controller.get_flags() & ControllerFlags.NO_GRIPS == 0:\n\t\t\tadd_button(l_lines, SCButtons.LGRIP)\n\t\t\tadd_button(r_lines, SCButtons.RGRIP)\n\t\tadd_action(l_lines, SCButtons.LT, self.profile.triggers[LEFT])\n\t\tadd_action(r_lines, SCButtons.RT, self.profile.triggers[RIGHT])\n\t\tfor b in (SCButtons.LB, SCButtons.Y, SCButtons.X):\n\t\t\tadd_button(l_lines, b)\n\t\tfor b in (SCButtons.RB, SCButtons.B, SCButtons.A):\n\t\t\tadd_button(r_lines, b)\n\t\t\n\t\tif self._controller.get_flags() & ControllerFlags.HAS_CPAD != 0:\n\t\t\tfor lst in (l_lines, r_lines):\n\t\t\t\twhile len(lst) > 3: lst.pop()\n\t\t\t\twhile len(lst) < 3: lst.append((None, \"\"))\n\t\t\tadd_action(r_lines, CPAD, self.profile.pads[CPAD])\n\t\tadd_action(l_lines, SCButtons.STICKPRESS, self.profile.stick)\n\t\t\n\t\tself.background.set_help(l_lines, r_lines)\n\t\n\t\n\tdef update_labels(self):\n\t\t\"\"\" Updates keyboard labels based on active X keymap \"\"\"\n\t\t\n\t\tlabels = {}\n\t\t# Get current layout group\n\t\tself.group = X.get_xkb_state(self.dpy).group\n\t\t# Get state of shift/alt/ctrl key\n\t\tmt = Gdk.ModifierType(self.keymap.get_modifier_state())\n\t\tfor button in self.background.buttons:\n\t\t\tif getattr(Keys, button.name, None) in KEY_TO_KEYCODE:\n\t\t\t\tkeycode = KEY_TO_KEYCODE[getattr(Keys, button.name)]\n\t\t\t\ttranslation = self.keymap.translate_keyboard_state(keycode, mt, self.group)\n\t\t\t\tif hasattr(translation, \"keyval\"):\n\t\t\t\t\tcode = Gdk.keyval_to_unicode(translation.keyval)\n\t\t\t\telse:\n\t\t\t\t\tcode = Gdk.keyval_to_unicode(translation[1])\n\t\t\t\tif code >= 33:\t\t\t \t\t# Printable chars, w/out space\n\t\t\t\t\tlabels[button] = chr(code).strip()\n\t\t\t\telse:\n\t\t\t\t\tlabels[button] = SPECIAL_KEYS.get(code)\n\t\tself.background.set_labels(labels)\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('image', type=str, nargs=\"?\",\n\t\t\tdefault = self.kbimage, help=\"keyboard image to use\")\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\treturn True\n\t\n\t\n\tdef _cononect_handlers(self):\n\t\tself._eh_ids += [\n\t\t\t( self.daemon, self.daemon.connect('dead', self.on_daemon_died) ),\n\t\t\t( self.daemon, self.daemon.connect('error', self.on_daemon_died) ),\n\t\t\t( self.daemon, self.daemon.connect('reconfigured', self.on_reconfigured) ),\n\t\t\t( self.daemon, self.daemon.connect('alive', self.on_daemon_connected) ),\n\t\t]\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself._cononect_handlers()\n\t\tOSDWindow.run(self)\n\t\n\t\n\tdef load_profile(self):\n\t\tself.profile.load(find_profile(Keyboard.OSK_PROF_NAME)).compress()\n\t\tself.set_help()\n\t\n\t\n\tdef on_reconfigured(self, *a):\n\t\tself.load_profile()\n\t\tlog.debug(\"Reloaded profile\")\n\t\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tdef success(*a):\n\t\t\tlog.info(\"Sucessfully locked input\")\n\t\t\tpass\n\t\t\n\t\tc = self.choose_controller(self.daemon)\n\t\tif c is None or not c.is_connected():\n\t\t\t# There is no controller connected to daemon\n\t\t\tself.on_failed_to_lock(\"Controller not connected\")\n\t\t\treturn\n\t\t\n\t\tself._eh_ids += [\n\t\t\t(c, c.connect('event', self.on_event)),\n\t\t\t(c, c.connect('lost', self.on_controller_lost)),\n\t\t]\n\t\t\n\t\t# TODO: Single-handed mode for PS4 posponed\n\t\tlocks = [ LEFT, RIGHT, STICK, \"STICKPRESS\" ] + [ b.name for b in SCButtons ]\n\t\tif (c.get_flags() & ControllerFlags.HAS_CPAD) == 0:\n\t\t\t# Two pads, two hands\n\t\t\tlocks = [ LEFT, RIGHT, STICK, \"STICKPRESS\" ] + [ b.name for b in SCButtons ]\n\t\t\tself.cursors[CPAD].hide()\n\t\telse:\n\t\t\t# Single-handed mode\n\t\t\tlocks = [ CPAD, \"CPADPRESS\", STICK, \"STICKPRESS\" ] + [ b.name for b in SCButtons ]\n\t\t\tself._hovers[self.cursors[RIGHT]] = None\n\t\t\tself._hovers = { self.cursors[CPAD] : None }\n\t\t\tself._pressed = { self.cursors[CPAD] : None }\n\t\t\tself.cursors[LEFT].hide()\n\t\t\tself.cursors[RIGHT].hide()\n\t\t\t\n\t\t\t# There is no configurable nor default mapping for CPDAD,\n\t\t\t# so situable mappings are hardcoded here\n\t\t\tself.profile.pads[CPAD] = scc.osd.osk_actions.OSKCursorAction(CPAD)\n\t\t\tself.profile.pads[CPAD].speed = [ 0.85, 1.2 ]\n\t\t\tself.profile.buttons[SCButtons.CPADPRESS] = scc.osd.osk_actions.OSKPressAction(CPAD)\n\t\t\t\n\t\t\tfor i in (LEFT, RIGHT):\n\t\t\t\tif isinstance(self.profile.triggers[i], scc.osd.osk_actions.OSKPressAction):\n\t\t\t\t\tself.profile.triggers[i] = scc.osd.osk_actions.OSKPressAction(CPAD)\n\t\t\n\t\tself._controller = c\n\t\tc.lock(success, self.on_failed_to_lock, *locks)\n\t\tself.set_help()\n\t\n\t\n\tdef quit(self, code=-1):\n\t\tif self.get_controller():\n\t\t\tself.get_controller().unlock_all()\n\t\tfor source, eid in self._eh_ids:\n\t\t\tsource.disconnect(eid)\n\t\tself._eh_ids = []\n\t\tdel self.mapper\n\t\tOSDWindow.quit(self, code)\n\t\n\t\n\tdef show(self, *a):\n\t\tif self.background is None:\n\t\t\tself._create_background()\n\t\tOSDWindow.show(self, *a)\n\t\tself.load_profile()\n\t\tself.mapper = SlaveMapper(self.profile, None,\n\t\t\tkeyboard=b\"SCC OSD Keyboard\", mouse=b\"SCC OSD Mouse\")\n\t\tself.mapper.set_special_actions_handler(self)\n\t\tself.set_cursor_position(0, 0, self.cursors[LEFT], self.limits[LEFT])\n\t\tself.set_cursor_position(0, 0, self.cursors[RIGHT], self.limits[RIGHT])\n\t\tself.set_cursor_position(0, 0, self.cursors[CPAD], self.limits[CPAD])\n\t\tself.timer('labels', 0.1, self.update_labels)\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\t\"\"\"\n\t\tCalled when button press, button release or stick / pad update is\n\t\tsend by daemon.\n\t\t\"\"\"\n\t\tgroup = X.get_xkb_state(self.dpy).group\n\t\tif self.group != group:\n\t\t\tself.group = group\n\t\t\tself.timer('labels', 0.1, self.update_labels)\n\t\tself.mapper.handle_event(daemon, what, data)\n\t\n\t\n\tdef on_sa_close(self, *a):\n\t\t\"\"\" Called by CloseOSDKeyboardAction \"\"\"\n\t\tself.quit(0)\n\t\n\t\n\tdef on_sa_cursor(self, mapper, action, x, y):\n\t\tself.set_cursor_position(\n\t\t\tx * action.speed[0],\n\t\t\ty * action.speed[1],\n\t\t\tself.cursors[action.side], self.limits[action.side])\n\t\n\t\n\tdef on_sa_move(self, mapper, action, x, y):\n\t\tself._stick = x, y\n\t\tif not self.timer_active('stick'):\n\t\t\tself.timer(\"stick\", 0.05, self._move_window)\n\t\n\t\n\tdef on_sa_press(self, mapper, action, pressed):\n\t\tself.key_from_cursor(self.cursors[action.side], pressed)\n\t\n\t\n\tdef set_cursor_position(self, x, y, cursor, limit):\n\t\t\"\"\"\n\t\tMoves cursor image.\n\t\t\"\"\"\n\t\tif cursor not in self._hovers: return\n\t\tw = limit[2] - (cursor.get_allocation().width * 0.5)\n\t\th = limit[3] - (cursor.get_allocation().height * 0.5)\n\t\tx = x / float(STICK_PAD_MAX)\n\t\ty = y / float(STICK_PAD_MAX) * -1.0\n\t\t\n\t\tx, y = circle_to_square(x, y)\n\t\t\n\t\tx = clamp(\n\t\t\tcursor.get_allocation().width * 0.5,\n\t\t\t(limit[0] + w * 0.5) + x * w * 0.5,\n\t\t\tself.get_allocation().width - cursor.get_allocation().width\n\t\t\t)\n\t\t\n\t\ty = clamp(\n\t\t\tcursor.get_allocation().height * 0.5,\n\t\t\t(limit[1] + h * 0.5) + y * h * 0.5,\n\t\t\tself.get_allocation().height - cursor.get_allocation().height\n\t\t\t)\n\t\t\n\t\tcursor.position = int(x), int(y)\n\t\tself.f.move(cursor,\n\t\t\tx - cursor.get_allocation().width * 0.5,\n\t\t\ty - cursor.get_allocation().height * 0.5)\n\t\tfor button in self.background.buttons:\n\t\t\tif button.contains(x, y):\n\t\t\t\tif button != self._hovers[cursor]:\n\t\t\t\t\tself._hovers[cursor] = button\n\t\t\t\t\tif self._pressed[cursor] is not None:\n\t\t\t\t\t\tself.mapper.keyboard.releaseEvent([ self._pressed[cursor] ])\n\t\t\t\t\t\tself.key_from_cursor(cursor, True)\n\t\t\t\t\tif not self.timer_active('update'):\n\t\t\t\t\t\tself.timer('update', 0.01, self.update_background)\n\t\t\t\t\tbreak\n\t\n\t\n\tdef update_background(self, *whatever):\n\t\t\"\"\"\n\t\tUpdates hilighted keys on bacgkround image.\n\t\t\"\"\"\n\t\tself.background.hilight(\n\t\t\tset([ a for a in self._hovers.values() if a ]),\n\t\t\tset([ a for a in self._pressed_areas.values() if a ])\n\t\t)\n\t\n\t\n\tdef _move_window(self, *a):\n\t\t\"\"\"\n\t\tCalled by timer while stick is tilted to move window around the screen.\n\t\t\"\"\"\n\t\tx, y = self._stick\n\t\tx = x * 50.0 / STICK_PAD_MAX\n\t\ty = y * -50.0 / STICK_PAD_MAX\n\t\trx, ry = self.get_position()\n\t\tself.move(rx + x, ry + y)\n\t\tif abs(self._stick[0]) > 100 or abs(self._stick[1]) > 100:\n\t\t\tself.timer(\"stick\", 0.05, self._move_window)\n\t\n\t\n\tdef key_from_cursor(self, cursor, pressed):\n\t\t\"\"\"\n\t\tSends keypress/keyrelease event to emulated keyboard, based on\n\t\tposition of cursor on OSD keyboard.\n\t\t\"\"\"\n\t\tx, y = cursor.position\n\t\t\n\t\tif pressed:\n\t\t\tfor button in self.background.buttons:\n\t\t\t\tif button.contains(x, y):\n\t\t\t\t\tif button.name.startswith(\"KEY_\") and hasattr(Keys, button.name):\n\t\t\t\t\t\tkey = getattr(Keys, button.name)\n\t\t\t\t\t\tif self._pressed[cursor] is not None:\n\t\t\t\t\t\t\tself.mapper.keyboard.releaseEvent([ self._pressed[cursor] ])\n\t\t\t\t\t\tself.mapper.keyboard.pressEvent([ key ])\n\t\t\t\t\t\tself._pressed[cursor] = key\n\t\t\t\t\t\tself._pressed_areas[cursor] = button\n\t\t\t\t\tbreak\n\t\telif self._pressed[cursor] is not None:\n\t\t\tself.mapper.keyboard.releaseEvent([ self._pressed[cursor] ])\n\t\t\tself._pressed[cursor] = None\n\t\t\tdel self._pressed_areas[cursor]\n\t\tif not self.timer_active('update'):\n\t\t\tself.timer('update', 0.01, self.update_background)\n\n\ndef main():\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tinit_logging()\n\t\n\tk = Keyboard()\n\tif not k.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tk.run()\n\n\nif __name__ == \"__main__\":\n\timport signal\n\t\n\tdef sigint(*a):\n\t\tprint(\"\\n*break*\")\n\t\tsys.exit(-1)\n\t\n\tsignal.signal(signal.SIGINT, sigint)\n\tmain()\n"
  },
  {
    "path": "scc/osd/launcher.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Launcher\n\nDisplay launcher with phone-like keyboard that user can use to select\napplication (list is generated using xdg) and start it.\n\nReuses styles from OSD Menu and OSD Dialog\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, Gio, GdkX11, Pango\nfrom scc.constants import STICK_PAD_MAX, DEFAULT, LEFT, RIGHT, STICK\nfrom scc.tools import point_in_gtkrect, circle_to_square, clamp\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.osd import OSDWindow, StickController\nfrom scc.paths import get_share_path\nfrom scc.lib import xwrappers as X\nfrom scc.config import Config\n\nimport os, logging\nlog = logging.getLogger(\"osd.menu\")\n\n\nclass Launcher(OSDWindow):\n\tEPILOG=\"\"\"Exit codes:\n   0  - clean exit, user selected option\n  -1  - clean exit, user canceled dialog\n   1  - error, invalid arguments\n   2  - error, failed to access sc-daemon, sc-daemon reported error or died while dialog is displayed.\n   3  - erorr, failed to lock input stick, pad or button(s)\n\t\"\"\"\n\t\n\tBUTTONS = [\n\t\t\"1\",\t \t\"2 ABC\",\t\"3 DEF\",\n\t\t\"5 GHI\", \t\"5 JKL\",\t\"6 MNO\",\n\t\t\"7 PQRS\",\t\"8 TUV\",\t\"9 WXYZ\",\n\t\t\"\", \t\t\"0\"\n\t]\n\t\n\tVALID_CHARS = \"12ABC3DEF5GHI5JKL6MNO7PQRS8TUV9WXYZ0\"\n\tCHAR_TO_NUMBER = { }\t# Generated on runtime\n\t\n\tMAX_ROWS = 5\n\t\n\t_app_db = None\t# Static list of all know applications\n\t\n\tdef __init__(self, cls=\"osd-menu\"):\n\t\tself._buttons = None\n\t\tself._string = \"\"\n\t\t\n\t\tOSDWindow.__init__(self, cls)\n\t\tself.daemon = None\n\t\tself.config = None\n\t\tself.feedback = None\n\t\tself.controller = None\n\t\tself.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))\t# Magic\n\t\t\n\t\tself.create_parent()\n\t\tself.create_app_list()\n\t\tself.create_buttons()\n\t\t\n\t\tcursor = os.path.join(get_share_path(), \"images\", 'menu-cursor.svg')\n\t\tself.cursors = [ Gtk.Image.new_from_file(cursor), Gtk.Image.new_from_file(cursor) ]\n\t\tfor c in self.cursors:\n\t\t\tc.set_name(\"osd-menu-cursor\")\n\t\t\tc.selected = None\n\t\t\tself.f.add(c)\n\t\tself.f.show_all()\n\t\t\n\t\tself._scon = StickController()\n\t\tself._scon.connect(\"direction\", self.on_stick_direction)\n\t\tself._selected = None\n\t\tself._menuid = None\n\t\tself._eh_ids = []\n\t\tself._confirm_with = 'A'\n\t\tself._cancel_with = 'B'\n\t\t\n\t\tif Launcher._app_db is None:\n\t\t\tLauncher._app_db = []\n\t\t\tfor x in Launcher.BUTTONS:\n\t\t\t\tfor c in x:\n\t\t\t\t\tLauncher.CHAR_TO_NUMBER[c] = x[0]\n\t\t\t\n\t\t\tfor x in Gio.AppInfo.get_all():\n\t\t\t\ttry:\n\t\t\t\t\tLauncher._app_db.append(( Launcher.name_to_keys(x), x ))\n\t\t\t\texcept UnicodeDecodeError:\n\t\t\t\t\t# Just fuck them...\n\t\t\t\t\tpass\n\t\n\t\n\t@staticmethod\n\tdef name_to_keys(appinfo):\n\t\tname = \"\".join([\n\t\t\tLauncher.CHAR_TO_NUMBER[x]\n\t\t\tfor x in appinfo.get_display_name().upper()\n\t\t\tif x in Launcher.VALID_CHARS\n\t\t])\n\t\treturn name\n\t\n\t\n\t@staticmethod\n\tdef string_to_keys_and_spaces(string):\n\t\tname = \"\".join([\n\t\t\tLauncher.CHAR_TO_NUMBER[x] if x in Launcher.VALID_CHARS else \" \"\n\t\t\tfor x in string.upper()\n\t\t])\n\t\treturn name\n\t\n\t\n\tdef create_parent(self):\n\t\tself.parent = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)\n\t\tself.parent.set_name(\"osd-dialog\")\n\t\tself.f = Gtk.Fixed()\n\t\tself.f.add(self.parent)\n\t\tself.add(self.f)\n\t\n\t\n\tdef create_app_list(self):\n\t\tlst = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)\n\t\tlst.set_name(\"osd-application-list\")\n\t\tself.items = [ self.generate_widget(\"\") for x in range(self.MAX_ROWS) ]\n\t\tfor a in self.items:\n\t\t\tlst.pack_start(a, False, True, 0)\n\t\tself.parent.pack_start(lst, True, True, 0)\n\t\tself._set_launchers([  ])\n\t\tlst.show_all()\n\t\n\t\n\tdef create_buttons(self):\n\t\tself.grid = Gtk.Grid()\n\t\tself.parent.pack_start(self.grid, True, True, 0)\n\t\tself._buttons = []\n\t\t\n\t\tx, y = 0, 0\n\t\tfor label in self.BUTTONS:\n\t\t\tif label:\n\t\t\t\tw = self.generate_widget(label)\n\t\t\t\tw.set_name(\"osd-key-buton\")\n\t\t\t\tself.grid.attach(w, x, y, 1, 1)\n\t\t\t\tself._buttons.append(w)\n\t\t\tx += 1\n\t\t\tif x > 2:\n\t\t\t\tx = 0\n\t\t\t\ty += 1\n\t\t\n\t\t\n\t\tw = self.generate_widget(_(\"Run\"))\n\t\tself.grid.attach(w, x, y, 1, 1)\n\t\t\n\t\t\n\t\tself.grid.set_name(\"osd-dialog-buttons\")\n\t\n\t\n\tdef pack_items(self, parent, items):\n\t\tfor item in items:\n\t\t\tif hasattr(item.widget, \"set_alignment\"):\n\t\t\t\titem.widget.set_alignment(0.5, 0.5)\n\t\t\tself._buttons.pack_end(item.widget, True, True, 0)\n\t\n\t\n\tdef use_daemon(self, d):\n\t\t\"\"\"\n\t\tAllows (re)using already existing DaemonManager instance in same process.\n\t\tuse_config() should be be called before parse_argumets() if this is used.\n\t\t\"\"\"\n\t\tself.daemon = d\n\t\tself._connect_handlers()\n\t\tself.on_daemon_connected(self.daemon)\n\t\n\t\n\tdef use_config(self, c):\n\t\t\"\"\"\n\t\tAllows reusing already existin Config instance in same process.\n\t\tHas to be called before parse_argumets()\n\t\t\"\"\"\n\t\tself.config = c\n\t\n\t\n\tdef get_menuid(self):\n\t\t\"\"\"\n\t\tReturns ID of used menu.\n\t\t\"\"\"\n\t\treturn None\n\t\n\t\n\tdef get_selected_item_id(self):\n\t\t\"\"\"\n\t\tReturns ID of selected item or None if nothing is selected.\n\t\t\"\"\"\n\t\treturn None\n\t\n\t\n\tdef _launch(self):\n\t\tself._selected.launcher.launch()\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('--confirm-with', type=str,\n\t\t\tmetavar=\"button\", default=DEFAULT,\n\t\t\thelp=\"button used to confirm choice\")\n\t\tself.argparser.add_argument('--cancel-with', type=str,\n\t\t\tmetavar=\"button\", default=DEFAULT,\n\t\t\thelp=\"button used to cancel dialog\")\n\t\tself.argparser.add_argument('--feedback-amplitude', type=int,\n\t\t\thelp=\"enables and sets power of feedback effect generated when active menu option is changed\")\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\t\n\t\tif self.args.feedback_amplitude:\n\t\t\tside = \"LEFT\"\n\t\t\tself.feedback = side, int(self.args.feedback_amplitude)\n\t\t\n\t\t# Create buttons that are displayed on screen\n\t\treturn True\n\t\n\t\n\tdef _set_launchers(self, launchers):\n\t\tlaunchers = launchers[0:self.MAX_ROWS]\n\t\tfor x in self.items:\n\t\t\tx.set_label(\"\")\n\t\t\tx.set_name(\"osd-hidden-item\")\n\t\t\tx.launcher = None\n\t\tfor i in range(0, len(launchers)):\n\t\t\tself.items[i].set_name(\"osd-launcher-item\")\n\t\t\tself.items[i].launcher = launchers[i]\n\t\t\tlabel = self.items[i].get_children()[0]\n\t\t\tlabel.set_markup(self._format_label_markup(launchers[i]))\n\t\t\tlabel.set_max_width_chars(1)\n\t\t\tlabel.set_ellipsize(Pango.EllipsizeMode.MIDDLE)\n\t\t\tlabel.set_xalign(0)\n\t\n\t\n\tdef _format_label_markup(self, label):\n\t\tif hasattr(label, \"get_display_name\"):\n\t\t\tlabel = label.get_display_name()\n\t\telse:\n\t\t\tlabel = str(label)\n\t\t\n\t\tdef _check(substr):\n\t\t\ti, ch = 0, self._string\n\t\t\twhile len(substr) > 0 and substr[0] == ch[0]:\n\t\t\t\tch = ch[1:]\n\t\t\t\tsubstr = substr[1:]\n\t\t\t\ti += 1\n\t\t\t\tif len(ch) == 0: return i\n\t\t\twhile len(substr) > 0 and substr[0] == \" \":\n\t\t\t\tsubstr = substr[1:]\n\t\t\t\ti += 1\n\t\t\treturn -1\n\t\t\t\n\t\tkeys = Launcher.string_to_keys_and_spaces(label)\n\t\tindex1, index2 = -1, -1\n\t\tfor i in range(0, len(keys)):\n\t\t\tif keys[i] == self._string[0]:\n\t\t\t\tindex2 = _check(keys[i:])\n\t\t\t\tif index2 > 0:\n\t\t\t\t\tindex1 = i\n\t\t\t\t\tindex2 = i + index2\n\t\t\t\t\tbreak\n\t\t\n\t\tlabel = \"%s<span color='#%s'>%s</span>%s\" % (\n\t\t\tlabel[0:index1],\n\t\t\tself.config[\"osd_colors\"][\"menuitem_hilight_text\"],\n\t\t\tlabel[index1:index2],\n\t\t\tlabel[index2:]\n\t\t)\n\t\treturn label\n\t\n\t\n\tdef _update_items(self):\n\t\tif len(self._string) > 0:\n\t\t\tgen = ( item for (keys, item) in self._app_db if self._string in keys )\n\t\t\tlaunchers = []\n\t\t\tfor i in gen:\n\t\t\t\tlaunchers.append(i)\n\t\t\t\tif len(launchers) > self.MAX_ROWS: break\n\t\t\tself._set_launchers(launchers)\n\t\t\tself.select(0)\n\t\telse:\n\t\t\tself._set_launchers([])\n\t\n\t\n\tdef generate_widget(self, label):\n\t\t\"\"\" Generates gtk widget for specified menutitem \"\"\"\n\t\tif hasattr(label, \"label\"): label = label.label\n\t\twidget = Gtk.Button.new_with_label(label)\n\t\twidget.set_relief(Gtk.ReliefStyle.NONE)\n\t\tif hasattr(widget.get_children()[0], \"set_xalign\"):\n\t\t\twidget.get_children()[0].set_xalign(0)\n\t\telse:\n\t\t\twidget.get_children()[0].set_halign(Gtk.Align.START)\n\t\twidget.set_name(\"osd-menu-item\")\n\t\t\n\t\treturn widget\n\t\n\t\n\tdef select(self, index):\n\t\tif self._selected:\n\t\t\tself._selected.set_name(self._selected.get_name()\n\t\t\t\t.replace(\"-selected\", \"\"))\n\t\t\tself._selected = None\n\t\tif self.items[index].launcher is not None:\n\t\t\tself._selected = self.items[index]\n\t\t\tself._selected.set_name(\n\t\t\t\t\tself._selected.get_name() + \"-selected\")\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef _connect_handlers(self):\n\t\tself._eh_ids += [\n\t\t\t(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('error', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),\n\t\t]\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself._connect_handlers()\n\t\tOSDWindow.run(self)\n\t\n\t\n\tdef show(self, *a):\n\t\tif not self.select(0):\n\t\t\tself.next_item(1)\n\t\tOSDWindow.show(self, *a)\n\t\tfor c in self.cursors:\n\t\t\tc.set_visible(False)\n\t\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tdef success(*a):\n\t\t\tlog.error(\"Sucessfully locked input\")\n\t\t\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\tself.controller = self.choose_controller(self.daemon)\n\t\tif self.controller is None or not self.controller.is_connected():\n\t\t\t# There is no controller connected to daemon\n\t\t\tself.on_failed_to_lock(\"Controller not connected\")\n\t\t\treturn\n\t\t\n\t\tccfg = self.config.get_controller_config(self.controller.get_id())\n\t\tself._confirm_with = ccfg[\"menu_confirm\"] if self.args.confirm_with == DEFAULT else self.args.confirm_with\n\t\tself._cancel_with = ccfg[\"menu_cancel\"] if self.args.cancel_with == DEFAULT else self.args.cancel_with\n\t\t\n\t\tself._eh_ids += [\n\t\t\t(self.controller, self.controller.connect('event', self.on_event)),\n\t\t\t(self.controller, self.controller.connect('lost', self.on_controller_lost)),\n\t\t]\n\t\tlocks = [ LEFT, RIGHT, STICK, \"LPAD\", \"RPAD\", \"LB\",\n\t\t\tself._confirm_with, self._cancel_with ]\n\t\tself.controller.lock(success, self.on_failed_to_lock, *locks)\n\t\n\t\n\tdef quit(self, code=-2):\n\t\tif self.get_controller():\n\t\t\tself.get_controller().unlock_all()\n\t\tfor source, eid in self._eh_ids:\n\t\t\tsource.disconnect(eid)\n\t\tself._eh_ids = []\n\t\tOSDWindow.quit(self, code)\n\t\n\t\n\tdef next_item(self, direction):\n\t\t\"\"\" Selects next menu item, based on self._direction \"\"\"\n\t\tstart, i = -1, 0\n\t\ttry:\n\t\t\tstart = self.items.index(self._selected)\n\t\t\ti = start + direction\n\t\texcept: pass\n\t\twhile True:\n\t\t\tif i == start:\n\t\t\t\t# Cannot find valid menu item\n\t\t\t\tself.select(start)\n\t\t\t\tbreak\n\t\t\tif i >= len(self.items):\n\t\t\t\ti = 0\n\t\t\t\tcontinue\n\t\t\tif i < 0:\n\t\t\t\ti = len(self.items) - 1\n\t\t\t\tcontinue\n\t\t\tif self.select(i): break\n\t\t\ti += direction\n\t\t\tif start < 0: start = 0\n\t\n\t\n\tdef on_stick_direction(self, trash, x, y):\n\t\tif y != 0:\n\t\t\tself.next_item(y)\n\t\n\t\n\tdef _move_cursor(self, cursor, x, y):\n\t\tif (x, y) == (0, 0):\n\t\t\tcursor.set_visible(False)\n\t\t\treturn\n\t\tcursor.set_visible(True)\n\t\tpad_w = cursor.get_allocation().width * 0.5\n\t\tpad_h = cursor.get_allocation().height * 0.5\n\t\tmax_w = self.grid.get_allocation().width - 2 * pad_w\n\t\tmax_h = self.grid.get_allocation().height - 2 * pad_h\n\t\t\n\t\tx, y = circle_to_square(x / (STICK_PAD_MAX * 2.0), y / (STICK_PAD_MAX * 2.0))\n\t\tx = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)\n\t\ty = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)\n\t\tx += self.grid.get_allocation().x\n\t\ty += self.grid.get_allocation().y\n\t\tself.f.move(cursor, int(x), int(y))\n\t\t\n\t\tfor i in self._buttons:\n\t\t\tif point_in_gtkrect(i.get_allocation(), x, y):\n\t\t\t\tif cursor.selected:\n\t\t\t\t\tcursor.selected.set_name(\"osd-key-buton\")\n\t\t\t\tcursor.selected = i\n\t\t\t\tcursor.selected.set_name(\"osd-key-buton-hilight\")\n\t\t\t\tbreak\n\t\n\t\n\tdef _get_under_cursor(self, cursor):\n\t\tx, y = self.f.child_get(cursor, \"x\", \"y\")\n\t\tfor i in self._buttons:\n\t\t\tif point_in_gtkrect(i.get_allocation(), x, y):\n\t\t\t\treturn i\n\t\treturn None\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\tif what == LEFT:\n\t\t\tself._move_cursor(self.cursors[0], *data)\n\t\telif what == \"LPAD\" and data[0] == 1:\n\t\t\tb = self._get_under_cursor(self.cursors[0])\n\t\t\tif b: self._string += b.get_label()[0]\n\t\t\tself._update_items()\n\t\telif what == RIGHT:\n\t\t\tself._move_cursor(self.cursors[1], *data)\n\t\telif what == \"RPAD\" and data[0] == 1:\n\t\t\tb = self._get_under_cursor(self.cursors[1])\n\t\t\tif b: self._string += b.get_label()[0]\n\t\t\tself._update_items()\n\t\telif what == \"LB\":\n\t\t\tif len(self._string) > 0:\n\t\t\t\tself._string = self._string[:-1]\n\t\t\t\tself._update_items()\n\t\telif what == STICK:\n\t\t\tself._scon.set_stick(*data)\n\t\telif what == self._cancel_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tself.quit(-1)\n\t\telif what == self._confirm_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tif self._selected:\n\t\t\t\t\tself._launch()\n\t\t\t\t\tself.quit(0)\n\t\t\t\telse:\n\t\t\t\t\tself.quit(-1)\n"
  },
  {
    "path": "scc/osd/menu.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Menu\n\nDisplay menu that user can navigate through and prints chosen item id to stdout\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom gi.repository import Gtk, GLib, Gio, Gdk, GdkX11, GdkPixbuf\nfrom scc.tools import point_in_gtkrect, find_menu, find_icon\nfrom scc.tools import circle_to_square, clamp\nfrom scc.constants import LEFT, RIGHT, SAME, STICK, ControllerFlags\nfrom scc.constants import DEFAULT, STICK_PAD_MAX, SCButtons\nfrom scc.menu_data import MenuData, Separator, Submenu\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.osd import OSDWindow, StickController\nfrom scc.paths import get_share_path\nfrom scc.lib import xwrappers as X\nfrom scc.config import Config\nfrom math import sqrt\n\nimport os, sys, logging\nlog = logging.getLogger(\"osd.menu\")\n\n# Fill MENU_GENERATORS dict\nimport scc.osd.menu_generators\nimport scc.x11.autoswitcher\n\n\nclass Menu(OSDWindow):\n\tEPILOG=\"\"\"Exit codes:\n   0  - clean exit, user selected option\n  -1  - clean exit, user canceled menu\n  -2  - clean exit, menu closed from callback method\n   1  - error, invalid arguments\n   2  - error, failed to access sc-daemon, sc-daemon reported error or died while menu is displayed.\n   3  - erorr, failed to lock input stick, pad or button(s)\n\t\"\"\"\n\tSUBMENU_OFFSET = 50\n\tPREFER_BW_ICONS = True\n\t\n\t\n\tdef __init__(self, cls=\"osd-menu\", layer = None):\n\t\tOSDWindow.__init__(self, cls, layer)\n\t\tself.daemon = None\n\t\tself.config = None\n\t\tself.feedback = None\n\t\tself.controller = None\n\t\tself.xdisplay = X.Display(hash(GdkX11.x11_get_default_xdisplay()))\t# Magic\n\t\t\n\t\tcursor = os.path.join(get_share_path(), \"images\", 'menu-cursor.svg')\n\t\tself.cursor = Gtk.Image.new_from_file(cursor)\n\t\tself.cursor.set_name(\"osd-menu-cursor\")\n\t\t\n\t\tself.parent = self.create_parent()\n\t\tself.f = Gtk.Fixed()\n\t\tself.f.add(self.parent)\n\t\tself.add(self.f)\n\t\t\n\t\tself._submenu = None\n\t\tself._scon = StickController()\n\t\tself._scon.connect(\"direction\", self.on_stick_direction)\n\t\tself._is_submenu = False\n\t\tself._selected = None\n\t\tself._menuid = None\n\t\tself._use_cursor = False\n\t\tself._eh_ids = []\n\t\tself._control_with = STICK\n\t\tself._control_with_dpad = False\n\t\tself._confirm_with = 'A'\n\t\tself._cancel_with = 'B'\n\t\n\tdef set_is_submenu(self):\n\t\t\"\"\"\n\t\tMarks menu as submenu. This changes behaviour of some methods,\n\t\tespecially disables (un)locking of input stick and buttons.\n\t\t\"\"\"\n\t\tself._is_submenu = True\n\t\n\t\n\tdef create_parent(self):\n\t\tv = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)\n\t\tv.set_name(\"osd-menu\")\n\t\treturn v\n\t\n\t\n\tdef pack_items(self, parent, items):\n\t\tfor item in items:\n\t\t\tparent.pack_start(item.widget, True, True, 0)\n\t\n\t\n\tdef use_daemon(self, d):\n\t\t\"\"\"\n\t\tAllows (re)using already existing DaemonManager instance in same process.\n\t\tuse_config() should be be called before parse_argumets() if this is used.\n\t\t\"\"\"\n\t\tself.daemon = d\n\t\tif not self._is_submenu:\n\t\t\tself._connect_handlers()\n\t\t\tself.on_daemon_connected(self.daemon)\n\t\n\t\n\tdef use_config(self, c):\n\t\t\"\"\"\n\t\tAllows reusing already existin Config instance in same process.\n\t\tHas to be called before parse_argumets()\n\t\t\"\"\"\n\t\tself.config = c\n\t\n\t\n\tdef get_menuid(self):\n\t\t\"\"\"\n\t\tReturns ID of used menu.\n\t\t\"\"\"\n\t\treturn self._menuid\n\t\n\t\n\tdef get_selected_item_id(self):\n\t\t\"\"\"\n\t\tReturns ID of selected item or None if nothing is selected.\n\t\t\"\"\"\n\t\tif self._selected:\n\t\t\treturn self._selected.id\n\t\treturn None\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('--control-with', '-c', type=str,\n\t\t\tmetavar=\"option\", default=DEFAULT, choices=(DEFAULT, LEFT, RIGHT, STICK),\n\t\t\thelp=\"which pad or stick should be used to navigate menu\")\n\t\tself.argparser.add_argument('--confirm-with', type=str,\n\t\t\tmetavar=\"button\", default=DEFAULT,\n\t\t\thelp=\"button used to confirm choice\")\n\t\tself.argparser.add_argument('--cancel-with', type=str,\n\t\t\tmetavar=\"button\", default=DEFAULT,\n\t\t\thelp=\"button used to cancel menu\")\n\t\tself.argparser.add_argument('--confirm-with-release', action='store_true',\n\t\t\thelp=\"confirm choice with button release instead of button press\")\n\t\tself.argparser.add_argument('--cancel-with-release', action='store_true',\n\t\t\thelp=\"cancel menu with button release instead of button press\")\n\t\tself.argparser.add_argument('--use-cursor', '-u', action='store_true',\n\t\t\thelp=\"display and use cursor\")\n\t\tself.argparser.add_argument('--size', type=int,\n\t\t\thelp=\"sets prefered width or height\")\n\t\tself.argparser.add_argument('--feedback-amplitude', type=int,\n\t\t\thelp=\"enables and sets power of feedback effect generated when active menu option is changed\")\n\t\tself.argparser.add_argument('--from-profile', '-p', type=str,\n\t\t\tmetavar=\"profile_file menu_name\",\n\t\t\thelp=\"load menu items from profile file\")\n\t\tself.argparser.add_argument('--from-file', '-f', type=str,\n\t\t\tmetavar=\"filename\",\n\t\t\thelp=\"load menu items from json file\")\n\t\tself.argparser.add_argument('--print-items', action='store_true',\n\t\t\thelp=\"prints menu items to stdout\")\n\t\tself.argparser.add_argument('items', type=str, nargs='*', metavar='id title',\n\t\t\thelp=\"Menu items\")\n\t\n\t\n\t@staticmethod\n\tdef _get_on_screen_position(w):\n\t\ta = w.get_allocation()\n\t\tparent = w.get_parent()\n\t\tif parent:\n\t\t\tif isinstance(parent, Menu) and parent.get_window() is not None:\n\t\t\t\tx, y = parent.get_window().get_position()\n\t\t\telse:\n\t\t\t\tx, y = Menu._get_on_screen_position(parent)\n\t\t\treturn a.x + x, a.y + y\n\t\telse:\n\t\t\treturn a.x, a.y\n\t\n\t\n\tdef parse_menu(self):\n\t\tif self.args.from_profile:\n\t\t\ttry:\n\t\t\t\tself._menuid = self.args.items[0]\n\t\t\t\tself.items = MenuData.from_profile(self.args.from_profile, self._menuid)\n\t\t\texcept IOError:\n\t\t\t\tprint('%s: error: profile file not found' % (sys.argv[0]), file=sys.stderr)\n\t\t\t\treturn False\n\t\t\texcept ValueError:\n\t\t\t\tprint('%s: error: menu not found' % (sys.argv[0]), file=sys.stderr)\n\t\t\t\treturn False\n\t\telif self.args.from_file:\n\t\t\ttry:\n\t\t\t\tself._menuid = self.args.from_file\n\t\t\t\tself.items = MenuData.from_file(self.args.from_file)\n\t\t\texcept:\n\t\t\t\tprint('%s: error: failed to load menu file' % (sys.argv[0]), file=sys.stderr)\n\t\t\t\treturn False\n\t\telse:\n\t\t\ttry:\n\t\t\t\tself.items = MenuData.from_args(self.args.items)\n\t\t\t\tself._menuid = None\n\t\t\texcept ValueError:\n\t\t\t\tprint('%s: error: invalid number of arguments' % (sys.argv[0]), file=sys.stderr)\n\t\t\t\treturn False\n\t\treturn True\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\tif not self.parse_menu():\n\t\t\treturn False\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\t\n\t\t# Parse simpler arguments\n\t\tself._size = self.args.size\n\t\t\n\t\t# Create buttons that are displayed on screen\n\t\titems = self.items.generate(self)\n\t\tself.items = []\n\t\tfor item in items:\n\t\t\titem.widget = self.generate_widget(item)\n\t\t\tif item.widget is not None:\n\t\t\t\tself.items.append(item)\n\t\tself.pack_items(self.parent, self.items)\n\t\tif len(self.items) == 0:\n\t\t\tprint('%s: error: no items in menu' % (sys.argv[0]), file=sys.stderr)\n\t\t\treturn False\n\t\t\n\t\tif self.args.print_items:\n\t\t\tmax_id_len = max(*[ len(x.id) for x in self.items ])\n\t\t\trow_format =\"{:>%s}:\\t{}\" % (max_id_len,)\n\t\t\tfor item in self.items:\n\t\t\t\tprint(row_format.format(item.id, item.label))\n\t\treturn True\n\t\n\t\n\tdef enable_cursor(self):\n\t\tif not self._use_cursor:\n\t\t\tself.f.add(self.cursor)\n\t\t\tself.f.show_all()\n\t\t\tself._use_cursor = True\n\t\n\t\n\tdef generate_widget(self, item):\n\t\t\"\"\" Generates gtk widget for specified menutitem \"\"\"\n\t\tif isinstance(item, Separator) and item.label:\n\t\t\twidget = Gtk.Button.new_with_label(item.label)\n\t\t\twidget.set_relief(Gtk.ReliefStyle.NONE)\n\t\t\twidget.set_name(\"osd-menu-separator\")\n\t\t\treturn widget\n\t\telif isinstance(item, Separator):\n\t\t\twidget = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)\n\t\t\twidget.set_name(\"osd-menu-separator\")\n\t\t\treturn widget\n\t\telse:\n\t\t\twidget = Gtk.Button.new_with_label(item.label)\n\t\t\twidget.set_relief(Gtk.ReliefStyle.NONE)\n\t\t\tif hasattr(widget.get_children()[0], \"set_xalign\"):\n\t\t\t\twidget.get_children()[0].set_xalign(0)\n\t\t\telse:\n\t\t\t\twidget.get_children()[0].set_halign(Gtk.Align.START)\n\t\t\tif isinstance(item, Submenu):\n\t\t\t\titem.callback = self.show_submenu\n\t\t\t\tlabel1 = widget.get_children()[0]\n\t\t\t\tlabel2 = Gtk.Label(_(\">>\"))\n\t\t\t\tlabel2.set_property(\"margin-left\", 30)\n\t\t\t\tbox = Gtk.Box(Gtk.Orientation.HORIZONTAL)\n\t\t\t\twidget.remove(label1)\n\t\t\t\tbox.pack_start(label1, True, True, 1)\n\t\t\t\tbox.pack_start(label2, False, True, 1)\n\t\t\t\twidget.add(box)\n\t\t\t\twidget.set_name(\"osd-menu-item\")\n\t\t\telif item.id is None:\n\t\t\t\twidget.set_name(\"osd-menu-dummy\")\n\t\t\telse:\n\t\t\t\twidget.set_name(\"osd-menu-item\")\n\t\t\t\n\t\t\tif isinstance(item.icon, Gio.FileIcon):\n\t\t\t\ticon_file = item.icon.get_file().get_path()\n\t\t\t\thas_colors = True\n\t\t\telif isinstance(item.icon, Gio.ThemedIcon):\n\t\t\t\ticon = Gtk.IconTheme.get_default().choose_icon(\n\t\t\t\t\titem.icon.get_names(), 64, 0)\n\t\t\t\ticon_file = icon.get_filename() if icon else None\n\t\t\t\thas_colors = True\n\t\t\telse:\n\t\t\t\ticon_file, has_colors = find_icon(item.icon, self.PREFER_BW_ICONS)\n\t\t\t\n\t\t\tif icon_file:\n\t\t\t\ticon = MenuIcon(icon_file, has_colors)\n\t\t\t\tlabel = widget.get_children()[0]\n\t\t\t\tfor c in [] + widget.get_children():\n\t\t\t\t\twidget.remove(c)\n\t\t\t\tbox = Gtk.Box()\n\t\t\t\tbox.pack_start(icon,  False, True, 0)\n\t\t\t\tbox.pack_start(label, True, True, 10)\n\t\t\t\twidget.add(box)\n\t\t\t\t\n\t\t\treturn widget\n\t\n\t\n\tdef select(self, index):\n\t\tif self._selected:\n\t\t\tself._selected.widget.set_name(self._selected.widget.get_name()\n\t\t\t\t.replace(\"-selected\", \"\"))\n\t\tif self.items[index].id:\n\t\t\tif self._selected != self.items[index]:\n\t\t\t\tif self.feedback and self.controller:\n\t\t\t\t\tself.controller.feedback(*self.feedback)\n\t\t\tself._selected = self.items[index]\n\t\t\tself._selected.widget.set_name(\n\t\t\t\t\tself._selected.widget.get_name() + \"-selected\")\n\t\t\tGLib.timeout_add(2, self._check_on_screen_position)\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef _check_on_screen_position(self, quick=False):\n\t\tif self.using_wlroots:\n\t\t\treturn\n\t\tx, y = Menu._get_on_screen_position(self._selected.widget)\n\t\ttry:\n\t\t\tm = self.get_window().get_display().get_monitor_at_window(self.get_window())\n\t\t\tassert m\n\t\t\ty_offset = m.get_geometry().y\n\t\t\tscreen_height = m.get_geometry().height\n\t\texcept:\n\t\t\ty_offset = 0\n\t\t\tscreen_height = self.get_window().get_screen().get_height()\n\t\ty -= y_offset\n\t\tif y < 50:\n\t\t\twx, wy = self.get_window().get_position()\n\t\t\tif quick:\n\t\t\t\twy = 50 - (y - wy)\n\t\t\telse:\n\t\t\t\twy += 5\n\t\t\t\tGLib.timeout_add(2, self._check_on_screen_position)\n\t\t\tself.get_window().move(wx, wy)\n\t\tif y > screen_height - 100:\n\t\t\twx, wy = self.get_window().get_position()\n\t\t\tif quick:\n\t\t\t\twy = screen_height - 100 - (y - wy)\n\t\t\telse:\n\t\t\t\twy -= 5\n\t\t\t\tGLib.timeout_add(2, self._check_on_screen_position)\n\t\t\tself.get_window().move(wx, wy)\n\t\n\t\n\tdef _connect_handlers(self):\n\t\tself._eh_ids += [\n\t\t\t(self.daemon, self.daemon.connect('dead', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('error', self.on_daemon_died)),\n\t\t\t(self.daemon, self.daemon.connect('alive', self.on_daemon_connected)),\n\t\t]\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself._connect_handlers()\n\t\tOSDWindow.run(self)\n\t\n\t\n\tdef show(self, *a):\n\t\tif not self.select(0):\n\t\t\tself.next_item(1)\n\t\tOSDWindow.show(self, *a)\n\t\tGLib.timeout_add(1, self._check_on_screen_position, True)\n\t\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\tself.controller = self.choose_controller(self.daemon)\n\t\tif self.controller is None or not self.controller.is_connected():\n\t\t\t# There is no controller connected to daemon\n\t\t\tself.on_failed_to_lock(\"Controller not connected\")\n\t\t\treturn\n\t\tself.use_controller(self.controller)\n\t\t\n\t\tself._eh_ids += [\n\t\t\t(self.controller, self.controller.connect('event', self.on_event)),\n\t\t\t(self.controller, self.controller.connect('lost', self.on_controller_lost)),\n\t\t]\n\t\tself.lock_inputs()\n\t\n\t\n\tdef use_controller(self, controller):\n\t\tccfg = self.config.get_controller_config(controller.get_id())\n\t\tself._control_with = getattr(self.args, \"control_with\", DEFAULT)\n\t\tself._cancel_with = getattr(self.args, \"cancel_with\", DEFAULT)\n\t\tif self._control_with == DEFAULT: self._control_with = ccfg[\"menu_control\"]\n\t\tif self._cancel_with == DEFAULT: self._cancel_with = ccfg[\"menu_cancel\"]\n\t\t\n\t\tself._confirm_with = getattr(self.args, \"confirm_with\", DEFAULT)\n\t\tif self._confirm_with == DEFAULT:\n\t\t\tself._confirm_with = ccfg[\"menu_confirm\"]\n\t\telif self._confirm_with == SAME:\n\t\t\tif self._control_with == RIGHT:\n\t\t\t\tself._confirm_with = SCButtons.RPADTOUCH.name\n\t\t\telse:\n\t\t\t\tself._confirm_with = SCButtons.LPADTOUCH.name\n\t\t\n\t\tif getattr(self.args, \"use_cursor\", False):\n\t\t\t# As special case, using LEFT pad on controller with\n\t\t\t# actual DPAD should not display cursor\n\t\t\tif self._control_with != LEFT or (controller.get_flags() & ControllerFlags.HAS_DPAD) == 0:\n\t\t\t\tself.enable_cursor()\n\t\t\n\t\tif getattr(self.args, \"feedback_amplitude\", None):\n\t\t\tside = \"LEFT\"\n\t\t\tif self._control_with == \"RIGHT\":\n\t\t\t\tside = \"RIGHT\"\n\t\t\telif self._control_with == \"STICK\":\n\t\t\t\tside = \"BOTH\"\n\t\t\tself.feedback = side, int(self.args.feedback_amplitude)\n\t\n\t\n\tdef lock_inputs(self):\n\t\tdef success(*a):\n\t\t\tlog.error(\"Sucessfully locked input\")\n\t\tlocks = [ self._control_with, self._confirm_with, self._cancel_with ]\n\t\tif self._control_with == \"STICK\":\n\t\t\tif self.controller.get_flags() & ControllerFlags.HAS_DPAD != 0:\n\t\t\t\tself._control_with_dpad = True\n\t\t\t\tlocks += [ \"LEFT\" ]\n\t\tself.controller.lock(success, self.on_failed_to_lock, *locks)\n\t\n\t\n\tdef quit(self, code=-2):\n\t\tif not self._is_submenu:\n\t\t\tif self.get_controller():\n\t\t\t\tself.get_controller().unlock_all()\n\t\t\tfor source, eid in self._eh_ids:\n\t\t\t\tsource.disconnect(eid)\n\t\t\tself._eh_ids = []\n\t\tOSDWindow.quit(self, code)\n\t\n\t\n\tdef next_item(self, direction):\n\t\t\"\"\" Selects next menu item, based on self._direction \"\"\"\n\t\tstart, i = -1, 0\n\t\ttry:\n\t\t\tstart = self.items.index(self._selected)\n\t\t\ti = start + direction\n\t\texcept: pass\n\t\twhile True:\n\t\t\tif i == start:\n\t\t\t\t# Cannot find valid menu item\n\t\t\t\tself.select(start)\n\t\t\t\tbreak\n\t\t\tif i >= len(self.items):\n\t\t\t\ti = 0\n\t\t\t\tGLib.timeout_add(1, self._check_on_screen_position, True)\n\t\t\t\tcontinue\n\t\t\tif i < 0:\n\t\t\t\ti = len(self.items) - 1\n\t\t\t\tGLib.timeout_add(1, self._check_on_screen_position, True)\n\t\t\t\tcontinue\n\t\t\tif self.select(i):\n\t\t\t\t# Not a separator\n\t\t\t\tbreak\n\t\t\ti += direction\n\t\t\tif start < 0: start = 0\n\t\n\t\n\tdef on_submenu_closed(self, *a):\n\t\tself.set_name(\"osd-menu\")\n\t\tif self._submenu.get_exit_code() in (0, -2):\n\t\t\tself._menuid = self._submenu._menuid\n\t\t\tself._selected = self._submenu._selected\n\t\t\tself.quit(self._submenu.get_exit_code())\n\t\tself._submenu = None\n\t\tif self.using_wlroots:\n\t\t\tself.layer_shell.set_layer(self, self.layer_shell.Layer.OVERLAY)\n\t\n\t\n\tdef show_submenu(self, trash, trash2, trash3, menuitem):\n\t\t\"\"\" Called when user chooses menu item pointing to submenu \"\"\"\n\t\tfilename = find_menu(menuitem.filename)\n\t\tif filename:\n\t\t\tlayer = None\n\t\t\tif self.using_wlroots:\n\t\t\t\tif self.layer_shell.is_supported(): \n\t\t\t\t\tlayer = self.layer_shell.Layer.OVERLAY\n\t\t\t\t\tself.layer_shell.set_layer(self, self.layer_shell.Layer.TOP)\n\t\t\tself._submenu = self.__class__(layer = layer)\n\t\t\tsub_pos = list(self.position)\n\t\t\tfor i in (0, 1):\n\t\t\t\tsub_pos[i] = (sub_pos[i] - self.SUBMENU_OFFSET\n\t\t\t\t\t\tif sub_pos[i] < 0 else sub_pos[i] + self.SUBMENU_OFFSET)\n\t\t\t\t\t\n\t\t\tself._submenu.use_config(self.config)\n\t\t\tself._submenu.parse_argumets([\"menu.py\",\n\t\t\t\t\"-x\", str(sub_pos[0]), \"-y\", str(sub_pos[1]),\n\t\t\t \t\"--from-file\", filename,\n\t\t\t\t\"--control-with\", self._control_with,\n\t\t\t\t\"--confirm-with\", self._confirm_with,\n\t\t\t\t\"--cancel-with\", self._cancel_with\n\t\t\t])\n\t\t\tself._submenu.set_is_submenu()\n\t\t\tself._submenu.use_daemon(self.daemon)\n\t\t\tself._submenu.use_controller(self.controller)\n\t\t\tself._submenu.controller = self.controller\n\t\t\tself._submenu.connect('destroy', self.on_submenu_closed)\n\t\t\tself._submenu.show()\n\t\t\tself.set_name(\"osd-menu-inactive\")\n\t\n\t\n\tdef _control_equals_cancel(self, daemon, x, y):\n\t\t\"\"\"\n\t\tCalled by on_event in that very special case when both confirm_with\n\t\tand cancel_with are set to STICK.\n\t\t\n\t\tSeparated because RadialMenu overrides on_event and still\n\t\tneeds to call this.\n\t\t\n\t\tReturns True if menu was canceled.\n\t\t\"\"\"\n\t\tdistance = sqrt(x*x + y*y)\n\t\tif distance < STICK_PAD_MAX / 8:\n\t\t\tself.quit(-1)\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef on_stick_direction(self, trash, x, y):\n\t\tif y != 0:\n\t\t\tself.next_item(y)\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\tif self._submenu:\n\t\t\treturn self._submenu.on_event(daemon, what, data)\n\t\tif what == self._control_with or what == \"LEFT\" and self._control_with_dpad:\n\t\t\tx, y = data\n\t\t\tif self._use_cursor:\n\t\t\t\t# Special case, both confirm_with and cancel_with\n\t\t\t\t# can be set to STICK\n\t\t\t\tif self._cancel_with == STICK and self._control_with == STICK:\n\t\t\t\t\tif self._control_equals_cancel(daemon, x, y):\n\t\t\t\t\t\treturn\n\t\t\t\t\n\t\t\t\tpad_w = self.cursor.get_allocation().width * 0.5\n\t\t\t\tpad_h = self.cursor.get_allocation().height * 0.5\n\t\t\t\tmax_w = self.get_allocation().width - 2 * pad_w\n\t\t\t\tmax_h = self.get_allocation().height - 2 * pad_h\n\t\t\t\t\n\t\t\t\tx, y = circle_to_square(x / (STICK_PAD_MAX * 2.0), y / (STICK_PAD_MAX * 2.0))\n\t\t\t\tx = clamp(pad_w, (pad_w + max_w) * 0.5 + x * max_w, max_w - pad_w)\n\t\t\t\ty = clamp(pad_h, (pad_h + max_h) * 0.5 + y * max_h * -1, max_h - pad_h)\n\t\t\t\tself.f.move(self.cursor, int(x), int(y))\n\t\t\t\t\n\t\t\t\t#for i in self.items:\n\t\t\t\t\t#if point_in_gtkrect(i.widget.get_allocation(), x, y):\n\t\t\t\t\t#\tself.select(self.items.index(i))\n\t\t\telse:\n\t\t\t\tself._scon.set_stick(x, y)\n\t\telif what == self._confirm_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tif self._selected and self._selected.callback:\n\t\t\t\t\tself._selected.callback(self, self.daemon, self.controller, self._selected)\n\t\t\t\telif self._selected:\n\t\t\t\t\tself.quit(0)\n\t\t\t\telse:\n\t\t\t\t\tself.quit(-1)\n\t\telif what == self._cancel_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tself.quit(-1)\n\t\n\t\nclass MenuIcon(Gtk.DrawingArea):\n\t\"\"\" Auti-sized, auto-recolored icon for menus \"\"\"\n\t\n\tdef __init__(self, filename, has_colors = False):\n\t\tGtk.DrawingArea.__init__(self)\n\t\tself.connect('size_allocate', self.on_size_allocate)\n\t\tself.has_colors = has_colors\n\t\tself.set_filename(filename)\n\t\n\t\n\tdef set_filename(self, filename):\n\t\tif filename is None:\n\t\t\tself.pb = None\n\t\telse:\n\t\t\tself.pb = GdkPixbuf.Pixbuf.new_from_file(filename)\n\t\n\t\n\tdef on_size_allocate(self, trash, allocation):\n\t\tif allocation.width < allocation.height:\n\t\t\tself.set_size_request(allocation.height, -1)\n\t\n\t\n\tdef do_draw(self, cr):\n\t\tallocation = self.get_allocation()\n\t\tif allocation.width >= allocation.height:\n\t\t\tcontext = Gtk.Widget.get_style_context(self)\n\t\t\tGtk.render_background(context, cr, 0, 0,\n\t\t\t\t\tallocation.width, allocation.height)\n\t\t\tif self.pb is None:\n\t\t\t\t# No icon set\n\t\t\t\treturn\n\t\t\tscaled = self.pb.scale_simple(\n\t\t\t\tallocation.height, allocation.height,\n\t\t\t\tGdkPixbuf.InterpType.BILINEAR\n\t\t\t)\n\t\t\tsurf = Gdk.cairo_surface_create_from_pixbuf(scaled, 1)\n\t\t\tif self.has_colors:\n\t\t\t\tcr.set_source_surface(surf, 1.0, 1.0)\n\t\t\t\t\t\t#allocation.height, allocation.height)\n\t\t\t\tcr.rectangle(0, 0, allocation.height, allocation.height)\n\t\t\telse:\n\t\t\t\tGdk.cairo_set_source_rgba(cr,\n\t\t\t\t\t\tcontext.get_color(Gtk.StateFlags.NORMAL))\n\t\t\t\tcr.mask_surface(surf, 0, 0)\n\t\t\tcr.fill()\n"
  },
  {
    "path": "scc/osd/menu_generators.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Menu Generators\n\nAuto-generated menus with stuff like list of all available profiles...\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gdk, Gio, GdkX11\nfrom scc.menu_data import MenuGenerator, MenuItem, MENU_GENERATORS\nfrom scc.paths import get_profiles_path, get_default_profiles_path\nfrom scc.tools import find_profile\nfrom scc.lib import xwrappers as X\n\nfrom ctypes import POINTER, cast\nimport os, sys, json, traceback, logging\nlog = logging.getLogger(\"osd.menu_gen\")\n\n\nclass ProfileListMenuGenerator(MenuGenerator):\n\t\"\"\" Generates list of all available profiles \"\"\"\n\tGENERATOR_NAME = \"profiles\"\n\t\n\t@staticmethod\n\tdef callback(menu, daemon, controller, menuitem):\n\t\tcontroller.set_profile(menuitem.filename)\n\t\tmenu.hide()\n\t\tdef on_response(*a):\n\t\t\tmenu.quit(-2)\n\t\tdaemon.request(b\"OSD: \" + menuitem.label.encode(\"utf-8\") + b\"\\n\",\n\t\t\ton_response, on_response)\n\t\n\t\n\tdef describe(self):\n\t\treturn _(\"[ All Profiles ]\")\n\t\n\t\n\tdef generate(self, menuhandler):\n\t\t# TODO: Cannot load directory content asynchronously here and I'm\n\t\t# TODO: not happy about it\n\t\trv, all_profiles = [], {}\n\t\tfor d in (get_default_profiles_path(), get_profiles_path()):\n\t\t\tfor x in os.listdir(d):\n\t\t\t\tif x.endswith(\".sccprofile\") and not x.startswith(\".\"):\n\t\t\t\t\tall_profiles[x] = os.path.join(d, x)\n\t\tfor p in sorted(all_profiles, key=lambda s: s.lower()):\n\t\t\tmenuitem = MenuItem(\"generated\", p[0:-11])\t# strips \".sccprofile\"\n\t\t\tmenuitem.filename = all_profiles[p]\n\t\t\tmenuitem.callback = self.callback\n\t\t\trv.append(menuitem)\n\t\treturn rv\n\n\nclass RecentListMenuGenerator(MenuGenerator):\n\t\"\"\" Generates list of X recently used profiles \"\"\"\n\tGENERATOR_NAME = \"recent\"\n\t\n\tdef __init__(self, rows=5, **b):\n\t\tMenuGenerator.__init__(self)\n\t\tself.rows = rows\n\t\n\t\n\tdef generate(self, menuhandler):\n\t\treturn _(\"[ %s Recent Profiles ]\") % (self.rows,)\n\t\n\t\n\tdef encode(self):\n\t\treturn { \"generator\" : self.GENERATOR_NAME, \"rows\" : self.rows }\n\t\n\t\n\tdef callback(self, menu, daemon, controller, menuitem):\n\t\tcontroller.set_profile(menuitem.filename)\n\t\tmenu.hide()\n\t\tdef on_response(*a):\n\t\t\tmenu.quit(-2)\n\t\tdaemon.request(b\"OSD: \" + menuitem.label.encode(\"utf-8\") + b\"\\n\",\n\t\t\ton_response, on_response)\n\t\n\t\n\tdef generate(self, menuhandler):\n\t\trv = []\n\t\tfor p in menuhandler.config['recent_profiles']:\n\t\t\tfilename = find_profile(p)\n\t\t\tif filename:\n\t\t\t\tmenuitem = MenuItem(\"generated\", p)\n\t\t\t\tmenuitem.filename = filename\n\t\t\t\tmenuitem.callback = ProfileListMenuGenerator.callback\n\t\t\t\trv.append(menuitem)\n\t\t\tif len(rv) >= self.rows:\n\t\t\t\tbreak\n\t\treturn rv\n\n\nclass WindowListMenuGenerator(MenuGenerator):\n\t\"\"\" Generates list of all windows \"\"\"\n\tGENERATOR_NAME = \"windowlist\"\n\tMAX_LENGHT = 50\n\t\n\t#def generate(self, menuhandler):\n\t#\treturn _(\"[ Window Lists ]\")\n\n\t\n\tdef encode(self):\n\t\treturn { \"generator\" : self.GENERATOR_NAME }\n\t\n\t\n\t@staticmethod\n\tdef callback(menu, daemon, controller, menuitem):\n\t\ttry:\n\t\t\txid = int(menuitem.id)\n\t\t\tdisplay = Gdk.Display.get_default()\n\t\t\twindow = GdkX11.X11Window.foreign_new_for_display(display, xid)\n\t\t\twindow.focus(0)\n\t\texcept Exception as e:\n\t\t\tlog.error(\"Failed to activate window\")\n\t\t\tlog.error(traceback.format_exc())\n\t\tmenu.quit(-2)\n\t\n\t\n\tdef generate(self, menuhandler):\n\t\trv = []\n\t\tdpy = X.Display(hash(GdkX11.x11_get_default_xdisplay()))\t# Magic\n\t\troot = X.get_default_root_window(dpy)\n\t\t\n\t\tcount, wlist = X.get_window_prop(dpy, root, b\"_NET_CLIENT_LIST\", 1024)\n\t\tskip_taskbar = X.intern_atom(dpy, b\"_NET_WM_STATE_SKIP_TASKBAR\", True)\n\t\twlist = cast(wlist, POINTER(X.XID))[0:count]\n\t\tfor win in wlist:\n\t\t\tif not skip_taskbar in X.get_wm_state(dpy, win):\n\t\t\t\ttitle = X.get_window_title(dpy, win)[0:self.MAX_LENGHT]\n\t\t\t\tmenuitem = MenuItem(str(win), title)\n\t\t\t\tmenuitem.callback = WindowListMenuGenerator.callback\n\t\t\t\trv.append(menuitem)\n\t\treturn rv\n\n\nclass GameListMenuGenerator(MenuGenerator):\n\t\"\"\"\n\tGenerates list of applications known to XDG menu\n\tand belonging to 'Game' category\n\t\"\"\"\n\tGENERATOR_NAME = \"games\"\n\tMAX_LENGHT = 50\n\t\n\t_games = None\t\t# Static list of know games\n\t\n\t#def generate(self, menuhandler):\n\t#\treturn _(\"[ Games ]\")\n\n\t\n\tdef encode(self):\n\t\treturn { \"generator\" : self.GENERATOR_NAME }\n\t\n\t\n\t@staticmethod\n\tdef callback(menu, daemon, controller, menuitem):\n\t\tmenuitem._desktop_file.launch()\n\t\tmenu.quit(-2)\n\t\n\t\n\tdef generate(self, menuhandler):\n\t\tif GameListMenuGenerator._games is None:\n\t\t\tGameListMenuGenerator._games = []\n\t\t\tid = 0\n\t\t\tfor x in Gio.AppInfo.get_all():\n\t\t\t\tif x.get_categories():\n\t\t\t\t\tif \"Game\" in x.get_categories().split(\";\"):\n\t\t\t\t\t\tmenuitem = MenuItem(str(id), x.get_display_name(),\n\t\t\t\t\t\t\ticon = x.get_icon())\n\t\t\t\t\t\tmenuitem.callback = GameListMenuGenerator.callback\n\t\t\t\t\t\tmenuitem._desktop_file = x\n\t\t\t\t\t\tGameListMenuGenerator._games.append(menuitem)\n\t\treturn GameListMenuGenerator._games\n\n\n# Add classes to MENU_GENERATORS dict\nfor i in [ globals()[x] for x in dir() if hasattr(globals()[x], 'GENERATOR_NAME') ]:\n\tif i.GENERATOR_NAME is not None:\n\t\tMENU_GENERATORS[i.GENERATOR_NAME] = i\n"
  },
  {
    "path": "scc/osd/message.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Message\n\nDisplay message that just sits there\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, GLib\nfrom scc.special_actions import OSDAction\nfrom scc.osd import OSDWindow\n\nimport os, sys, logging\nlog = logging.getLogger(\"osd.message\")\n\n\nclass Message(OSDWindow):\n\t\n\tdef __init__(self):\n\t\tOSDWindow.__init__(self, \"osd-message\")\n\t\t\n\t\tself.timeout = OSDAction.DEFAULT_TIMEOUT\n\t\tself.size = OSDAction.DEFAULT_SIZE\n\t\tself.text = \"text\"\n\t\tself._timeout_id = None\n\t\n\t\n\tdef show(self):\n\t\tself.l = Gtk.Label()\n\t\tself.l.set_name(\"osd-label-%s\" % (self.size, ))\n\t\tself.l.set_label(self.text)\n\t\t\n\t\tself.add(self.l)\n\t\t\n\t\tif self.size < 2:\n\t\t\tself.set_name(\"osd-message-1\")\n\t\tOSDWindow.show(self)\n\t\tif self.timeout > 0:\n\t\t\tself._timeout_id = GLib.timeout_add_seconds(self.timeout, self.quit)\n\t\n\t\n\tdef extend(self):\n\t\tself.set_state(Gtk.StateType.ACTIVE)\n\t\tself.l.set_state(Gtk.StateType.ACTIVE)\n\t\tGLib.timeout_add_seconds(0.5, self.cancel_active_state)\n\t\tif self._timeout_id:\n\t\t\tGLib.source_remove(self._timeout_id)\n\t\t\tself._timeout_id = GLib.timeout_add_seconds(self.timeout, self.quit)\n\t\n\t\n\tdef cancel_active_state(self):\n\t\tself.set_state(Gtk.StateType.NORMAL)\n\t\tself.l.set_state(Gtk.StateType.NORMAL)\n\t\n\t\n\tdef hash(self):\n\t\treturn hash(self.text) + self.timeout - (self.size * 5)\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('-t', type=float, metavar=\"seconds\",\n\t\t\t\tdefault=5, help=\"time before message is hidden (default: 5; 0 means forever)\")\n\t\tself.argparser.add_argument('-s', type=int, metavar=\"size\",\n\t\t\t\tdefault=3, help=\"font size, in range 1 to 3 (default: 3)\")\n\t\tself.argparser.add_argument('text', type=str, help=\"text to display\")\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\treturn False\n\t\tself.text = self.args.text\n\t\tself.timeout = self.args.t\n\t\tself.size = self.args.s\n\t\treturn True\t\n"
  },
  {
    "path": "scc/osd/osk_actions.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - On Screen Keyboard Actions\n\nSpecial Actions that are used to bind functions like closing keyboard or moving\ncursors around.\n\nActions defined here are *not* automatically registered, but OSD Keyboard\nand its binding editor enables them to use with 'OSK.something'\nsyntax.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.lib import Enum\nfrom scc.constants import TRIGGER_HALF, LEFT, RIGHT\nfrom scc.actions import Action, SpecialAction\n\nimport time, logging\nlog = logging.getLogger(\"OSDKeyActs\")\n_ = lambda x : x\n\n\nclass OSKAction(Action, SpecialAction):\n\tdef __init__(self, *a):\n\t\tAction.__init__(self, *a)\n\t\tself.speed = 1.0\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tself.speed = x\n\t\treturn True\n\t\n\t\n\tdef trigger(self, mapper, p, old_p):\n\t\tif p * self.speed >= TRIGGER_HALF and old_p * self.speed < TRIGGER_HALF:\n\t\t\tself.button_press(mapper)\n\t\telif p * self.speed < TRIGGER_HALF and old_p * self.speed >= TRIGGER_HALF:\n\t\t\tself.button_release(mapper)\n\n\nclass CloseOSKAction(OSKAction):\n\tSA = COMMAND = \"close\"\n\t\n\tdef describe(self, context):\n\t\tif context == Action.AC_OSK:\n\t\t\treturn _(\"Hide\")\n\t\treturn _(\"Hide Keyboard\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"OSK.%s()\" % (self.COMMAND,)\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.execute(mapper)\n\t\n\t\n\tdef button_release(self, mapper): pass\n\n\nclass OSKCursorAction(Action, SpecialAction):\n\tSA = COMMAND = \"cursor\"\n\t\n\tdef __init__(self, side):\n\t\tAction.__init__(self, side)\n\t\tif hasattr(side, \"name\"): side = side.name\n\t\tself.speed = (1.0, 1.0)\n\t\tself.side = side\n\t\n\t\n\tdef set_speed(self, x, y, z):\n\t\tself.speed = (x, y)\n\t\treturn True\t\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tself.execute(mapper, x, y)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.side == LEFT:\n\t\t\treturn _(\"Move LEFT Cursor\")\n\t\telif self.side == RIGHT:\n\t\t\treturn _(\"Move RIGHT Cursor\")\n\t\telse:\n\t\t\treturn _(\"Move Cursor\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"OSK.%s(%s)\" % (self.COMMAND, self.side)\n\n\nclass MoveOSKAction(OSKAction):\n\tSA = COMMAND = \"move\"\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tself.execute(mapper, x, y)\n\t\n\t\n\tdef describe(self, context):\n\t\treturn _(\"Move Keyboard\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"OSK.%s()\" % (self.COMMAND,)\n\n\nclass OSKPressAction(OSKAction):\n\tSA = COMMAND = \"press\"\n\t\n\tdef __init__(self, side):\n\t\tOSKAction.__init__(self, side)\n\t\tif hasattr(side, \"name\"): side = side.name\n\t\tself.side = side\n\t\n\t\n\tdef describe(self, context):\n\t\tif context == Action.AC_OSK:\n\t\t\treturn _(\"Press Key\")\n\t\tif self.side == LEFT:\n\t\t\treturn _(\"Press Key Under LEFT Cursor\")\n\t\telse:\n\t\t\treturn _(\"Press Key Under RIGHT Cursor\")\t\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.execute(mapper, True)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tself.execute(mapper, False)\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"OSK.%s(%s)\" % (self.COMMAND, self.side)\n"
  },
  {
    "path": "scc/osd/quick_menu.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Quick OSD Menu\n\nControled by buttons instead of stick. Fast to use, but can display only\nlimited number of items\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, GLib\nfrom scc.menu_data import MenuItem, Submenu\nfrom scc.tools import find_icon, find_menu\nfrom scc.paths import get_share_path\nfrom scc.config import Config\nfrom scc.osd.menu import Menu, MenuIcon\nfrom scc.osd import OSDWindow\n\nimport os, sys, logging\nlog = logging.getLogger(\"osd.quickmenu\")\n\n\nclass QuickMenu(Menu):\n\tBUTTONS = [ \"A\", \"B\", \"X\", \"Y\", \"LB\", \"RB\"]\n\tBUTTON_INDEXES = [ 0, 1, 2, 3, 7, 8]\t# indexes to gui->buttons list\n\t\t\t\t\t\t\t\t\t\t\t# in controller gui config\n\t\n\t\n\tdef __init__(self, cls=\"osd-menu\"):\n\t\tMenu.__init__(self, cls)\n\t\tself._cancel_with = 'START'\n\t\tself._pressed = []\n\t\tself._icons = []\n\t\tself._timer = None\n\t\n\t\n\tdef generate_widget(self, item):\n\t\t\"\"\"\n\t\tIn QuickMenu, everything but submenus and simple\n\t\tmenuitems is ignored.\n\t\t\"\"\"\n\t\tif self._button_index >= len(self.BUTTONS):\n\t\t\treturn None\n\t\tif isinstance(item, (MenuItem, Submenu)):\n\t\t\twidget = Gtk.Button.new_with_label(item.label)\n\t\t\twidget.set_relief(Gtk.ReliefStyle.NONE)\n\t\t\tif hasattr(widget.get_children()[0], \"set_xalign\"):\n\t\t\t\twidget.get_children()[0].set_xalign(0)\n\t\t\telse:\n\t\t\t\twidget.get_children()[0].set_halign(Gtk.Align.START)\n\t\t\tif isinstance(item, Submenu):\n\t\t\t\titem.callback = self.show_submenu\n\t\t\t\tlabel1 = widget.get_children()[0]\n\t\t\t\tlabel2 = Gtk.Label(_(\">>\"))\n\t\t\t\tlabel2.set_property(\"margin-left\", 30)\n\t\t\t\tbox = Gtk.Box(Gtk.Orientation.HORIZONTAL)\n\t\t\t\twidget.remove(label1)\n\t\t\t\tbox.pack_start(label1, True, True, 1)\n\t\t\t\tbox.pack_start(label2, False, True, 1)\n\t\t\t\twidget.add(box)\n\t\t\t\twidget.set_name(\"osd-menu-item\")\n\t\t\telif item.id is None:\n\t\t\t\t# Ignored as well\n\t\t\t\treturn None\n\t\t\telse:\n\t\t\t\twidget.set_name(\"osd-menu-item\")\n\t\t\t\n\t\t\titem.button = self.BUTTONS[self._button_index]\n\t\t\tself._button_index += 1\n\t\t\t\n\t\t\ticon_file, has_colors = find_icon(\"buttons/%s\" % item.button, False)\n\t\t\ticon = MenuIcon(icon_file, has_colors)\n\t\t\tlabel = widget.get_children()[0]\n\t\t\tfor c in [] + widget.get_children():\n\t\t\t\twidget.remove(c)\n\t\t\tself._icons.append(icon)\n\t\t\tbox = Gtk.Box()\n\t\t\tbox.pack_start(icon,  False, True, 0)\n\t\t\tbox.pack_start(label, True, True, 10)\n\t\t\twidget.add(box)\n\t\t\treturn widget\n\t\treturn None\n\t\n\t\n\tdef _add_arguments(self):\n\t\tOSDWindow._add_arguments(self)\n\t\tself.argparser.add_argument('--cancel-with', type=str,\n\t\t\tmetavar=\"button\", default='START',\n\t\t\thelp=\"button used to cancel menu (default: START)\")\n\t\tself.argparser.add_argument('--timeout', type=int,\n\t\t\tdefault=5,\n\t\t\thelp=\"how many seconds before menu is automatically canceled\")\n\t\tself.argparser.add_argument('--cancel-with-release', action='store_true',\n\t\t\thelp=\"cancel menu with button release instead of button press\")\n\t\tself.argparser.add_argument('--from-profile', '-p', type=str,\n\t\t\tmetavar=\"profile_file menu_name\",\n\t\t\thelp=\"load menu items from profile file\")\n\t\tself.argparser.add_argument('--from-file', '-f', type=str,\n\t\t\tmetavar=\"filename\",\n\t\t\thelp=\"load menu items from json file\")\n\t\tself.argparser.add_argument('--print-items', action='store_true',\n\t\t\thelp=\"prints menu items to stdout\")\n\t\tself.argparser.add_argument('items', type=str, nargs='*', metavar='id title',\n\t\t\thelp=\"Menu items\")\n\t\n\t\n\tdef _check_on_screen_position(self, quick=False):\n\t\tpass\n\t\n\t\n\tdef lock_inputs(self):\n\t\tdef success(*a):\n\t\t\tlog.error(\"Sucessfully locked input\")\n\t\t\tconfig = self.controller.load_gui_config(os.path.join(\n\t\t\t\t\tget_share_path(), \"images\"))\n\t\t\tif config and config[\"gui\"] and config[\"gui\"][\"buttons\"]:\n\t\t\t\tbuttons = config[\"gui\"][\"buttons\"]\n\t\t\t\ttry:\n\t\t\t\t\tfor i in range(len(self._icons)):\n\t\t\t\t\t\ticon = self._icons[i]\n\t\t\t\t\t\tname = buttons[self.BUTTON_INDEXES[i]]\n\t\t\t\t\t\tfilename, trash = find_icon(\"buttons/%s\" % name)\n\t\t\t\t\t\ticon.set_filename(filename)\n\t\t\t\t\t\ticon.queue_draw()\n\t\t\t\texcept IndexError:\n\t\t\t\t\tpass\n\t\tlocks = [ x for x in self.BUTTONS ] + [ self._cancel_with ]\n\t\tself.controller.lock(success, self.on_failed_to_lock, *locks)\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tif not OSDWindow.parse_argumets(self, argv):\n\t\t\tprint(\"failed to parse args\")\n\t\t\treturn False\n\t\tif not self.parse_menu():\n\t\t\tprint(\"failed to parse menu\")\n\t\t\treturn False\n\t\tif not self.config:\n\t\t\tself.config = Config()\n\t\t\n\t\tself._cancel_with = self.args.cancel_with\n\t\tself._timeout = self.args.timeout\n\t\t\n\t\t# Create buttons that are displayed on screen\n\t\titems = self.items.generate(self)\n\t\tself.items = []\n\t\tself._button_index = 0\n\t\tfor item in items:\n\t\t\titem.widget = self.generate_widget(item)\n\t\t\tif item.widget is not None:\n\t\t\t\tself.items.append(item)\n\t\tself.pack_items(self.parent, self.items)\n\t\tif len(self.items) == 0:\n\t\t\tprint('%s: error: no items in menu' % (sys.argv[0]), file=sys.stderr)\n\t\t\treturn False\n\t\t\n\t\treturn True\n\t\n\t\n\tdef next_item(self, direction):\n\t\tpass\n\t\n\t\n\tdef select(self, index):\n\t\tpass\n\t\n\t\n\tdef show_submenu(self, trash, trash2, trash3, menuitem):\n\t\t\"\"\" Called when user chooses menu item pointing to submenu \"\"\"\n\t\tfilename = find_menu(menuitem.filename)\n\t\tif filename:\n\t\t\tself._submenu = QuickMenu()\n\t\t\tsub_pos = list(self.position)\n\t\t\tfor i in (0, 1):\n\t\t\t\tsub_pos[i] = (sub_pos[i] - self.SUBMENU_OFFSET\n\t\t\t\t\t\tif sub_pos[i] < 0 else sub_pos[i] + self.SUBMENU_OFFSET)\n\t\t\t\t\t\n\t\t\tself._submenu.use_config(self.config)\n\t\t\tself._submenu.parse_argumets([\"menu.py\",\n\t\t\t\t\"-x\", str(sub_pos[0]), \"-y\", str(sub_pos[1]),\n\t\t\t\t\"--timeout\", str(self._timeout),\n\t\t\t\t\"--from-file\", filename\n\t\t\t])\n\t\t\tself._submenu.set_is_submenu()\n\t\t\tself._submenu.use_daemon(self.daemon)\n\t\t\tself._submenu.connect('destroy', self.on_submenu_closed)\n\t\t\tself._submenu.controller = self.controller\n\t\t\tself._submenu.show()\n\t\t\tself.cancel_timer()\n\t\n\t\n\tdef on_submenu_closed(self, *a):\n\t\t# Quickmenu can have submenus, but everything cancels at once when\n\t\t# last Quickmenu in hierarchy is canceled or timeouts\n\t\tif self._submenu.get_exit_code() in (0, -2):\n\t\t\tself._menuid = self._submenu._menuid\n\t\tself._selected = self._submenu._selected\n\t\tself.quit(self._submenu.get_exit_code())\n\t\n\t\n\tdef pressed(self, what):\n\t\t\"\"\"\n\t\tCalled when button is pressed. If menu with that button assigned\n\t\texists, it is hilighted.\n\t\t\"\"\"\n\t\tfor item in self.items:\n\t\t\tif item.button == what:\n\t\t\t\tself._pressed.append(item)\n\t\t\t\titem.widget.set_name(\"osd-menu-item-selected\")\n\t\n\t\n\tdef released(self, what):\n\t\t\"\"\"\n\t\tCalled when button is pressed. If menu with that button assigned\n\t\texists, it is hilighted.\n\t\t\"\"\"\n\t\tlast = None\n\t\tfor item in self.items:\n\t\t\tif item.button == what:\n\t\t\t\twhile item in self._pressed:\n\t\t\t\t\tself._pressed.remove(item)\n\t\t\t\titem.widget.set_name(\"osd-menu-item\")\n\t\t\t\tlast = item\n\t\t\n\t\tif len(self._pressed) == 0 and last is not None:\n\t\t\tif last.callback:\n\t\t\t\tlast.callback(self, self.daemon, self.controller, last)\n\t\t\telse:\n\t\t\t\tself._selected = last\n\t\t\t\tself.quit(0)\n\t\n\t\n\tdef on_timeout(self, *a):\n\t\tself.quit(-1)\n\t\n\t\n\tdef show(self, *a):\n\t\tMenu.show(self, *a)\n\t\tself.restart_timer()\n\t\n\t\n\tdef restart_timer(self):\n\t\tself.cancel_timer()\n\t\tself._timer = GLib.timeout_add_seconds(self._timeout, self.on_timeout)\n\t\n\t\n\tdef cancel_timer(self):\n\t\tif self._timer:\n\t\t\tGLib.source_remove(self._timer)\n\t\t\tself._timer = None\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\tif self._submenu:\n\t\t\treturn self._submenu.on_event(daemon, what, data)\n\t\telif what == self._cancel_with:\n\t\t\tif data[0] == 0:\t# Button released\n\t\t\t\tself.quit(-1)\n\t\telif what in self.BUTTONS:\n\t\t\tself.restart_timer()\n\t\t\tif data[0] == 1:\t# Button pressed\n\t\t\t\tself.pressed(what)\n\t\t\telse:\t\t\t\t# Released\n\t\t\t\tself.released(what)\n\n\nif __name__ == \"__main__\":\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tm = QuickMenu()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tif m.get_exit_code() == 0:\n\t\tprint(m.get_selected_item_id())\n\tsys.exit(m.get_exit_code())\n"
  },
  {
    "path": "scc/osd/radial_menu.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - OSD Menu\n\nDisplay menu that user can navigate through\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nfrom gi.repository import Gtk, Gdk, GLib, GdkX11\nfrom scc.constants import LEFT, RIGHT, STICK, STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.menu_data import MenuData, Separator, Submenu\nfrom scc.gui.svg_widget import SVGWidget, SVGEditor\nfrom scc.osd.menu import Menu, MenuIcon\nfrom scc.osd import OSDWindow\nfrom scc.tools import degdiff, find_icon\nfrom scc.paths import get_share_path\nfrom scc.lib import xwrappers as X\nfrom scc.config import Config\nfrom math import pi as PI, atan2, sin, cos\n\nimport os, sys, json, logging\nlog = logging.getLogger(\"osd.menu\")\n\n\nclass RadialMenu(Menu):\n\tRECOLOR_BACKGROUNDS = ( \"background\", \"menuitem_hilight_border\", \"text\" )\n\tRECOLOR_STROKES = ( \"border\", \"menuitem_border\" )\n\tMIN_DISTANCE = 3000\t\t# Minimal cursor distance from center (in px^2)\n\tICON_SIZE = 96\n\t\n\tdef __init__(self,):\n\t\tMenu.__init__(self, \"osd-radial-menu\")\n\t\tself.angle = 0\n\t\tself.rotation = 0\n\t\tself.scale = 1.0\n\t\tself.items_with_icon = []\n\t\n\t\n\tdef create_parent(self):\n\t\tbackground = os.path.join(get_share_path(), \"images\", 'radial-menu.svg')\n\t\tself.b = SVGWidget(background)\n\t\tself.b.connect('size-allocate', self.on_size_allocate)\n\t\tself.recolor()\n\t\treturn self.b\n\t\n\t\n\tdef recolor(self):\n\t\tconfig = Config()\n\t\tsource_colors = {}\n\t\ttry:\n\t\t\t# Try to read json file and bail out if it fails\n\t\t\tdesc = os.path.join(get_share_path(), \"images\", 'radial-menu.svg.json')\n\t\t\tsource_colors = json.loads(open(desc, \"r\").read())['colors']\n\t\texcept Exception as e:\n\t\t\tlog.warning(\"Failed to load keyboard description\")\n\t\t\tlog.warning(e)\n\t\t\treturn\n\t\teditor = self.b.edit()\n\t\t\n\t\tfor k in RadialMenu.RECOLOR_BACKGROUNDS:\n\t\t\tif k in config['osd_colors'] and k in source_colors:\n\t\t\t\teditor.recolor_background(source_colors[k], config['osd_colors'][k])\n\t\teditor.recolor_background(source_colors[\"background\"], config['osd_colors'][\"background\"])\n\t\t\n\t\tfor k in RadialMenu.RECOLOR_STROKES:\n\t\t\tif k in config['osd_colors'] and k in source_colors:\n\t\t\t\tprint( \"REC\", source_colors[k], config['osd_colors'][k])\n\t\t\t\teditor.recolor_strokes(source_colors[k], config['osd_colors'][k])\n\t\t\n\t\teditor.commit()\n\t\n\t\n\tdef on_size_allocate(self, trash, allocation):\n\t\t\"\"\" (Re)centers all icons when menu is displayed or size is changed \"\"\"\n\t\tcx = allocation.width * self.scale * 0.5\n\t\tcy = allocation.height * self.scale * 0.5\n\t\tradius = min(cx, cy) * 2 / 3\n\t\tfor i in self.items_with_icon:\n\t\t\tangle, icon = float(i.a) * PI / 180.0, i.icon_widget\n\t\t\tx, y = cx + sin(angle) * radius, cy - cos(angle) * radius\n\t\t\tx = x - (self.ICON_SIZE * self.scale * 0.5)\n\t\t\ty = y - (self.ICON_SIZE * self.scale * 0.5)\n\t\t\ti.icon_widget.get_parent().move(i.icon_widget, x, y)\n\t\n\t\n\tdef get_window_size(self):\n\t\tw, h = Menu.get_window_size(self)\n\t\tif self.scale != 1.0:\n\t\t\tw = int(w * self.scale)\n\t\t\th = int(h * self.scale)\n\t\treturn w, h\n\t\n\t\n\tdef _add_arguments(self):\n\t\tMenu._add_arguments(self)\n\t\tself.argparser.add_argument('--rotation', type=float, default=0,\n\t\t\thelp=\"rotates input by angle (default: 0)\")\n\t\n\t\n\tdef parse_argumets(self, argv):\n\t\tself.editor = self.b.edit()\n\t\trv = Menu.parse_argumets(self, argv)\n\t\tself.rotation = self.args.rotation\n\t\tif rv:\n\t\t\tself.enable_cursor()\n\t\treturn rv\n\t\n\t\n\tdef generate_widget(self, item):\n\t\tif isinstance(item, (Separator, Submenu)) or item.id is None:\n\t\t\t# Labels and separators, radial menu can't show these\n\t\t\treturn None\n\t\te = self.editor.clone_element(\"menuitem_template\")\n\t\tSVGEditor.set_text(e, item.label)\n\t\te.attrib['id'] = \"menuitem_\" + item.id\n\t\treturn e\n\t\n\t\n\tdef pack_items(self, trash, items):\n\t\tif self._size > 0 and self._size < 100:\n\t\t\tself.scale = self._size / 100.0\n\t\t\troot = SVGEditor.get_element(self.editor, \"root\")\n\t\t\tSVGEditor.scale(root, self.scale)\n\t\tpb = self.b.get_pixbuf()\n\t\t# Image width is not scaled as everything bellow operates\n\t\t# in 'root' object coordinate space\n\t\timage_width = pb.get_width()\n\t\t\n\t\tindex = 0\n\t\titem_offset = 360.0 / len(self.items)\n\t\ta1 = (-90.0 - item_offset * 0.5) * PI / 180.0\n\t\ta2 = (-90.0 + item_offset * 0.5) * PI / 180.0\n\t\tfor i in self.items_with_icon:\n\t\t\ti.icon_widget.get_parent().remove_child(i.icon_widget)\n\t\tself.items_with_icon = []\n\t\tfor i in items:\n\t\t\t# Set size of each arc\n\t\t\tif SVGEditor.get_element(i.widget, \"arc\") is not None:\n\t\t\t\tl = SVGEditor.get_element(i.widget, \"arc\")\n\t\t\t\tradius = float(l.attrib[\"radius\"])\t# TODO: Find how to get value of 'sodipodi:rx'\n\t\t\t\tl.attrib[\"d\"] = l.attrib[\"d-template\"] % (\n\t\t\t\t\tradius * cos(a1) + image_width / 2,\n\t\t\t\t\tradius * sin(a1) + image_width / 2,\n\t\t\t\t\tradius * cos(a2) + image_width / 2,\n\t\t\t\t\tradius * sin(a2) + image_width / 2,\n\t\t\t\t)\n\t\t\t# Rotate arc to correct position\n\t\t\ti.a = (360.0 / float(len(self.items))) * float(index)\n\t\t\tSVGEditor.rotate(i.widget, i.a, image_width * 0.5, image_width * 0.5)\n\t\t\t# Check if there is any icon\n\t\t\ticon_file, has_colors = find_icon(i.icon, False) if hasattr(i, \"icon\") else (None, False)\n\t\t\tif icon_file:\n\t\t\t\t# Icon - hide all text and place MenuIcon widget on top of image\n\t\t\t\tself.editor.remove_element(SVGEditor.get_element(i.widget, \"menuitem_text\"))\n\t\t\t\tself.editor.remove_element(SVGEditor.get_element(i.widget, \"line0\"))\n\t\t\t\tself.editor.remove_element(SVGEditor.get_element(i.widget, \"line2\"))\n\t\t\t\ti.icon_widget = MenuIcon(icon_file, has_colors)\n\t\t\t\ti.icon_widget.set_name(\"osd-radial-menu-icon\")\n\t\t\t\ti.icon_widget.set_size_request(self.ICON_SIZE * self.scale, self.ICON_SIZE * self.scale)\n\t\t\t\tself.b.get_parent().put(i.icon_widget, 200, 200)\n\t\t\t\tself.items_with_icon.append(i)\n\t\t\telse:\n\t\t\t\t# No icon - rotate text in arc to other direction to keep it horisontal\n\t\t\t\tif SVGEditor.get_element(i.widget, \"menuitem_text\") is not None:\n\t\t\t\t\tl = SVGEditor.get_element(i.widget, \"menuitem_text\")\n\t\t\t\t\tl.attrib['id'] = \"text_\" + i.id\n\t\t\t\t\tl.attrib['transform'] = \"%s rotate(%s)\" % (l.attrib['transform'], -i.a)\n\t\t\t\t# Place up to 3 lines of item label\n\t\t\t\tlabel = i.label.split(\"\\n\")\n\t\t\t\tfirst_line = 0\n\t\t\t\tif len(label) == 1:\n\t\t\t\t\tself.editor.remove_element(SVGEditor.get_element(i.widget, \"line0\"))\n\t\t\t\t\tself.editor.remove_element(SVGEditor.get_element(i.widget, \"line2\"))\n\t\t\t\t\tfirst_line = 1\n\t\t\t\telif len(label) == 2:\n\t\t\t\t\tself.editor.remove_element(SVGEditor.get_element(i.widget, \"line0\"))\n\t\t\t\t\tfirst_line = 1\n\t\t\t\tfor line in range(0, len(label)):\n\t\t\t\t\tl = SVGEditor.get_element(i.widget, \"line%s\" % (first_line + line,))\n\t\t\t\t\tif l is None:\n\t\t\t\t\t\tbreak\n\t\t\t\t\tSVGEditor.set_text(l, label[line])\n\t\t\t# Continue with next menu item\n\t\t\ti.index = index\n\t\t\t\n\t\t\tindex += 1\n\t\t\n\t\tself.editor.remove_element(\"menuitem_template\")\n\t\tself.editor.commit()\n\t\tdel self.editor\n\t\n\t\n\tdef show(self):\n\t\tOSDWindow.show(self)\n\t\t\n\t\tfrom ctypes import byref\n\t\t\n\t\tpb = self.b.get_pixbuf()\n\t\twin = X.XID(self.get_window().get_xid())\n\t\t\n\t\twidth = int(pb.get_width() * self.scale * self.get_scale_factor())\n\t\theight = int(pb.get_height() * self.scale * self.get_scale_factor())\n\t\tpixmap = X.create_pixmap(self.xdisplay, win, width, height, 1)\n\t\tself.f.move(self.cursor, int(width / 2), int(height / 2))\n\t\t\n\t\tgc = X.create_gc(self.xdisplay, pixmap, 0, None)\n\t\tX.set_foreground(self.xdisplay, gc, 0)\n\t\tX.fill_rectangle(self.xdisplay, pixmap, gc, 0, 0, width, height)\n\t\tX.set_foreground(self.xdisplay, gc, 1)\n\t\tX.set_background(self.xdisplay, gc, 1)\n\t\t\n\t\tr = int(width * 0.985)\n\t\tx = int((width - r) / 2)\n\n\t\tX.fill_arc(self.xdisplay, pixmap, gc,\n\t\t\tx, x, r, r, 0, 360*64)\n\t\t\n\t\tX.flush_gc(self.xdisplay, gc)\n\t\tX.flush(self.xdisplay)\n\t\t\n\t\tX.shape_combine_mask(self.xdisplay, win, X.SHAPE_BOUNDING, 0, 0, pixmap, X.SHAPE_SET)\n\t\t\n\t\tX.flush(self.xdisplay)\n\t\n\t\n\tdef select(self, i):\n\t\tif type(i) == int:\n\t\t\ti = self.items[i]\n\t\tif self._selected and hasattr(self._selected, \"icon_widget\"):\n\t\t\tif self._selected.icon_widget:\n\t\t\t\tself._selected.icon_widget.set_name(\"osd-radial-menu-icon\")\n\t\tself._selected = i\n\t\tif hasattr(self._selected, \"icon_widget\") and self._selected.icon_widget:\n\t\t\tself._selected.icon_widget.set_name(\"osd-radial-menu-icon-selected\")\n\t\tself.b.hilight({\n\t\t\t\"menuitem_\" + i.id : \"#\" + self.config[\"osd_colors\"][\"menuitem_hilight\"],\n\t\t\t\"text_\" + i.id :  \"#\" + self.config[\"osd_colors\"][\"menuitem_hilight_text\"],\n\t\t})\n\t\n\t\n\tdef on_event(self, daemon, what, data):\n\t\tif self._submenu:\n\t\t\treturn self._submenu.on_event(daemon, what, data)\n\t\tif what == self._control_with:\n\t\t\tx, y = data\n\t\t\t# Special case, both confirm_with and cancel_with can be set to STICK\n\t\t\tif self._cancel_with == STICK and self._control_with == STICK:\n\t\t\t\tif self._control_equals_cancel(daemon, x, y):\n\t\t\t\t\treturn\n\t\t\t\n\t\t\tif self.rotation:\n\t\t\t\trx = x * cos(self.rotation) - y * sin(self.rotation)\n\t\t\t\try = x * sin(self.rotation) + y * cos(self.rotation)\n\t\t\t\tx, y = rx, ry\n\t\t\t\n\t\t\tmax_w = self.get_allocation().width * self.scale - (self.cursor.get_allocation().width * 1.0)\n\t\t\tmax_h = self.get_allocation().height * self.scale - (self.cursor.get_allocation().height * 1.0)\n\t\t\tcx = ((x * 0.75 / (STICK_PAD_MAX * 2.0)) + 0.5) * max_w\n\t\t\tcy = (0.5 - (y * 0.75 / (STICK_PAD_MAX * 2.0))) * max_h\n\t\t\t\n\t\t\tcx -= self.cursor.get_allocation().width *  0.5\n\t\t\tcy -= self.cursor.get_allocation().height *  0.5\n\t\t\tself.f.move(self.cursor, int(cx), int(cy))\n\t\t\t\n\t\t\tif abs(x) + abs(y) > RadialMenu.MIN_DISTANCE:\n\t\t\t\tangle = atan2(x, y) * 180.0 / PI\n\t\t\t\thalf_width = 180.0 / len(self.items)\n\t\t\t\tfor i in self.items:\n\t\t\t\t\tif abs(degdiff(i.a, angle)) < half_width:\n\t\t\t\t\t\tif self._selected != i:\n\t\t\t\t\t\t\tif self.feedback and self.controller:\n\t\t\t\t\t\t\t\tself.controller.feedback(*self.feedback)\n\t\t\t\t\t\t\tself.select(i)\n\t\telse:\n\t\t\treturn Menu.on_event(self, daemon, what, data)\n\n\nif __name__ == \"__main__\":\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tinit_logging()\n\t\n\tm = RadialMenu()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tif m.get_exit_code() == 0:\n\t\tprint(m.get_selected_item_id())\n\tsys.exit(m.get_exit_code())\n\n"
  },
  {
    "path": "scc/osd/slave_mapper.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Slave Mapper\n\nMapper that is hooked to scc-daemon instance through socket instead of\nusing libusb directly. Relies to Observe or Lock message being sent by client.\n\nUsed by on-screen keyboard.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom collections import deque\nfrom scc.constants import SCButtons, LEFT, RIGHT, CPAD, DPAD, TRIGGER_MAX\nfrom scc.constants import STICK, RSTICK\nfrom scc.mapper import Mapper\n\nimport logging, time\nlog = logging.getLogger(\"SlaveMapper\")\n\nclass SlaveMapper(Mapper):\n\tdef __init__(self, profile, scheduler, keyboard=b\"SCController Keyboard\", mouse=None):\n\t\tMapper.__init__(self, profile, scheduler, keyboard, mouse, None)\n\t\tself._feedback_cb = None\n\t\n\tdef set_controller(self, c):\n\t\t\"\"\" Sets controller device, used by some (one so far) actions \"\"\"\n\t\traise TypeError(\"SlaveMapper doesn't connect to controller device\")\n\t\n\t\n\tdef get_controller(self):\n\t\t\"\"\" Returns assigned controller device or None if no controller is set \"\"\"\n\t\traise TypeError(\"SlaveMapper doesn't connect to controller device\")\n\t\n\t\n\tdef set_feedback_callback(self, cb):\n\t\t\"\"\"\n\t\tSets callback called to process haptic feedback effects.\n\t\t\n\t\tIf callback is set, it's called as callback(hapticdata) every time\n\t\twhen feedback would happen normally.\n\t\t\n\t\tCallback is used here instead of signal so this module doesn't\n\t\tdepends on GLib\n\t\t\"\"\"\n\t\tself._feedback_cb = cb\n\t\n\t\n\tdef send_feedback(self, hapticdata):\n\t\t\"\"\"\n\t\tSimply calls self._feedback_cb, if set. See docstring above.\n\t\t\"\"\"\n\t\tif self._feedback_cb:\n\t\t\tself._feedback_cb(hapticdata)\n\t\n\t\n\tdef handle_event(self, daemon, what, data):\n\t\t\"\"\"\n\t\tHandles event sent by scc-daemon.\n\t\tWithout calling this, SlaveMapper basically does nothing.\n\t\t\"\"\"\n\t\tself.old_buttons = self.buttons\n\t\tif what == STICK:\n\t\t\tself.profile.stick.whole(self, data[0], data[1], what)\n\t\telif what == RSTICK:\n\t\t\tself.profile.rstick.whole(self, data[0], data[1], what)\n\t\telif what == SCButtons.LT.name:\n\t\t\tself.profile.triggers[LEFT].trigger(self, *data)\n\t\telif what == SCButtons.RT.name:\n\t\t\tself.profile.triggers[RIGHT].trigger(self, *data)\n\t\telif hasattr(SCButtons, what) or what == \"STICKPRESS\":\n\t\t\tif what == \"STICKPRESS\":\n\t\t\t\tx = SCButtons.STICKPRESS\n\t\t\telse:\n\t\t\t\tx = getattr(SCButtons, what)\n\t\t\tif data[0]:\n\t\t\t\t# Pressed\n\t\t\t\tself.buttons = self.buttons | x\n\t\t\t\tself.profile.buttons[x].button_press(self)\n\t\t\telse:\n\t\t\t\tself.buttons = self.buttons & ~x\n\t\t\t\tself.profile.buttons[x].button_release(self)\n\t\t\t\tif what == \"LPADTOUCH\":\n\t\t\t\t\tself.profile.pads[LEFT].whole(self, 0, 0, LEFT)\n\t\t\t\telif what == \"RPADTOUCH\":\n\t\t\t\t\tself.profile.pads[RIGHT].whole(self, 0, 0, RIGHT)\n\t\telif what in (LEFT, RIGHT, CPAD, DPAD):\n\t\t\t# print what, self.profile.pads[what]\n\t\t\tself.profile.pads[what].whole(self, data[0], data[1], what)\n\t\telse:\n\t\t\tprint(\">>>\", what, data)\n\t\tself.generate_events()\n"
  },
  {
    "path": "scc/osd/timermanager.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Timer manager\n\nSimple abstract class for named, cancelable timers\n\"\"\"\n\nfrom __future__ import unicode_literals\nfrom gi.repository import GLib\n\nclass TimerManager(object):\n\tdef __init__(self):\n\t\tself._timers = {}\n\t\n\tdef timer(self, name, delay, callback, *data, **kwdata):\n\t\t\"\"\"\n\t\tRuns callback after specified number of seconds. Uses\n\t\tGLib.timeout_add_seconds with small wrapping to allow named\n\t\ttimers to be canceled by reset() call\n\t\t\"\"\"\n\t\tmethod = GLib.timeout_add_seconds\n\t\tif delay < 1 and delay > 0:\n\t\t\tmethod = GLib.timeout_add\n\t\t\tdelay = delay * 1000.0\n\t\tif name is None:\n\t\t\t# No wrapping is needed, call GLib directly\n\t\t\tmethod(delay, callback, *data, **kwdata)\n\t\telse:\n\t\t\tif name in self._timers:\n\t\t\t\t# Cancel old timer\n\t\t\t\tGLib.source_remove(self._timers[name])\n\t\t\t# Create new one\n\t\t\tself._timers[name] = method(delay, self._callback, name, callback, *data, **kwdata)\n\t\n\tdef timer_active(self, name):\n\t\t\"\"\" Returns True if named timer is active \"\"\"\n\t\treturn (name in self._timers)\n\t\n\tdef cancel_timer(self, name):\n\t\t\"\"\"\n\t\tCancels named timer. Returns True on success, False if there is no such timer.\n\t\t\"\"\"\n\t\tif name in self._timers:\n\t\t\tGLib.source_remove(self._timers[name])\n\t\t\tdel self._timers[name]\n\t\t\treturn True\n\t\treturn False\n\t\n\tdef cancel_all(self):\n\t\t\"\"\" Cancels all active timers \"\"\"\n\t\tfor x in self._timers:\n\t\t\tGLib.source_remove(self._timers[x])\n\t\tself._timers = {}\n\t\n\tdef _callback(self, name, callback, *data, **kwdata):\n\t\t\"\"\"\n\t\tRemoves name from list of active timers and calls real callback.\n\t\t\"\"\"\n\t\tdel self._timers[name]\n\t\tcallback(*data, **kwdata)\n\t\treturn False\n\t\n"
  },
  {
    "path": "scc/parser.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nSC Controller - ActionParser\n\nParses action(s) expressed as string or in dict loaded from json file into\none or more Action instances.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom tokenize import generate_tokens, TokenError\nfrom collections import namedtuple\n\nfrom scc.constants import SCButtons, HapticPos, PARSER_CONSTANTS, STICK\nfrom scc.actions import Action, RangeOP, NoAction, MultiAction\nfrom scc.special_actions import OSDAction\nfrom scc.uinput import Keys, Axes, Rels\nfrom scc.macros import Macro\nfrom scc.tools import nameof\nimport scc.aliases\n\nimport token as TokenType\nimport sys\n\n\nclass ParseError(Exception): pass\n\n\ndef build_action_constants():\n\t\"\"\" Generates dicts for ActionParser.CONSTS \"\"\"\n\trv = {\n\t\t'Keys'\t\t: Keys,\n\t\t'Axes'\t\t: Axes,\n\t\t'Rels'\t\t: Rels,\n\t\t'HapticPos'\t: HapticPos,\n\t\t'None'\t\t: NoAction(),\n\t\t'True'\t\t: True,\n\t\t'False'\t\t: False,\n\t}\n\tfor c in PARSER_CONSTANTS:\n\t\trv[c] = c\n\tfor tpl in (Keys, Axes, Rels, SCButtons, HapticPos):\n\t\tfor x in tpl:\n\t\t\trv[x.name] = x\n\tfor b in (\"A\", \"B\", \"X\", \"Y\", \"START\", \"SELECT\"):\n\t\tname = \"BTN_%s\" % (b,)\n\t\trv[name] = getattr(Keys, name)\n\treturn rv\n\n\nclass ActionParser(object):\n\t\"\"\"\n\tParses action expressed as string into Action instances.\n\t\n\tUsage:\n\t\tap = ActionParser(string)\n\t\taction = ap.parse()\n\t\tif action is None:\n\t\t\terror = ap.get_error()\n\t\t\t# do something with error\n\t\"\"\"\n\tToken = namedtuple('Token', 'type value')\n\t\n\tCONSTS = build_action_constants()\n\t\n\t\n\tdef __init__(self, string=\"\"):\n\t\tself.restart(string)\n\t\n\t\n\tdef from_json_data(self, data, key=None):\n\t\t\"\"\"\n\t\tConverts dict stored in profile file into action.\n\t\t\n\t\tMay throw ParseError.\n\t\t\"\"\"\n\t\tif key is not None:\n\t\t\t# Don't fail if called for non-existent key, return NoAction instead.\n\t\t\t# Using this is sorter than\n\t\t\t# calling 'if button in data[\"buttons\"]: ...' everywhere\n\t\t\tif key in data:\n\t\t\t\treturn self.from_json_data(data[key], None)\n\t\t\telse:\n\t\t\t\treturn NoAction()\n\t\t\n\t\tif \"action\" in data:\n\t\t\ta = self.restart(data[\"action\"]).parse() or NoAction()\n\t\telse:\n\t\t\ta = NoAction()\n\t\tdecoders = set()\n\t\tfor key in data:\n\t\t\tif key in Action.PKEYS:\n\t\t\t\tdecoders.add(Action.PKEYS[key])\n\t\t\n\t\tif decoders:\n\t\t\tfor cls in sorted(decoders, key=lambda a : a.PROFILE_KEY_PRIORITY ):\n\t\t\t\ta = cls.decode(data, a, self, 0)\t# Profile version is not yet used anywhere\n\t\treturn a\n\t\n\t\n\tdef restart(self, s):\n\t\t\"\"\"\n\t\tRestarts parsing with new string\n\t\tReturns self for chaining.\n\t\t\"\"\"\n\t\tif type(s) == bytes:\n\t\t\ts = s.decode(\"utf-8\")\n\t\t\t\n\t\ttry:\n\t\t\tself.tokens = [\n\t\t\t\tActionParser.Token(type, string)\n\t\t\t\tfor type, string, *_\n\t\t\t\tin generate_tokens( iter([s]).__next__ )\n\t\t\t\tif type != TokenType.ENDMARKER\n\t\t\t]\n\t\texcept TokenError:\n\t\t\tself.tokens = None\n\t\tself.index = 0\n\t\treturn self\n\t\n\t\n\tdef _next_token(self):\n\t\trv = self.tokens[self.index]\n\t\tself.index += 1\n\t\treturn rv\n\t\n\t\n\tdef _peek_token(self):\n\t\t\"\"\" As _next_token, but without increasing counter \"\"\"\n\t\treturn self.tokens[self.index]\n\t\n\t\n\tdef _tokens_left(self):\n\t\t\"\"\" Returns True if there are any tokens left \"\"\"\n\t\treturn self.index < len(self.tokens)\n\t\n\t\n\tdef _parse_parameter(self):\n\t\t\"\"\" Parses single parameter \"\"\"\n\t\tt = self._next_token()\n\t\twhile t.type == TokenType.NEWLINE or t.value == \"\\n\":\n\t\t\tif not self._tokens_left():\n\t\t\t\traise ParseError(\"Expected parameter at end of string\")\n\t\t\tt = self._next_token()\n\t\t\n\t\tif t.type == TokenType.NAME:\n\t\t\t# Constant or action used as parameter\n\t\t\tif self._tokens_left() and self._peek_token().type == TokenType.OP and self._peek_token().value == '(':\n\t\t\t\t# Action used as parameter\n\t\t\t\tself.index -= 1 # go step back and reparse as action\n\t\t\t\tparameter = self._parse_action()\n\t\t\telif self._tokens_left() and t.value in Action.ALL and type(Action.ALL[t.value]) == dict and self._peek_token().value == '.':\n\t\t\t\t# SOMETHING.Action used as parameter\n\t\t\t\tself.index -= 1 # go step back and reparse as action\n\t\t\t\tparameter = self._parse_action()\n\t\t\telse:\n\t\t\t\t# Constant\n\t\t\t\tif not t.value in ActionParser.CONSTS:\n\t\t\t\t\traise ParseError(\"Expected parameter, got '%s' which is not defined\" % (t.value,))\n\t\t\t\tparameter = ActionParser.CONSTS[t.value]\n\t\t\t\n\t\t\t# Check for dots\n\t\t\twhile self._tokens_left() and self._peek_token().type == TokenType.OP and self._peek_token().value == '.':\n\t\t\t\tself._next_token()\n\t\t\t\tif not self._tokens_left():\n\t\t\t\t\traise ParseError(\"Expected NAME after '.'\")\n\t\t\t\t\n\t\t\t\tt = self._next_token()\n\t\t\t\tif not hasattr(parameter, t.value):\n\t\t\t\t\traise ParseError(\"%s has no attribute '%s'\" % (parameter, t.value,))\n\t\t\t\tparameter = getattr(parameter, t.value)\n\t\t\t\n\t\t\t# Check for ranges (<, >, <=, >=)\n\t\t\tif self._tokens_left() and self._peek_token().type == TokenType.OP:\n\t\t\t\tif self._peek_token().value in RangeOP.OPS:\n\t\t\t\t\top = self._next_token().value\n\t\t\t\t\t# TODO: Maybe other axes\n\t\t\t\t\tif parameter not in (STICK, SCButtons.LT, SCButtons.RT, SCButtons.X, SCButtons.Y):\n\t\t\t\t\t\traise ParseError(\"'%s' is not trigger nor axis\" % (nameof(parameter), ))\n\t\t\t\t\tif not self._tokens_left():\n\t\t\t\t\t\traise ParseError(\"Excepted number after '%s'\" % (op, ))\n\t\t\t\t\ttry:\n\t\t\t\t\t\tnumber = float(self._next_token().value)\n\t\t\t\t\texcept ValueError:\n\t\t\t\t\t\traise ParseError(\"Excepted number after '%s'\" % (op, ))\n\t\t\t\t\tparameter = RangeOP(parameter, op, number)\n\t\t\t\n\t\t\treturn parameter\n\t\t\n\t\tif t.type == TokenType.OP and t.value == \"-\":\n\t\t\tif not self._tokens_left() or self._peek_token().type != TokenType.NUMBER:\n\t\t\t\traise ParseError(\"Expected number after '-'\")\n\t\t\treturn - self._parse_number()\n\t\t\n\t\tif t.type == TokenType.NUMBER:\n\t\t\tself.index -= 1\n\t\t\treturn self._parse_number()\n\t\t\n\t\tif t.type == TokenType.STRING:\n\t\t\t#return t.value[1:-1].decode('unicode_escape')\n\t\t\treturn t.value[1:-1]\n\t\t\n\t\traise ParseError(\"Expected parameter, got '%s'\" % (t.value,))\n\n\n\tdef _parse_number(self):\n\t\tt = self._next_token()\n\t\tif t.type != TokenType.NUMBER:\n\t\t\traise ParseError(\"Expected number, got '%s'\" % (t.value,))\n\t\tif \".\" in t.value:\n\t\t\treturn float(t.value)\n\t\telif \"e\" in t.value.lower():\n\t\t\treturn float(t.value)\n\t\telif t.value.lower().startswith(\"0x\"):\n\t\t\treturn int(t.value, 16)\n\t\telif t.value.lower().startswith(\"0b\"):\n\t\t\treturn int(t.value, 2)\n\t\telse:\n\t\t\treturn int(t.value)\n\n\n\tdef _parse_parameters(self):\n\t\t\"\"\" Parses parameter list \"\"\"\n\t\t# Check and skip over '('\n\t\tt = self._next_token()\n\t\tif t.type != TokenType.OP or t.value != '(':\n\t\t\traise ParseError(\"Expected '(' of parameter list, got '%s'\" % (t.value,))\n\n\t\tparameters = []\n\t\twhile self._tokens_left():\n\t\t\t# Check for ')' that would end parameter list\n\t\t\tt = self._peek_token()\n\t\t\tif t.type == TokenType.OP and t.value == ')':\n\t\t\t\tself._next_token()\n\t\t\t\treturn parameters\n\n\t\t\t# Parse one parameter\n\t\t\tparameters.append(self._parse_parameter())\n\t\t\t# Check if next token is either ')' or ','\n\t\t\tt = self._peek_token()\n\t\t\twhile t.type == TokenType.NEWLINE or t.value == \"\\n\":\n\t\t\t\tself._next_token()\n\t\t\t\tif not self._tokens_left():\n\t\t\t\t\traise ParseError(\"Expected ',' or end of parameter list after parameter '%s'\" % (parameters[-1],))\n\t\t\t\tt = self._peek_token()\n\t\t\tif t.type == TokenType.OP and t.value == ')':\n\t\t\t\tpass\n\t\t\telif t.type == TokenType.OP and t.value == ',':\n\t\t\t\tself._next_token()\n\t\t\telse:\n\t\t\t\traise ParseError(\"Expected ',' or end of parameter list after parameter '%s'\" % (parameters[-1],))\n\n\n\t\t# Code shouldn't reach here, unless there is not closing ')' in parameter list\n\t\traise ParseError(\"Unmatched parenthesis\")\n\t\n\t\n\tdef _create_action(self, cls, *pars):\n\t\ttry:\n\t\t\treturn cls(*pars)\n\t\texcept ValueError as e:\n\t\t\traise ParseError(str(e))\n\t\texcept TypeError as e:\n\t\t\tprint(e, file=sys.stderr)\n\t\t\traise ParseError(\"Invalid number of parameters for '%s'\" % (cls.COMMAND))\n\t\n\t\n\tdef _parse_action(self, frm=Action.ALL):\n\t\t\"\"\"\n\t\tParses one action, that is one of:\n\t\t - something(params)\n\t\t - something()\n\t\t - something\n\t\t\"\"\"\n\t\t# Check if next token is TokenType.NAME and grab action name from it\n\t\tt = self._next_token()\n\t\tif t.type != TokenType.NAME:\n\t\t\traise ParseError(\"Expected action name, got '%s'\" % (t.value,))\n\t\tif t.value not in frm:\n\t\t\traise ParseError(\"Unknown action '%s'\" % (t.value,))\n\t\taction_name = t.value\n\t\taction_class = frm[action_name]\n\t\t\n\t\t# Check if there are any tokens left - return action without parameters\n\t\t# if not\n\t\tif not self._tokens_left():\n\t\t\treturn self._create_action(action_class)\n\t\t\n\t\t# Check if token after action name is parenthesis and if yes, parse\n\t\t# parameters from it\n\t\tt = self._peek_token()\n\t\tparameters = []\n\t\tif t.type == TokenType.OP and t.value == '.':\n\t\t\t# ACTION dict can have nested dicts; SOMETHING.action\n\t\t\tif type(action_class) == dict:\n\t\t\t\tself._next_token()\n\t\t\t\treturn self._parse_action(action_class)\n\t\t\telse:\n\t\t\t\traise ParseError(\"Unexpected '.' after '%s'\" % (action_name,))\n\t\tif t.type == TokenType.OP and t.value == '(':\n\t\t\tparameters  = self._parse_parameters()\n\t\t\tif not self._tokens_left():\n\t\t\t\treturn self._create_action(action_class, *parameters)\n\t\t\tt = self._peek_token()\n\t\t\n\t\t# ... or, if it is one of ';', 'and' or 'or' and if yes, parse next action\n\t\tif t.type == TokenType.NAME and t.value == 'and':\n\t\t\t# Two (or more) actions joined by 'and'\n\t\t\tself._next_token()\n\t\t\tif not self._tokens_left():\n\t\t\t\traise ParseError(\"Expected action after 'and'\")\n\t\t\taction1 = self._create_action(action_class, *parameters)\n\t\t\taction2 = self._parse_action()\n\t\t\treturn MultiAction(action1, action2)\n\t\t\n\t\tif t.type == TokenType.NEWLINE or t.value == \"\\n\":\n\t\t\t# Newline can be used to join actions instead of 'and'\n\t\t\tself._next_token()\n\t\t\tif not self._tokens_left():\n\t\t\t\t# Newline at end of string is not error\n\t\t\t\treturn self._create_action(action_class, *parameters)\n\t\t\tt = self._peek_token()\n\t\t\tif t.type == TokenType.OP and t.value in (')', ','):\n\t\t\t\t# ')' starts next line\n\t\t\t\treturn self._create_action(action_class, *parameters)\n\t\t\taction1 = self._create_action(action_class, *parameters)\n\t\t\taction2 = self._parse_action()\n\t\t\treturn MultiAction(action1, action2)\n\t\t\n\t\tif t.type == TokenType.OP and t.value == ';':\n\t\t\t# Two (or more) actions joined by ';'\n\t\t\tself._next_token()\n\t\t\twhile self._tokens_left() and self._peek_token().type == TokenType.NEWLINE:\n\t\t\t\tself._next_token()\n\t\t\tif not self._tokens_left():\n\t\t\t\t# Having ';' at end of string is not actually error\n\t\t\t\treturn self._create_action(action_class, *parameters)\n\t\t\taction1 = self._create_action(action_class, *parameters)\n\t\t\taction2 = self._parse_action()\n\t\t\treturn Macro(action1, action2)\n\t\t\n\t\treturn self._create_action(action_class, *parameters)\n\t\n\t\n\tdef parse(self):\n\t\t\"\"\"\n\t\tReturns parsed action.\n\t\tThrows ParseError if action cannot be parsed.\n\t\t\"\"\"\n\t\tif self.tokens == None:\n\t\t\traise ParseError(\"Syntax error\")\n\t\ta = self._parse_action()\n\t\tif self._tokens_left():\n\t\t\traise ParseError(\"Unexpected '%s'\" % (self._next_token().value, ))\n\t\treturn a\n\n\nclass TalkingActionParser(ActionParser):\n\t\"\"\"\n\tActionParser that returns None when parsing fails instead of\n\ttrowing exception and outputs message to stderr\n\t\"\"\"\n\n\tdef restart(self, string):\n\t\tself.string = string\n\t\treturn ActionParser.restart(self, string)\n\n\n\tdef parse(self):\n\t\t\"\"\"\n\t\tReturns parsed action or None if action cannot be parsed.\n\t\t\"\"\"\n\t\ttry:\n\t\t\treturn ActionParser.parse(self)\n\t\texcept ParseError as e:\n\t\t\tprint(\"Warning: Failed to parse '%s':\" % (self.string,), e, file=sys.stderr)\n"
  },
  {
    "path": "scc/paths.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Paths\n\nMethods in this module are used to determine stuff like where user data is stored,\nwhere sccdaemon can be executed from and similar.\n\nThis is gui-only thing, as sccdaemon doesn't really need to load anything what\npython can't handle.\nAll this is needed since I want to have entire thing installable, runnable\nfrom source tarball *and* debugable in working folder.\n\"\"\"\nimport os, sys, __main__\n\n\ndef get_config_path():\n\t\"\"\"\n\tReturns configuration directory.\n\t~/.config/scc under normal conditions.\n\t\"\"\"\n\tconfdir = os.path.expanduser(\"~/.config\")\n\tif \"XDG_CONFIG_HOME\" in os.environ:\n\t\tconfdir = os.environ['XDG_CONFIG_HOME']\n\treturn os.path.join(confdir, \"scc\")\n\n\ndef get_profiles_path():\n\t\"\"\"\n\tReturns directory where profiles are stored.\n\t~/.config/scc/profiles under normal conditions.\n\t\"\"\"\n\treturn os.path.join(get_config_path(), \"profiles\")\n\n\ndef get_default_profiles_path():\n\t\"\"\"\n\tReturns directory where default profiles are stored.\n\tProbably something like /usr/share/scc/default_profiles,\n\tor $SCC_SHARED/default_profiles if program is being started from\n\tscript extracted from source tarball\n\t\"\"\"\n\treturn os.path.join(get_share_path(), \"default_profiles\")\n\n\ndef get_menuicons_path():\n\t\"\"\"\n\tReturns directory where menu icons are stored.\n\t~/.config/scc/menu-icons under normal conditions.\n\t\"\"\"\n\treturn os.path.join(get_config_path(), \"menu-icons\")\n\n\ndef get_default_menuicons_path():\n\t\"\"\"\n\tReturns directory where default menu icons are stored.\n\tProbably something like /usr/share/scc/images/menu-icons,\n\tor $SCC_SHARED/images/menu-icons if program is being started from\n\tscript extracted from source tarball\n\t\"\"\"\n\treturn os.path.join(get_share_path(), \"images/menu-icons\")\n\n\ndef get_button_images_path():\n\t\"\"\"\n\tReturns directory where button images are stored.\n\t/usr/share/scc/images/button-images by default.\n\t\"\"\"\n\treturn os.path.join(get_share_path(), \"images/button-images\")\n\n\ndef get_menus_path():\n\t\"\"\"\n\tReturns directory where profiles are stored.\n\t~/.config/scc/profiles under normal conditions.\n\t\"\"\"\n\treturn os.path.join(get_config_path(), \"menus\")\n\n\ndef get_default_menus_path():\n\t\"\"\"\n\tReturns directory where default profiles are stored.\n\tProbably something like /usr/share/scc/default_profiles,\n\tor ./default_profiles if program is being started from\n\textracted source tarball\n\t\"\"\"\n\treturn os.path.join(get_share_path(), \"default_menus\")\n\n\ndef get_controller_icons_path():\n\t\"\"\"\n\tReturns directory where controller icons are stored.\n\t~/.config/scc/controller-icons under normal conditions.\n\t\n\tThis directory may not exist.\n\t\"\"\"\n\treturn os.path.join(get_config_path(), \"controller-icons\")\n\n\ndef get_default_controller_icons_path():\n\t\"\"\"\n\tReturns directory where controller icons are stored.\n\tProbably something like /usr/share/scc/images/controller-icons,\n\tor ./images/controller-icons if program is being started from\n\textracted source tarball.\n\t\n\tThis directory should always exist.\n\t\"\"\"\n\treturn os.path.join(get_share_path(), \"images\", \"controller-icons\")\n\n\ndef get_share_path():\n\t\"\"\"\n\tReturns directory where shared files are kept.\n\tUsually \"/usr/share/scc\" or $SCC_SHARED if program is being started from\n\tscript extracted from source tarball\n\t\"\"\"\n\tif \"SCC_SHARED\" in os.environ:\n\t\treturn os.environ[\"SCC_SHARED\"]\n\tpaths = (\n\t\t\"/usr/local/share/scc/\",\n\t\tos.path.expanduser(\"~/.local/share/scc\"),\n\t\tos.path.join(sys.prefix, \"share/scc\")\n\t)\n\tfor path in paths:\n\t\tif os.path.exists(path):\n\t\t\treturn path\n\t# No path found, assume default and hope for best\n\treturn \"/usr/share/scc\"\n\n\ndef get_pid_file():\n\t\"\"\"\n\tReturns path to PID file.\n\t~/.config/scc/daemon.pid under normal conditions.\n\t\"\"\"\n\treturn os.path.join(get_config_path(), \"daemon.pid\")\n\n\ndef get_daemon_socket():\n\t\"\"\"\n\tReturns path to socket that can be used to controll sccdaemon.\n\t\n\t~/.config/scc/daemon.socket under normal conditions.\n\t\"\"\"\n\treturn os.path.join(get_config_path(), \"daemon.socket\")\n"
  },
  {
    "path": "scc/poller.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Poller\n\nUses select to pool for file descriptors. Driver classes can use\ndaemon.get_poller().register and .unregister to add file descriptors and\nregister callbacks to be called when data is available in them.\n\nCallback is called as callback(fd, event) where event is one of select.POLL*\n\"\"\"\nimport select, logging\nlog = logging.getLogger(\"Poller\")\n\n\nDO_NOTHING = lambda *a: False\n\nclass Poller(object):\n\tPOLLIN = select.POLLIN\n\tPOLLOUT = select.POLLOUT\n\tPOLLPRI = select.POLLPRI\n\t\n\tdef __init__(self):\n\t\tself._events = {}\n\t\tself._callbacks = {}\n\t\tself._pool_in = ()\n\t\tself._pool_out = ()\n\t\tself._pool_pri = ()\n\t\n\t\n\tdef register(self, fd, events, callback):\n\t\tif fd < 0:\n\t\t\traise ValueError(\"Invalid file descriptor\")\n\t\tself._events[fd] = events\n\t\tself._callbacks[fd] = callback\n\t\tself._generate_lists()\n\t\n\t\n\tdef unregister(self, fd):\n\t\tif fd in self._events: del self._events[fd]\n\t\tif fd in self._callbacks: del self._callbacks[fd]\n\t\tself._generate_lists()\n\t\n\t\n\tdef _generate_lists(self):\n\t\tself._pool_in = [ fd for fd, events in self._events.items() if events & Poller.POLLIN ]\n\t\tself._pool_out = [ fd for fd, events in self._events.items() if events & Poller.POLLOUT ]\n\t\tself._pool_pri = [ fd for fd, events in self._events.items() if events & Poller.POLLPRI ]\n\t\n\t\n\tdef poll(self, timeout=0.01):\n\t\tinn, out, pri = select.select( self._pool_in, self._pool_out, self._pool_pri, timeout )\n\t\t\n\t\tfor fd in inn:\n\t\t\tself._callbacks.get(fd, DO_NOTHING)(fd, Poller.POLLIN)\n\t\tfor fd in out:\n\t\t\tself._callbacks.get(fd, DO_NOTHING)(fd, Poller.POLLOUT)\n\t\tfor fd in pri:\n\t\t\tself._callbacks.get(fd, DO_NOTHING)(fd, Poller.POLLPRI)\n"
  },
  {
    "path": "scc/profile.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Profile\n\nHandles mapping profile stored in json file\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.constants import LEFT, RIGHT, CPAD, DPAD, WHOLE, STICK, RSTICK, GYRO\nfrom scc.constants import SCButtons, HapticPos\nfrom scc.special_actions import MenuAction\nfrom scc.modifiers import HoldModifier\nfrom scc.lib.jsonencoder import JSONEncoder\nfrom scc.parser import TalkingActionParser\nfrom scc.menu_data import MenuData\nfrom scc.actions import NoAction\n\nimport json, logging\nlog = logging.getLogger(\"profile\")\n\n\nclass Profile(object):\n\tVERSION = 1.4\t# Current profile version. When loading profile file\n\t\t\t\t\t# with version lower than this, auto-conversion may happen\n\t\n\tLEFT  = LEFT\n\tRIGHT = RIGHT\n\tLPAD = SCButtons.LPAD.name\n\tRPAD = SCButtons.RPAD.name\n\tCPAD = CPAD\n\tDPAD = DPAD\n\tWHOLE = WHOLE\n\tSTICK = STICK\n\tRSTICK = RSTICK\n\tGYRO  = GYRO\n\tX, Y, Z = \"X\", \"Y\", \"Z\"\n\tSTICK_AXES = { X : \"lpad_x\", Y : \"lpad_y\" }\n\tLPAD_AXES  = STICK_AXES\n\tRPAD_AXES  = { X : \"rpad_x\", Y : \"rpad_y\" }\n\tTRIGGERS   = [ LEFT, RIGHT ]\n\t\n\tdef __init__(self, parser):\n\t\tself.parser = parser\n\t\tself.clear()\n\t\tself.filename = None\n\t\t# UI-only values\n\t\tself.is_template = False\n\t\tself.description = \"\"\n\t\n\t\n\tdef save(self, filename):\n\t\t\"\"\" Saves profile into file. Returns self \"\"\"\n\t\tfileobj = open(filename, \"w\")\n\t\tself.save_fileobj(fileobj)\n\t\tfileobj.close()\n\t\treturn self\n\t\n\t\n\tdef save_fileobj(self, fileobj):\n\t\t\"\"\" Saves profile into file-like object. Returns self \"\"\"\n\t\tdata = {\n\t\t\t\"_\"\t\t\t\t: (self.description if \"\\n\" not in self.description\n\t\t\t\t\t\t\t\telse self.description.strip(\"\\n\").split(\"\\n\")),\n\t\t\t'buttons'\t\t: {},\n\t\t\t'stick'\t\t\t: self.stick,\n\t\t\t'rstick'\t\t: self.rstick,\n\t\t\t'gyro'\t\t\t: self.gyro,\n\t\t\t'trigger_left'\t: self.triggers[Profile.LEFT],\n\t\t\t'trigger_right'\t: self.triggers[Profile.RIGHT],\n\t\t\t\"pad_left\"\t\t: self.pads[Profile.LEFT],\n\t\t\t\"pad_right\"\t\t: self.pads[Profile.RIGHT],\n\t\t\t\"cpad\"\t\t\t: self.pads[Profile.CPAD],\n\t\t\t\"dpad\"\t\t\t: self.pads[Profile.DPAD],\n\t\t\t\"menus\"\t\t\t: { id : self.menus[id].encode() for id in self.menus },\n\t\t\t\"is_template\"\t: self.is_template,\n\t\t\t\"version\"\t\t: Profile.VERSION,\n\t\t}\n\t\t\n\t\tfor i in self.buttons:\n\t\t\tif self.buttons[i]:\n\t\t\t\tdata['buttons'][i.name] = self.buttons[i]\n\t\t\n\t\t# Generate & save json\n\t\tjstr = Encoder(sort_keys=True, indent=4).encode(data)\n\t\tfileobj.write(jstr)\n\t\treturn self\n\t\n\t\n\tdef load(self, filename):\n\t\t\"\"\" Loads profile from file. Returns self \"\"\"\n\t\tfileobj = open(filename, \"r\")\n\t\tself.load_fileobj(fileobj)\n\t\tself.filename = filename\n\t\treturn self\n\t\n\t\n\tdef load_fileobj(self, fileobj):\n\t\t\"\"\"\n\t\tLoads profile from file-like object.\n\t\tFilename attribute is not set, what may cause some trouble if used in GUI.\n\t\t\n\t\tReturns self.\n\t\t\"\"\"\n\t\tdata = json.loads(fileobj.read())\n\t\t# Version\n\t\ttry:\n\t\t\tversion = float(data[\"version\"])\n\t\texcept:\n\t\t\tversion = 0\n\t\t\n\t\t# Settings - Description\n\t\t# (stored in key \"_\", so it's serialized on top of JSON file)\n\t\tif \"_\" not in data:\n\t\t\tself.description = \"\"\n\t\telif type(data[\"_\"]) == list:\n\t\t\tself.description = \"\\n\".join(data[\"_\"])\n\t\telse:\n\t\t\tself.description = data[\"_\"]\n\t\t# Settings - Template\n\t\tself.is_template = bool(data[\"is_template\"]) if \"is_template\" in data else False\n\t\t\n\t\t# Buttons\n\t\tself.buttons = {}\n\t\tfor x in SCButtons:\n\t\t\tself.buttons[x] = self.parser.from_json_data(data[\"buttons\"], x.name)\n\t\t# Pressing stick is interpreted as STICKPRESS button,\n\t\t# formely called just STICK\n\t\tif \"STICK\" in data[\"buttons\"] and \"STICKPRESS\" not in data[\"buttons\"]:\n\t\t\tself.buttons[SCButtons.STICKPRESS] = self.parser.from_json_data(\n\t\t\t\t\tdata[\"buttons\"], \"STICK\")\n\t\t\n\t\t# Stick & gyro\n\t\tself.stick = self.parser.from_json_data(data, \"stick\")\n\t\tself.gyro = self.parser.from_json_data(data, \"gyro\")\n\t\t\n\t\tif \"triggers\" in data:\n\t\t\t# Old format\n\t\t\t# Triggers\n\t\t\tself.triggers = ({\n\t\t\t\tx : self.parser.from_json_data(data[\"triggers\"], x) for x in Profile.TRIGGERS\n\t\t\t})\n\t\t\t\n\t\t\t# Pads\n\t\t\tself.pads = {\n\t\t\t\tProfile.LEFT\t: self.parser.from_json_data(data, \"left_pad\"),\n\t\t\t\tProfile.RIGHT\t: self.parser.from_json_data(data, \"right_pad\"),\n\t\t\t\tProfile.CPAD\t: NoAction(),\n\t\t\t\tProfile.DPAD\t: NoAction(),\n\t\t\t}\n\t\t\t\n\t\t\t# Rigth stick\n\t\t\tself.rstick = NoAction()\n\t\telse:\n\t\t\t# New format\n\t\t\t# Triggers\n\t\t\tself.triggers = {\n\t\t\t\tProfile.LEFT\t: self.parser.from_json_data(data, \"trigger_left\"),\n\t\t\t\tProfile.RIGHT\t: self.parser.from_json_data(data, \"trigger_right\"),\n\t\t\t}\n\t\t\t\n\t\t\t# Pads\n\t\t\tself.pads = {\n\t\t\t\tProfile.LEFT\t: self.parser.from_json_data(data, \"pad_left\"),\n\t\t\t\tProfile.RIGHT\t: self.parser.from_json_data(data, \"pad_right\"),\n\t\t\t\tProfile.CPAD\t: self.parser.from_json_data(data, \"cpad\"),\n\t\t\t\tProfile.DPAD\t: self.parser.from_json_data(data, \"dpad\"),\n\t\t\t}\n\t\t\t\n\t\t\t# Rigth stick\n\t\t\tself.rstick = self.parser.from_json_data(data, \"rstick\")\n\t\t\n\t\t# Menus\n\t\tself.menus = {}\n\t\tif \"menus\" in data:\n\t\t\tfor id in data[\"menus\"]:\n\t\t\t\tfor invalid_char in \".:/\":\n\t\t\t\t\tif invalid_char in id:\n\t\t\t\t\t\traise ValueError(\"Invalid character '%s' in menu id '%s'\" % (invalid_char, id))\n\t\t\t\tself.menus[id] = MenuData.from_json_data(data[\"menus\"][id], self.parser)\n\t\t\n\t\t# Conversion\n\t\tself.original_version = version\t\t# TODO: This is temporary\n\t\tif version < Profile.VERSION:\n\t\t\tself._convert(version)\n\t\t\n\t\treturn self\n\t\n\t\n\tdef clear(self):\n\t\t\"\"\" Clears all actions and adds default menu action on center button \"\"\"\n\t\tself.buttons = { x : NoAction() for x in SCButtons }\n\t\tself.buttons[SCButtons.C] = HoldModifier(\n\t\t\tMenuAction(\"Default.menu\"),\n\t\t\tnormalaction = MenuAction(\"Default.menu\")\n\t\t)\n\t\tself.menus = {}\n\t\tself.stick = NoAction()\n\t\tself.rstick = NoAction()\n\t\tself.is_template = False\n\t\tself.triggers = { Profile.LEFT: NoAction(), Profile.RIGHT: NoAction() }\n\t\tself.pads = {\n\t\t\tProfile.LEFT: NoAction(),\n\t\t\tProfile.RIGHT: NoAction(),\n\t\t\tProfile.CPAD: NoAction(),\n\t\t\tProfile.DPAD: NoAction(),\n\t\t}\n\t\tself.gyro = NoAction()\n\t\n\t\n\tdef get_all_actions(self):\n\t\t\"\"\"\n\t\tReturns generator with every action defined in this profile,\n\t\tincluding actions in menus.\n\t\tRecursively walks into macros, dpads and everything else that can have\n\t\tnested actions, so both parent and all child actions are yielded.\n\t\t\n\t\tMay yield NoAction, but shouldn't yield None.\n\t\t\n\t\tUsed for checks when profile is exported or imported.\n\t\t\"\"\"\n\t\tfor action in self.get_actions():\n\t\t\tfor i in action.get_all_actions():\n\t\t\t\tyield i\n\t\tfor id in self.menus:\n\t\t\tfor i in self.menus[id].get_all_actions():\n\t\t\t\tyield i\n\t\n\t\n\tdef get_actions(self):\n\t\t\"\"\"\n\t\tAs get_all_actions, but returns only root actions, without children,\n\t\tand ignores menus.\n\t\t\"\"\"\n\t\tfor dct in (self.buttons, self.triggers, self.pads):\n\t\t\tfor k in dct:\n\t\t\t\tyield dct[k]\n\t\tfor action in (self.stick, self.rstick, self.gyro):\n\t\t\tyield action\n\t\n\t\n\tdef get_filename(self):\n\t\t\"\"\"\n\t\tReturns filename of last loaded file or None.\n\t\t\"\"\"\n\t\treturn self.filename\n\t\n\t\n\tdef compress(self):\n\t\t\"\"\"\n\t\tCalls compress on every action to throw out some redundant stuff.\n\t\tNote that calling save() after compress() will cause data loss.\n\t\t\"\"\"\n\t\tfor dct in (self.buttons, self.triggers, self.pads):\n\t\t\tfor x in dct:\n\t\t\t\tdct[x] = dct[x].compress()\n\t\tself.rstick = self.rstick.compress()\n\t\tself.stick = self.stick.compress()\n\t\tself.gyro = self.gyro.compress()\n\t\tfor menu in self.menus.values():\n\t\t\tmenu.compress()\n\t\n\t\n\tdef _convert(self, from_version):\n\t\t\"\"\" Performs conversion from older profile version \"\"\"\n\t\tif from_version < 1:\n\t\t\tfrom scc.modifiers import ModeModifier\n\t\t\t# Add 'display Default.menu if center button is held' for old profiles\n\t\t\tc = self.buttons[SCButtons.C]\n\t\t\tif not c:\n\t\t\t\t# Nothing set to C button\n\t\t\t\tself.buttons[SCButtons.C] = HoldModifier(\n\t\t\t\t\tMenuAction(\"Default.menu\"),\n\t\t\t\t\tnormalaction = MenuAction(\"Default.menu\")\n\t\t\t\t)\n\t\t\telif hasattr(c, \"holdaction\") and c.holdaction:\n\t\t\t\t# Already set to something, don't overwrite it\n\t\t\t\tpass\n\t\t\telif c.to_string().startswith(\"OSK.\"):\n\t\t\t\t# Special case, don't touch this either\n\t\t\t\tpass\n\t\t\telse:\n\t\t\t\tself.buttons[SCButtons.C] = HoldModifier(\n\t\t\t\t\tMenuAction(\"Default.menu\"),\n\t\t\t\t\tnormalaction = self.buttons[SCButtons.C]\n\t\t\t\t)\n\t\tif from_version < 1.1:\n\t\t\t# Convert old scrolling wheel to new representation\n\t\t\tfrom scc.modifiers import FeedbackModifier, BallModifier\n\t\t\tfrom scc.actions import MouseAction, XYAction\n\t\t\tfrom scc.uinput import Rels\n\t\t\tiswheelaction = ( lambda x : isinstance(x, MouseAction) and\n\t\t\t\t\tx.parameters[0] in (Rels.REL_HWHEEL, Rels.REL_WHEEL) )\n\t\t\tfor p in (Profile.LEFT, Profile.RIGHT):\n\t\t\t\ta, feedback = self.pads[p], None\n\t\t\t\tif isinstance(a, FeedbackModifier):\n\t\t\t\t\tfeedback = a.haptic.get_position()\n\t\t\t\t\ta = a.action\n\t\t\t\tif isinstance(a, XYAction):\n\t\t\t\t\tif iswheelaction(a.x) or iswheelaction(a.y):\n\t\t\t\t\t\tn = BallModifier(XYAction(a.x, a.y))\n\t\t\t\t\t\tif feedback is not None:\n\t\t\t\t\t\t\tn = FeedbackModifier(feedback, 4096, 16, n)\n\t\t\t\t\t\tself.pads[p] = n\n\t\t\t\t\t\tlog.info(\"Converted %s to %s\", a.to_string(), n.to_string())\n\t\tif from_version < 1.2:\n\t\t\t# Convert old trigger settings that were done with ButtonAction\n\t\t\t# to new TriggerAction\n\t\t\tfrom scc.constants import TRIGGER_HALF, TRIGGER_MAX, TRIGGER_CLICK\n\t\t\tfrom scc.actions import ButtonAction, TriggerAction, MultiAction\n\t\t\tfrom scc.uinput import Keys\n\t\t\tfor p in (Profile.LEFT, Profile.RIGHT):\n\t\t\t\tif isinstance(self.triggers[p], ButtonAction):\n\t\t\t\t\tbuttons, numbers = [], []\n\t\t\t\t\tn = None\n\t\t\t\t\t# There were one or two keys and zero to two numeric\n\t\t\t\t\t# parameters for old button action\n\t\t\t\t\tfor param in self.triggers[p].parameters:\n\t\t\t\t\t\tif param in Keys:\n\t\t\t\t\t\t\tbuttons.append(param)\n\t\t\t\t\t\telif type(param) in (int, float):\n\t\t\t\t\t\t\tnumbers.append(int(param))\n\t\t\t\t\tif len(numbers) == 0:\n\t\t\t\t\t\t# Trigger range was not specified, assume defaults\n\t\t\t\t\t\tnumbers = ( TRIGGER_HALF, TRIGGER_CLICK )\n\t\t\t\t\telif len(numbers) == 1:\n\t\t\t\t\t\t# Only lower range was specified, add default upper range\n\t\t\t\t\t\tnumbers.append(TRIGGER_CLICK)\n\t\t\t\t\tif len(buttons) == 1:\n\t\t\t\t\t\t# If only one button was set, trigger should work like\n\t\t\t\t\t\t# one big button\n\t\t\t\t\t\tn = TriggerAction(numbers[0], ButtonAction(buttons[0]))\n\t\t\t\t\telif len(buttons) == 2:\n\t\t\t\t\t\t# Both buttons were set\n\t\t\t\t\t\tn = MultiAction(\n\t\t\t\t\t\t\tTriggerAction(numbers[0], numbers[1], ButtonAction(buttons[0])),\n\t\t\t\t\t\t\tTriggerAction(numbers[1], TRIGGER_MAX, ButtonAction(buttons[1]))\n\t\t\t\t\t\t)\n\t\t\t\t\t\n\t\t\t\t\tif n:\n\t\t\t\t\t\tlog.info(\"Converted %s to %s\",\n\t\t\t\t\t\t\tself.triggers[p].to_string(), n.to_string())\n\t\t\t\t\t\tself.triggers[p] = n\n\t\tif from_version < 1.3:\n\t\t\t# Action format completly changed in v0.4, but profile foramat is same.\n\t\t\tpass\n\nclass Encoder(JSONEncoder):\n\tdef default(self, obj):\n\t\t#if type(obj) in (list, tuple):\n\t\t#\treturn basestring(\"[\" + \", \".join(self.encode(x) for x in obj) + \" ]\")\n\t\tif hasattr(obj, \"encode\"):\n\t\t\treturn obj.encode()\n\t\treturn JSONEncoder.default(self, obj)\n"
  },
  {
    "path": "scc/sccdaemon.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Daemon class\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport stat\n\nfrom scc.lib import xwrappers as X\nfrom scc.lib import xinput\nfrom scc.lib.daemon import Daemon\nfrom scc.constants import SCButtons, DAEMON_VERSION, HapticPos\nfrom scc.constants import LEFT, RIGHT, STICK, RSTICK, CPAD, DPAD\nfrom scc.tools import find_profile, find_menu, nameof, shsplit, shjoin\nfrom scc.uinput import CannotCreateUInputException\nfrom scc.tools import set_logging_level, find_binary, clamp\nfrom scc.device_monitor import create_device_monitor\nfrom scc.cemuhook_server import CemuhookServer\nfrom scc.custom import load_custom_module\nfrom scc.gestures import GestureDetector\nfrom scc.parser import TalkingActionParser\nfrom scc.controller import HapticData\nfrom scc.scheduler import Scheduler\nfrom scc.menu_data import MenuData\nfrom scc.profile import Profile\nfrom scc.actions import Action\nfrom scc.config import Config\nfrom scc.poller import Poller\nfrom scc.mapper import Mapper\nfrom scc import drivers\n\nfrom socketserver import UnixStreamServer, ThreadingMixIn, StreamRequestHandler\nimport os, sys, pkgutil, signal, time, json, logging\nimport threading, traceback, subprocess, shlex\nlog = logging.getLogger(\"SCCDaemon\")\ntlog = logging.getLogger(\"Socket Thread\")\n\nclass ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): daemon_threads = True\n\n\nclass SCCDaemon(Daemon):\n\t\n\tdef __init__(self, piddile, socket_file):\n\t\tset_logging_level(True, True)\n\t\tDaemon.__init__(self, piddile)\n\t\tConfig()\t\t\t\t\t# Generates ~/.config/scc and default config if needed\n\t\tself.started = False\n\t\tself.exiting = False\n\t\tself.socket_file = socket_file\n\t\tself.poller = Poller()\n\t\tself.dev_monitor = create_device_monitor(self)\n\t\tself.scheduler = Scheduler()\n\t\tself.xdisplay = None\n\t\tself.sserver = None\t\t\t# UnixStreamServer instance\n\t\tself.errors = []\n\t\tself.alone = False\t\t\t# Set by launching script from --alone flag\n\t\tself.custom_py_loaded = False\n\t\tself.osd_daemon = None\n\t\tself.default_profile = None\n\t\tself.autoswitch_daemon = None\n\t\t# TODO: Use osd_ids for all menus\n\t\tself.osd_ids = {}\n\t\tself.controllers = []\n\t\tself.mainloops = [ self.poller.poll, self.scheduler.run ]\n\t\tself.rescan_cbs = [ ]\n\t\tself.on_exit_cbs = []\n\t\tself.subprocs = []\n\t\tself.lock = threading.Lock()\n\t\tself.cemuhook = None\n\t\tself.default_mapper = None\n\t\tself.free_mappers = [ ]\n\t\tself.clients = set()\n\t\tself.cwd = os.getcwd()\n\t\n\t\n\tdef init_drivers(self):\n\t\t\"\"\"\n\t\tSearchs and initializes all controller drivers.\n\t\tSee __init__.py in scc.drivers.\n\t\t\"\"\"\n\t\tlog.debug(\"Initializing drivers...\")\n\t\tcfg = Config()\n\t\tself._to_start = set()  # del-eted later by start_drivers\n\t\tto_init = []\n\t\tfor importer, modname, ispkg in pkgutil.walk_packages(path=drivers.__path__, onerror=lambda x: None):\n\t\t\tif not ispkg and modname != \"driver\":\n\t\t\t\tif modname == \"usb\" or cfg[\"drivers\"].get(modname):\n\t\t\t\t\t# 'usb' driver has to be always active\n\t\t\t\t\tmod = getattr(__import__('scc.drivers.%s' % (modname,)).drivers, modname)\n\t\t\t\t\tif hasattr(mod, \"init\"):\n\t\t\t\t\t\tto_init.append(mod)\n\t\t\t\telse:\n\t\t\t\t\tlog.warn(\"Skipping disabled driver '%s'\", modname)\n\t\t\n\t\tfrom scc.drivers import MOD_INIT_ORDER as order\n\t\tindex_fn = lambda n: order.index(n) if n in order else 1024\n\t\tsort_fn = lambda m: index_fn(m.__name__)\n\t\t\n\t\tfor mod in sorted(to_init, key=sort_fn):\n\t\t\tif getattr(mod, \"init\")(self, cfg):\n\t\t\t\tif hasattr(mod, \"start\"):\n\t\t\t\t\tself._to_start.add(getattr(mod, \"start\"))\n\t\n\t\n\tdef init_default_mapper(self):\n\t\t\"\"\"\n\t\tdefault_mapper is persistent mapper assigned to first Controller instance.\n\t\tEven if all controllers are removed, this mapper stays active. This is\n\t\tneeded so various stuff (mainlg GUI) doesn't need to check if there is\n\t\tany controller connected all the time.\n\t\t\"\"\"\n\t\t# But, despite all above, it's just mapper as every other :)\n\t\treturn self.init_mapper()\n\t\n\t\n\tdef set_default_profile(self, profile_file):\n\t\t\"\"\"\n\t\tSets profile that is used for first available controller\n\t\t\"\"\"\n\t\tself.default_profile = profile_file\n\t\n\t\n\tdef start_drivers(self):\n\t\tfor s in self._to_start:\n\t\t\ts(self)\n\t\tdel self._to_start\n\t\n\t\n\tdef stop_drivers(self):\n\t\tfor s in self.drivers_to_stop:\n\t\t\ts(self)\n\t\n\t\n\tdef get_poller(self):\n\t\t\"\"\" Returns poller that can be used for polling file descriptors \"\"\"\n\t\treturn self.poller\n\t\n\t\n\tdef get_device_monitor(self):\n\t\t\"\"\"\n\t\tReturns device monitor that can be used to listen for device adding and removals\n\t\t\"\"\"\n\t\treturn self.dev_monitor\n\t\n\t\n\tdef get_scheduler(self):\n\t\t\"\"\" Returns scheduler instance \"\"\"\n\t\treturn self.scheduler\n\t\n\t\n\tdef add_mainloop(self, fn):\n\t\t\"\"\"\n\t\tAdds function that is called in every mainloop iteration.\n\t\tCan be called only durring initialization, in driver 'init' method.\n\t\t\"\"\"\n\t\tif fn not in self.mainloops:\n\t\t\tself.mainloops.append(fn)\n\t\n\t\n\tdef remove_mainloop(self, fn):\n\t\t\"\"\"\n\t\tRemoves function added by add_mainloop\n\t\t\"\"\"\n\t\tif fn in self.mainloops:\n\t\t\tself.mainloops.remove(fn)\n\t\n\t\n\tdef add_on_exit(self, fn):\n\t\t\"\"\"\n\t\tAdds function that is called just before daemon is stopped.\n\t\tUsefull for cleanup.\n\t\t\"\"\"\n\t\tif fn not in self.on_exit_cbs:\n\t\t\tself.on_exit_cbs.append(fn)\n\t\n\t\n\tdef add_on_rescan(self, fn):\n\t\t\"\"\"\n\t\tAdds function that is called when `Rescan.` message is recieved.\n\t\t\"\"\"\n\t\tif fn not in self.on_exit_cbs:\n\t\t\tself.rescan_cbs.append(fn)\n\t\n\t\n\tdef _set_profile(self, mapper, filename):\n\t\t# Called from socket server thread\n\t\tp = Profile(TalkingActionParser())\n\t\tp.load(filename).compress()\n\t\tself.profile_file = filename\n\t\t\n\t\tif mapper.profile.gyro and not p.gyro:\n\t\t\t# Turn off gyro sensor that was enabled but is no longer needed\n\t\t\tif mapper.get_controller():\n\t\t\t\tlog.debug(\"Turning gyrosensor OFF\")\n\t\t\t\tmapper.get_controller().set_gyro_enabled(False)\n\t\telif not mapper.profile.gyro and p.gyro:\n\t\t\t# Turn on gyro sensor that was turned off, if profile has gyro action set\n\t\t\tif mapper.get_controller():\n\t\t\t\tlog.debug(\"Turning gyrosensor ON\")\n\t\t\t\tmapper.get_controller().set_gyro_enabled(True)\n\t\t# Cancel everything\n\t\tmapper.cancel_all()\n\t\t# Release all buttons\n\t\tmapper.release_virtual_buttons()\n\t\t# Reset mouse (issue #222)\n\t\tmapper.mouse.reset()\n\t\t\n\t\t# This last line kinda depends on GIL...\n\t\tmapper.profile = p\n\t\t# Re-apply all locks\n\t\tfor c in self.clients:\n\t\t\tc.reaply_locks(self, mapper)\n\t\tif mapper.get_controller():\n\t\t\tself.send_profile_info(mapper.get_controller(), self._send_to_all)\n\t\telse:\n\t\t\tself.send_profile_info(None, self._send_to_all, mapper=mapper)\n\t\n\t\n\tdef _send_to_all(self, message_str):\n\t\t\"\"\"\n\t\tSends message to all connect clients.\n\t\tShould be called while lock is acquired.\n\t\tMessage should be utf-8 encoded str.\n\t\t\"\"\"\n\t\tfor client in self.clients:\n\t\t\ttry:\n\t\t\t\tclient.wfile.write(message_str)\n\t\t\texcept: pass\n\t\n\t\n\tdef on_sa_turnoff(self, mapper, action):\n\t\t\"\"\" Called when 'turnoff' action is used \"\"\"\n\t\tif mapper.get_controller():\n\t\t\tmapper.get_controller().turnoff()\n\t\n\t\n\tdef on_sa_restart(self, *a):\n\t\t\"\"\" Called when 'restart' action is used \"\"\"\n\t\twith self.lock:\n\t\t\tfor c in self.clients:\n\t\t\t\tc.close()\n\t\tos.system(\"%s %s None restart &\" % ( sys.executable, sys.argv[0] ))\n\t\n\t\n\tdef on_sa_led(self, mapper, action):\n\t\t\"\"\" Called when 'led' action is used \"\"\"\n\t\tif mapper.get_controller():\n\t\t\tmapper.get_controller().set_led_level(action.brightness)\n\t\n\t\n\tdef on_sa_shell(self, mapper, action):\n\t\t\"\"\" Called when 'shell' action is used \"\"\"\n\t\treturn subprocess.Popen(action.command, shell=True)\n\t\n\t\n\tdef on_sa_gestures(self, mapper, action, x, y, what):\n\t\t\"\"\" Called when 'gestures' action is used \"\"\"\n\t\t# TODO: Take up_direction from action\n\t\tgd = None\n\t\twith self.lock:\n\t\t\tif action.osd_enabled and self.osd_daemon:\n\t\t\t\t# When OSD is enabled, gesture detection is handled\n\t\t\t\t# by scc-osd-daemon.\n\t\t\t\tself.osd_daemon.gesture_action = action\n\t\t\t\tself._osd('gesture',\n\t\t\t\t\t\"--controller\", mapper.get_controller().get_id(),\n\t\t\t\t \t'--control-with', what)\n\t\t\t\tlog.debug(\"Gesture detection request sent to scc-osd-daemon\")\n\t\t\telse:\n\t\t\t\t# Otherwise it is handled internally\n\t\t\t\tup_direction = 0\n\t\t\t\tgd = self._start_gesture(\n\t\t\t\t\tmapper,\n\t\t\t\t\twhat,\n\t\t\t\t\tup_direction,\n\t\t\t\t\tlambda gesture_string : action.gesture(mapper, gesture_string)\n\t\t\t\t)\n\t\tif gd:\n\t\t\tgd.enable()\n\t\t\tlog.debug(\"Gesture detection started on %s\", what)\n\t\t\tgd.whole(mapper, x, y, what)\n\t\n\tdef on_sa_cemuhook(self, mapper, action, data):\n\t\t\"\"\" Called by 'cemuhook' action \"\"\"\n\t\tif self.cemuhook is None:\n\t\t\ttry:\n\t\t\t\tself.cemuhook = CemuhookServer(self)\n\t\t\texcept Exception as e:\n\t\t\t\tlog.error(\"Failed to initialize CemuHookUDP Motion Provider: %s\", e)\n\t\t\t\treturn\n\t\tself.cemuhook.feed(data)\n\t\n\tdef _osd(self, *data):\n\t\t\"\"\"\n\t\tHas to be called with self.lock held.\n\t\tReturns True on success.\n\t\t\"\"\"\n\t\t# Pre-format data\n\t\tdata = b\"OSD: %s\\n\" % (shjoin(data) ,)\n\t\t\n\t\t# Check if scc-osd-daemon is available\n\t\tif not self.osd_daemon:\n\t\t\tlog.warning(\"Cannot show OSD; there is no scc-osd-daemon registered\")\n\t\t\treturn False\n\t\t# Send request\n\t\ttry:\n\t\t\tself.osd_daemon.wfile.write(data)\n\t\t\tself.osd_daemon.wfile.flush()\n\t\texcept Exception as e:\n\t\t\tlog.error(\"Failed to display OSD: %s\", e)\n\t\t\tself.osd_daemon = None\n\t\t\treturn False\n\t\treturn True\n\t\n\t\n\tdef on_sa_osd(self, mapper, action):\n\t\t\"\"\" Called when 'osd' action is used \"\"\"\n\t\twith self.lock:\n\t\t\tself._osd('message', '-t', str(action.timeout), '-s', str(action.size), action.text)\n\t\n\t\n\tdef on_sa_clearosd(self, mapper, action):\n\t\t\"\"\" Called when 'clearosd' action is used \"\"\"\n\t\twith self.lock:\n\t\t\tself._osd('clear')\n\t\n\t\n\tdef on_sa_area(self, mapper, action, x1, y1, x2, y2):\n\t\t\"\"\" Called when *AreaAction has OSD enabled \"\"\"\n\t\twith self.lock:\n\t\t\tself._osd('area', '-x', str(x1), '-y', str(y1), '--width', str(x2-x1), '--height', str(y2-y1))\n\t\n\t\n\tdef on_sa_clear_osd(self, *a):\n\t\twith self.lock:\n\t\t\tself._osd('clear')\n\t\n\t\n\tdef on_sa_keyboard(self, mapper, action):\n\t\t\"\"\" Called when 'keyboard' action is used \"\"\"\n\t\twith self.lock:\n\t\t\tself._osd('keyboard')\n\t\n\t\n\tdef on_sa_menu(self, mapper, action, *pars):\n\t\t\"\"\" Called when 'menu' action is used \"\"\"\n\t\tp = [ action.MENU_TYPE ]\n\t\tif mapper.get_controller():\n\t\t\tp += [ \"--controller\", mapper.get_controller().get_id() ]\n\t\tif \".\" in action.menu_id:\n\t\t\tpath = find_menu(action.menu_id)\n\t\t\tif not path:\n\t\t\t\tlog.error(\"Cannot show menu: Menu '%s' not found\", action.menu_id)\n\t\t\t\treturn\n\t\t\tp += [ \"--from-file\", path ]\n\t\telse:\n\t\t\tp += [ \"--from-profile\", mapper.profile.get_filename(), action.menu_id ]\n\t\tp += list(pars)\n\t\t\n\t\twith self.lock:\n\t\t\tself._osd(*p)\n\t\n\ton_sa_gridmenu = on_sa_menu\n\t\n\t\n\tdef on_sa_dialog(self, mapper, action, *pars):\n\t\t# Replace actions with id, title pairs\n\t\tdata = []\n\t\tself.osd_ids = {}\n\t\tfor x in pars:\n\t\t\tif isinstance(x, Action):\n\t\t\t\tid = str(hash(x))\n\t\t\t\tself.osd_ids[id] = x.strip()\n\t\t\t\tdata += [ id, x.describe(Action.AC_MENU) ]\n\t\t\telse:\n\t\t\t\tdata.append(x)\n\t\t\n\t\twith self.lock:\n\t\t\tself._osd(\"dialog\", *data)\n\t\n\t\n\tdef on_sa_profile(self, mapper, action):\n\t\t\"\"\" Called when 'profile' action is used \"\"\"\n\t\tname = action.profile\n\t\tif \"/\" in name:\n\t\t\t# Small sanity check\n\t\t\tlog.error(\"Cannot load profile: Profile '%s' not found\", name)\n\t\t\treturn\n\t\tpath = find_profile(name)\n\t\tif path:\n\t\t\twith self.lock:\n\t\t\t\ttry:\n\t\t\t\t\tself._set_profile(mapper, path)\n\t\t\t\t\tlog.info(\"Loaded profile '%s'\", name)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.exception(e)\n\t\t\treturn\n\t\tlog.error(\"Cannot load profile: Profile '%s' not found\", name)\n\t\n\t\n\tdef on_start(self):\n\t\tos.chdir(self.cwd)\n\t\n\t\n\tdef on_controller_status(self, sc, onoff):\n\t\tif onoff:\n\t\t\tlog.debug(\"Controller turned ON\")\n\t\telse:\n\t\t\tlog.debug(\"Controller turned OFF\")\n\t\n\t\n\tdef sigterm(self, *a):\n\t\tself.exiting = True\n\t\tfor fn in self.on_exit_cbs:\n\t\t\tfn(self)\n\t\tfor d in (self.osd_daemon, self.autoswitch_daemon):\n\t\t\tif d: d.wfile.close()\n\t\tself.osd_daemon, self.autoswitch_daemon = None, None\n\t\tfor p in self.subprocs:\n\t\t\tp.kill()\n\t\tself.subprocs = []\n\t\tsys.exit(0)\n\t\n\t\n\tdef connect_x(self):\n\t\t\"\"\" Creates connection to X Server \"\"\"\n\t\tif \"WAYLAND_DISPLAY\" in os.environ:\n\t\t\timport gi\n\t\t\tgi.require_version(\"Gtk\", \"3.0\")\n\t\t\ttry:\n\t\t\t\tgi.require_version(\"GtkLayerShell\", \"0.1\")\n\t\t\t\tfrom gi.repository import GtkLayerShell\n\t\t\t\tif GtkLayerShell.is_supported():\n\t\t\t\t\tlog.warning(\"Wayland with WLRoots detected. Disabling X11 support, some functionality will be unavailable\")\n\t\t\t\t\tself.xdisplay = None\n\t\t\t\t\tself.subprocs.append(Subprocess(\"scc-osd-daemon\", True))\n\t\t\t\t\treturn\n\t\t\texcept (ImportError, ValueError):\n\t\t\t\tpass\n\n\t\t\tlog.warning(\"Wayland detected. Disabling X11 support, some functionality will be unavailable\")\n\t\t\tself.xdisplay = None\n\t\t\treturn\n\n\t\tif \"DISPLAY\" not in os.environ:\n\t\t\tlog.warning(\"DISPLAY env variable not set. Some functionality will be unavailable\")\n\t\t\tself.xdisplay = None\n\t\t\treturn\n\t\t\n\t\tself.xdisplay = X.open_display(os.environ[\"DISPLAY\"].encode(\"utf-8\"))\n\t\tif self.xdisplay:\n\t\t\tlog.debug(\"Connected to XServer %s\", os.environ[\"DISPLAY\"])\n\t\t\t\n\t\t\tfor c in self.controllers:\n\t\t\t\tif c.get_mapper():\n\t\t\t\t\tc.get_mapper().set_xdisplay(self.xdisplay)\n\t\t\tfor m in self.free_mappers:\n\t\t\t\tm.set_xdisplay(self.xdisplay)\n\t\t\tif not self.alone:\n\t\t\t\tself.subprocs.append(Subprocess(\"scc-osd-daemon\", True))\n\t\t\t\tif len(Config()[\"autoswitch\"]):\n\t\t\t\t\t# Start scc-autoswitch-daemon only if there are some switch rules defined\n\t\t\t\t\tself.subprocs.append(Subprocess(\"scc-autoswitch-daemon\", True))\n\t\telse:\n\t\t\tlog.warning(\"Failed to connect to XServer. Some functionality will be unavailable\")\n\t\t\tself.xdisplay = None\n\t\n\t\n\tdef init_mapper(self):\n\t\t\"\"\"\n\t\tSetups new mapper instance.\n\t\t\"\"\"\n\t\ttry:\n\t\t\tmapper = Mapper(Profile(TalkingActionParser()),\n\t\t\t\t\tself.scheduler, poller=self.poller)\n\t\texcept CannotCreateUInputException as e:\n\t\t\t# Most likely UInput is not available\n\t\t\t# Create mapper with all virtual devices set to Dummies.\n\t\t\tlog.exception(e)\n\t\t\tself.add_error(\"uinput\", str(e))\n\t\t\tmapper = Mapper(Profile(TalkingActionParser()),\n\t\t\t\tself.scheduler, keyboard=None, mouse=None, gamepad=False)\n\t\t\n\t\tmapper.set_special_actions_handler(self)\n\t\tmapper.set_xdisplay(self.xdisplay)\n\t\tmapper.schedule(1.0, self.fix_xinput)\n\t\treturn mapper\n\t\n\t\n\tdef fix_xinput(self, mapper):\n\t\tname = mapper.get_gamepad_name()\n\t\tif self.xdisplay and Config()[\"fix_xinput\"] and name:\n\t\t\t# Three conditions: X has to be available, 'fix_xinput' must\n\t\t\t# be enabled in config and controller should not be dummy\n\t\t\t# (should have a name)\n\t\t\ttry:\n\t\t\t\tfor d in xinput.get_devices():\n\t\t\t\t\tif d.get_name() == name:\n\t\t\t\t\t\tif d.is_pointer() and d.is_slave():\n\t\t\t\t\t\t\td.float()\n\t\t\texcept OSError as e:\n\t\t\t\t# Most likely 'xinput' executable not found\n\t\t\t\tlog.warn(\"Failed to deatach gamepad from xinput master: %s\", e)\n\t\n\t\n\tdef load_default_profile(self, mapper=None):\n\t\tmapper = mapper or self.default_mapper\n\t\tif self.default_profile == None:\n\t\t\ttry:\n\t\t\t\tself.default_profile = find_profile(Config()[\"recent_profiles\"][0])\n\t\t\texcept:\n\t\t\t\t# Broken config is not reason to fail here\n\t\t\t\tpass\n\t\ttry:\n\t\t\tmapper.profile.load(self.default_profile).compress()\n\t\texcept Exception as e:\n\t\t\tlog.warning(\"Failed to load profile. Starting with no mappings.\")\n\t\t\tlog.warning(\"Reason: %s\", e)\n\t\n\t\n\tdef add_controller(self, c):\n\t\tif len(self.free_mappers) > 0:\n\t\t\t# Reuse already created mapper, so SCC will not spam system\n\t\t\t# with fake devices\n\t\t\tmapper, self.free_mappers = self.free_mappers[0], self.free_mappers[1:]\n\t\t\tif mapper != self.default_mapper:\n\t\t\t\tlog.debug(\"Reused mapper %s for %s\", mapper, c)\n\t\telse:\n\t\t\t# New controller, but no mapper created\n\t\t\tmapper = self.init_mapper()\n\t\t\tself.load_default_profile(mapper)\n\t\tmapper.set_controller(c)\n\t\tc.set_mapper(mapper)\n\t\tif mapper == self.default_mapper:\n\t\t\tlog.debug(\"Assigned default_mapper to %s\", c)\n\t\tif mapper.profile.gyro:\n\t\t\tlog.debug(\"Turning gyrosensor ON\")\n\t\t\tc.set_gyro_enabled(True)\n\t\t\n\t\tc.apply_config(Config().get_controller_config(c.get_id()))\n\t\tself.controllers.append(c)\n\t\tlog.debug(\"Controller added: %s\", c)\n\t\twith self.lock:\n\t\t\tself.send_controller_list(self._send_to_all)\n\t\t\tself.send_all_profiles(self._send_to_all)\n\t\n\t\n\tdef remove_controller(self, c):\n\t\tmapper = c.mapper\n\t\tif mapper:\n\t\t\tmapper.release_virtual_buttons()\n\t\tc.disconnected()\n\t\t\n\t\twith self.lock:\n\t\t\twhile c in self.controllers:\n\t\t\t\tself.controllers.remove(c)\n\t\t\tlog.debug(\"Controller removed: %s\", c)\n\t\t\t\n\t\t\tif mapper == self.default_mapper and len(self.controllers) > 0:\n\t\t\t\t# Special case, default_mapper should be always\n\t\t\t\t# assigned to something, so if controller with default_mapper\n\t\t\t\t# is disconnected, it's reassigned to next available controller\n\t\t\t\tswap_c = self.controllers[0]\n\t\t\t\tswap_mapper = swap_c.get_mapper()\n\t\t\t\tswap_mapper.set_controller(None)\n\t\t\t\tswap_c.set_mapper(mapper)\n\t\t\t\tmapper.set_controller(swap_c)\n\t\t\t\tself.free_mappers.append(swap_mapper)\n\t\t\t\tlog.debug(\"Reassigned default_mapper to %s\", swap_c)\n\t\t\telse:\n\t\t\t\tc.set_mapper(None)\n\t\t\t\tif mapper:\n\t\t\t\t\tmapper.set_controller(None)\n\t\t\t\t\tself.free_mappers.append(mapper)\n\t\t\tself.send_controller_list(self._send_to_all)\n\t\n\t\n\tdef get_active_ids(self):\n\t\t\"\"\" Returns iterable with IDs of all active controllers \"\"\"\n\t\treturn [ x.get_id() for x in self.controllers ]\n\t\n\t\n\tdef add_error(self, id, error):\n\t\t\"\"\"\n\t\tAdds error (string) to report. Used when USB driver reports that device\n\t\tcannot be accessed or when UInput is not available.\n\t\t\n\t\tEvery error has id that can be later used to remove it from list to\n\t\tindicate that error has been resolved.\n\t\t\"\"\"\n\t\twith self.lock:\n\t\t\tself.errors.append(( id, error ))\n\t\t\tself._send_to_all((\"Error: %s\\n\" % (error,)).encode(\"utf-8\"))\n\t\n\t\n\tdef remove_error(self, id):\n\t\t\"\"\"\n\t\tRemoves error added with 'add_error'. If such error cannot be found,\n\t\tdoes nothing.\n\t\t\n\t\tWhen last error is removed, this method automatically sends \"Ready.\"\n\t\tmessage to indicate that daemon is ready to serve clients.\n\t\t\"\"\"\n\t\twith self.lock:\n\t\t\tself.errors = [ (_id, error) for (_id, error) in self.errors if _id != id ]\n\t\t\tif len(self.errors) == 0:\n\t\t\t\tself._send_to_all(b\"Ready.\\n\")\n\t\n\t\n\tdef send_controller_list(self, method):\n\t\t\"\"\"\n\t\tSends controller count and list of controllers using provided method\n\t\t\"\"\"\n\t\tfor c in self.controllers:\n\t\t\tmethod((\"Controller: %s %s %s %s\\n\" % (\n\t\t\t\tc.get_id(), c.get_type(), c.flags,\n\t\t\t\tc.get_gui_config_file()\n\t\t\t)).encode(\"utf-8\"))\n\t\tmethod((\"Controller Count: %s\\n\" % (len(self.controllers),)).encode(\"utf-8\"))\n\t\n\t\n\tdef send_profile_info(self, controller, method, mapper=None):\n\t\t\"\"\"\n\t\tSends info about current profile using provided method.\n\t\tReturns True if mapper is default_mapper.\n\t\t\"\"\"\n\t\tmapper = mapper if mapper else controller.mapper\n\t\tif controller:\n\t\t\tmethod((\"Controller profile: %s %s\\n\" % (\n\t\t\t\tcontroller.get_id(),\n\t\t\t\tmapper.profile.get_filename()\n\t\t\t)).encode(\"utf-8\"))\n\t\tif mapper == self.default_mapper:\n\t\t\tmethod((\"Current profile: %s\\n\" % (\n\t\t\t\tmapper.profile.get_filename(),\n\t\t\t)).encode(\"utf-8\"))\n\t\t\treturn True\n\t\treturn False\n\t\n\t\n\tdef send_all_profiles(self, method):\n\t\t\"\"\"\n\t\tSends info about all profiles assigned to all\n\t\tcontrollers using provided method.\n\t\t\"\"\"\n\t\t# As special case, at least default_mapper profile has to be sent always\n\t\tdefault_sent = False\n\t\tfor c in self.controllers:\n\t\t\tdefault_sent = self.send_profile_info(c, method) or default_sent\n\t\tif not default_sent:\n\t\t\tself.send_profile_info(None, method, mapper=self.default_mapper)\n\t\n\t\n\tdef run(self):\n\t\tlog.debug(\"Starting SCCDaemon...\")\n\t\tsignal.signal(signal.SIGTERM, self.sigterm)\n\t\tself.init_drivers()\n\t\tself.dev_monitor.start()\n\t\tload_custom_module(log)\n\t\tself.default_mapper = self.init_default_mapper()\n\t\tself.free_mappers.append(self.default_mapper)\n\t\tself.load_default_profile()\n\t\tself.lock.acquire()\n\t\tself.start_listening()\n\t\tself.connect_x()\n\t\tself.lock.release()\n\t\tself.start_drivers()\n\t\tself.dev_monitor.rescan()\n\t\t\n\t\twhile True:\n\t\t\tfor fn in self.mainloops:\n\t\t\t\tfn()\n\t\n\t\n\tdef start_listening(self):\n\t\tif os.path.exists(self.socket_file):\n\t\t\tos.unlink(self.socket_file)\n\t\tinstance = self\n\t\t\n\t\tclass SSHandler(StreamRequestHandler):\n\t\t\tdef handle(self):\n\t\t\t\tinstance._sshandler(self.connection, self.rfile, self.wfile)\n\t\t\n\t\tself.sserver = ThreadingUnixStreamServer(self.socket_file, SSHandler)\n\t\tt = threading.Thread(target=self.sserver.serve_forever)\n\t\tt.daemon = True\n\t\tt.start()\n\t\tos.chmod(self.socket_file, stat.S_IRUSR | stat.S_IWUSR)\n\t\tlog.debug(\"Created control socket %s\", self.socket_file)\n\t\n\t\n\tdef _start_gesture(self, mapper, what, up_angle, callback):\n\t\t\"\"\"\n\t\tStarts gesture detection on specified pad.\n\t\tCalls callback with gesture string when finished.\n\t\t\n\t\tShould be called with lock held.\n\t\t\"\"\"\n\t\tgd = None\n\t\t\n\t\tdef cb(detector, gesture):\n\t\t\t# This callback is expected to be called with lock held\n\t\t\twith self.lock:\n\t\t\t\tself._apply(mapper, what, lambda a : a.original_action)\n\t\t\tlog.debug(\"Gesture detected on %s: %s\", what, gesture)\n\t\t\tcallback(gesture)\n\t\t\n\t\tdef set(action):\n\t\t\t# ObservingAction should be above GestureDetector\n\t\t\tif isinstance(action, ObservingAction):\n\t\t\t\tgd.original_action = action.original_action\n\t\t\t\taction.original_action = gd\n\t\t\t\treturn action\n\t\t\telse:\n\t\t\t\tgd.original_action = action\n\t\t\t\treturn gd\n\t\t\n\t\tgd = GestureDetector(up_angle, cb)\n\t\tself._apply(mapper, what, set)\n\t\treturn gd\t\n\t\n\t\n\tdef _sshandler(self, connection, rfile, wfile):\n\t\twith self.lock:\n\t\t\tclient = Client(connection, self.default_mapper, rfile, wfile)\n\t\t\tself.clients.add(client)\n\t\t\twfile.write(b\"SCCDaemon\\n\")\n\t\t\twfile.write((\"Version: %s\\n\" % (DAEMON_VERSION,)).encode(\"utf-8\"))\n\t\t\twfile.write((\"PID: %s\\n\" % (os.getpid(),)).encode(\"utf-8\"))\n\t\t\tself.send_controller_list(wfile.write)\n\t\t\tself.send_all_profiles(wfile.write)\n\t\t\tif len(self.errors) == 0:\n\t\t\t\twfile.write(b\"Ready.\\n\")\n\t\t\telse:\n\t\t\t\tfor id, error in self.errors:\n\t\t\t\t\twfile.write((\"Error: %s\\n\" % (error,)).encode(\"utf-8\"))\n\t\t\n\t\twhile True:\n\t\t\ttry:\n\t\t\t\tline = rfile.readline()\n\t\t\texcept Exception:\n\t\t\t\t# Connection terminated\n\t\t\t\tbreak\n\t\t\tif len(line) == 0: break\n\t\t\tif len(line.strip(b\"\\t\\n \")) > 0:\n\t\t\t\tself._handle_message(client, line.strip(b\"\\n\"))\n\t\t\n\t\twith self.lock:\n\t\t\tclient.unlock_actions(self)\n\t\t\tif self.osd_daemon == client:\n\t\t\t\tlog.info(\"scc-osd-daemon lost\")\n\t\t\t\tself.osd_daemon = None\n\t\t\tif self.autoswitch_daemon == client:\n\t\t\t\tlog.info(\"scc-autoswitch-daemon lost\")\n\t\t\t\tself.autoswitch_daemon = None\n\t\t\tself.clients.remove(client)\n\t\n\t\n\tdef _handle_message(self, client, message):\n\t\t\"\"\"\n\t\tHandles message recieved from client.\n\t\t\"\"\"\n\t\tif message.startswith(b\"Profile:\"):\n\t\t\twith self.lock:\n\t\t\t\ttry:\n\t\t\t\t\tfilename = message[8:].decode(\"utf-8\").strip(\"\\t \")\n\t\t\t\t\tself._set_profile(client.mapper, filename)\n\t\t\t\t\tlog.info(\"Loaded profile '%s'\", filename)\n\t\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\t\texcept Exception as e:\n\t\t\t\t\texc = traceback.format_exc()\n\t\t\t\t\tlog.exception(e)\n\t\t\t\t\ttb = str(exc).encode(\"utf-8\").decode('unicode_escape').encode(\"latin1\")\n\t\t\t\t\tclient.wfile.write(b\"Fail: \" + tb + b\"\\n\")\n\t\telif message.startswith(b\"OSD:\"):\n\t\t\tif not self.osd_daemon:\n\t\t\t\tclient.wfile.write(b\"Fail: Cannot show OSD; there is no scc-osd-daemon registered\\n\")\n\t\t\telse:\n\t\t\t\ttry:\n\t\t\t\t\ttext = message[5:].decode(\"utf-8\").strip(\"\\t \")\n\t\t\t\t\twith self.lock:\n\t\t\t\t\t\tif not self._osd(\"message\", text):\n\t\t\t\t\t\t\traise Exception()\n\t\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\t\texcept Exception:\n\t\t\t\t\tclient.wfile.write(b\"Fail: cannot display OSD\\n\")\n\t\telif message.startswith(b\"Feedback:\"):\n\t\t\ttry:\n\t\t\t\tposition, amplitude = message[9:].strip().split(b\" \", 2)\n\t\t\t\tdata = HapticData(\n\t\t\t\t\tgetattr(HapticPos, position.strip(\" \\t\\r\")),\n\t\t\t\t\tint(amplitude)\n\t\t\t\t)\n\t\t\t\tif client.mapper.get_controller():\n\t\t\t\t\tclient.mapper.get_controller().feedback(data)\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\texcept Exception as e:\n\t\t\t\tlog.exception(e)\n\t\t\t\tclient.wfile.write(b\"Fail: %s\\n\" % (e,))\n\t\telif message.startswith(b\"Controller.\"):\n\t\t\twith self.lock:\n\t\t\t\tclient.mapper = self.default_mapper\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telif message.startswith(b\"Controller:\"):\n\t\t\twith self.lock:\n\t\t\t\ttry:\n\t\t\t\t\tcontroller_id = message[11:].strip().decode('utf-8')\n\t\t\t\t\tfor c in self.controllers:\n\t\t\t\t\t\tif c.get_id() == controller_id:\n\t\t\t\t\t\t\tclient.mapper = c.get_mapper()\n\t\t\t\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\t\t\t\t\tbreak\n\t\t\t\t\telse:\n\t\t\t\t\t\traise Exception(\"goto fail\")\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tclient.wfile.write(b\"Fail: no such controller\\n\")\n\t\telif message.startswith(b\"State.\"):\n\t\t\tif Config()[\"enable_sniffing\"]:\n\t\t\t\tclient.wfile.write(b\"State: %s\\n\" % (str(client.mapper.state), ))\n\t\t\telse:\n\t\t\t\tlog.warning(\"Refused 'State' request: Sniffing disabled\")\n\t\t\t\tclient.wfile.write(b\"Fail: Sniffing disabled.\\n\")\n\t\telif message.startswith(b\"Led:\"):\n\t\t\ttry:\n\t\t\t\tnumber = int(message[4:])\n\t\t\t\tnumber = clamp(0, number, 100)\n\t\t\texcept Exception as e:\n\t\t\t\tclient.wfile.write(b\"Fail: %s\\n\" % (e,))\n\t\t\t\treturn\n\t\t\tif client.mapper.get_controller():\n\t\t\t\tclient.mapper.get_controller().set_led_level(number)\n\t\telif message.startswith(b\"Observe:\"):\n\t\t\tif Config()[\"enable_sniffing\"]:\n\t\t\t\tto_observe = [ x for x in message.split(b\":\", 1)[1].strip(b\" \\t\\r\").split(b\" \") ]\n\t\t\t\twith self.lock:\n\t\t\t\t\tfor l in to_observe:\n\t\t\t\t\t\tclient.observe_action(self, SCCDaemon.source_to_constant(l))\n\t\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\telse:\n\t\t\t\tlog.warning(\"Refused 'Observe' request: Sniffing disabled\")\n\t\t\t\tclient.wfile.write(b\"Fail: Sniffing disabled.\\n\")\n\t\telif message.startswith(b\"Replace:\"):\n\t\t\ttry:\n\t\t\t\tl, actionstr = message.split(b\":\", 1)[1].strip(b\" \\t\\r\").split(b\" \", 1)\n\t\t\t\taction = TalkingActionParser().restart(actionstr).parse().compress()\n\t\t\texcept Exception as e:\n\t\t\t\te = str(e).encode(\"utf-8\").decode('unicode_escape').encode(\"latin1\")\n\t\t\t\tclient.wfile.write(b\"Fail: failed to parse: \" + e + b\"\\n\")\n\t\t\t\treturn\n\t\t\twith self.lock:\n\t\t\t\ttry:\n\t\t\t\t\tif not self._can_lock_action(client.mapper, SCCDaemon.source_to_constant(l)):\n\t\t\t\t\t\tclient.wfile.write(b\"Fail: Cannot lock \" + l.encode(\"utf-8\") + b\"\\n\")\n\t\t\t\t\t\treturn\n\t\t\t\texcept ValueError as e:\n\t\t\t\t\ttb = str(traceback.format_exc()).encode(\"utf-8\").decode('unicode_escape').encode(\"latin1\")\n\t\t\t\t\tclient.wfile.write(b\"Fail: \" + tb + b\"\\n\")\n\t\t\t\t\treturn\n\t\t\t\tclient.replace_action(self, SCCDaemon.source_to_constant(l), action)\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telif message.startswith(b\"Lock:\"):\n\t\t\tto_lock = [ x for x in message.split(b\":\", 1)[1].strip(b\" \\t\\r\").split(b\" \") ]\n\t\t\twith self.lock:\n\t\t\t\ttry:\n\t\t\t\t\tfor l in to_lock:\n\t\t\t\t\t\tif not self._can_lock_action(client.mapper, SCCDaemon.source_to_constant(l)):\n\t\t\t\t\t\t\tclient.wfile.write(b\"Fail: Cannot lock \" + l.encode(\"utf-8\") + b\"\\n\")\n\t\t\t\t\t\t\treturn\n\t\t\t\texcept ValueError as e:\n\t\t\t\t\ttb = str(traceback.format_exc()).encode(\"utf-8\").decode('unicode_escape').encode(\"latin1\")\n\t\t\t\t\tclient.wfile.write(b\"Fail: \" + tb + b\"\\n\")\n\t\t\t\t\treturn\n\t\t\t\tfor l in to_lock:\n\t\t\t\t\tclient.lock_action(self, SCCDaemon.source_to_constant(l))\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telif message.startswith(b\"Unlock.\"):\n\t\t\twith self.lock:\n\t\t\t\tclient.unlock_actions(self)\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telif message.startswith(b\"Reconfigure.\"):\n\t\t\twith self.lock:\n\t\t\t\t# Load config\n\t\t\t\tcfg = Config()\n\t\t\t\t# Reconfigure connected controllers\n\t\t\t\tfor c in self.controllers:\n\t\t\t\t\tc.apply_config(cfg.get_controller_config(c.get_id()))\n\t\t\t\t# Start or stop scc-autoswitch-daemon as needed\n\t\t\t\tneed_autoswitch_daemon = len(cfg[\"autoswitch\"]) > 0\n\t\t\t\tif need_autoswitch_daemon and self.xdisplay and not self.autoswitch_daemon:\n\t\t\t\t\tself.subprocs.append(Subprocess(\"scc-autoswitch-daemon\", True))\n\t\t\t\telif not need_autoswitch_daemon and self.autoswitch_daemon:\n\t\t\t\t\tself._remove_subproccess(\"scc-autoswitch-daemon\")\n\t\t\t\t\tself.autoswitch_daemon.close()\n\t\t\t\t\tself.autoswitch_daemon = None\n\t\t\t\t# Respond\n\t\t\t\ttry:\n\t\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\t\t\tself._send_to_all(\"Reconfigured.\\n\".encode(\"utf-8\"))\n\t\t\t\texcept:\n\t\t\t\t\tpass\n\t\telif message.startswith(b\"Rescan.\"):\n\t\t\tcbs = []\n\t\t\twith self.lock:\n\t\t\t\tcbs += self.rescan_cbs\n\t\t\t\t# Respond first\n\t\t\t\ttry:\n\t\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\t\texcept:\n\t\t\t\t\tpass\n\t\t\t# Do stuff later\n\t\t\t# (this cannot be done while self.lock is held, as creating new\n\t\t\t# controller would create race condition)\n\t\t\tfor cb in self.rescan_cbs:\n\t\t\t\ttry:\n\t\t\t\t\tcb()\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.exception(e)\n\t\t\t# dev_monitor rescan has to be last to run\n\t\t\ttry:\n\t\t\t\tself.dev_monitor.rescan()\n\t\t\texcept Exception as e:\n\t\t\t\tlog.exception(e)\n\n\t\telif message.startswith(b\"Turnoff.\"):\n\t\t\tto_turn_off = []\n\t\t\twith self.lock:\n\t\t\t\tif client.mapper.get_controller():\n\t\t\t\t\tto_turn_off.append(client.mapper.get_controller())\n\t\t\t\telse:\n\t\t\t\t\tto_turn_off += [ c for c in self.controllers ]\n\t\t\tfor c in to_turn_off:\n\t\t\t\tc.turnoff()\n\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telif message.startswith(b\"Gesture:\"):\n\t\t\ttry:\n\t\t\t\twhat, up_angle = message[8:].strip().split(b\" \", 2)\n\t\t\t\tup_angle = int(up_angle)\n\t\t\texcept Exception as  e:\n\t\t\t\ttb = str(traceback.format_exc()).encode(\"utf-8\").decode('unicode_escape').encode(\"latin1\")\n\t\t\t\tclient.wfile.write(b\"Fail: \" + tb + b\"\\n\")\n\t\t\t\treturn\n\t\t\twith self.lock:\n\t\t\t\tclient.request_gesture(self, what, up_angle)\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telif message.startswith(b\"Restart.\"):\n\t\t\tself.on_sa_restart()\n\t\telif message.startswith(b\"Gestured:\"):\n\t\t\tgstr = message[9:].strip()\n\t\t\tclient.gesture_action.gesture(client.mapper, gstr)\n\t\t\twith self.lock:\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telif message.startswith(b\"Selected:\"):\n\t\t\tmenuaction = None\n\t\t\tdef press(mapper):\n\t\t\t\ttry:\n\t\t\t\t\tmenuaction.button_press(mapper)\n\t\t\t\t\tclient.mapper.schedule(0.1, release)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.error(\"Error while processing menu action\")\n\t\t\t\t\tlog.exception(e)\n\t\t\tdef release(mapper):\n\t\t\t\ttry:\n\t\t\t\t\tmenuaction.button_release(mapper)\n\t\t\t\texcept Exception as e:\n\t\t\t\t\tlog.error(\"Error while processing menu action\")\n\t\t\t\t\tlog.exception(e)\n\t\t\t\n\t\t\twith self.lock:\n\t\t\t\ttry:\n\t\t\t\t\t#menu_id, item_id = shsplit(message)[1:]\n\t\t\t\t\t#print(message)\n\t\t\t\t\ttmp = shsplit(str(message, \"utf-8\"))\n\t\t\t\t\tmenu_id, item_id = tmp[1:]\n\t\t\t\t\tmenuaction = None\n\t\t\t\t\tif menu_id in (None, \"None\"):\n\t\t\t\t\t\tmenuaction = self.osd_ids[item_id]\n\t\t\t\t\telif \".\" in menu_id:\n\t\t\t\t\t\t# TODO: Move this common place\n\t\t\t\t\t\tdata = json.loads(open(menu_id, \"r\").read())\n\t\t\t\t\t\tmenudata = MenuData.from_json_data(data, TalkingActionParser())\n\t\t\t\t\t\tmenuaction = menudata.get_by_id(item_id).action\n\t\t\t\t\telse:\n\t\t\t\t\t\tmenuaction = client.mapper.profile.menus[menu_id].get_by_id(item_id).action\n\t\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\t\t\texcept:\n\t\t\t\t\tlog.warning(\"Selected menu item is no longer valid.\")\n\t\t\t\t\tclient.wfile.write(b\"Fail: Selected menu item is no longer valid\\n\")\n\t\t\t\tif menuaction:\n\t\t\t\t\tclient.mapper.schedule(0, press)\n\t\telif message.startswith(b\"Register:\"):\n\t\t\twith self.lock:\n\t\t\t\tif message.strip().endswith(b\"osd\"):\n\t\t\t\t\tif self.osd_daemon: self.osd_daemon.close()\n\t\t\t\t\tself.osd_daemon = client\n\t\t\t\t\tlog.info(\"Registered scc-osd-daemon\")\n\t\t\t\telif message.strip().endswith(b\"autoswitch\"):\n\t\t\t\t\tif self.autoswitch_daemon: self.autoswitch_daemon.close()\n\t\t\t\t\tself.autoswitch_daemon = client\n\t\t\t\t\tlog.info(\"Registered scc-autoswitch-daemon\")\n\t\t\t\tclient.wfile.write(b\"OK.\\n\")\n\t\telse:\n\t\t\tclient.wfile.write(b\"Fail: Unknown command\\n\")\n\t\n\t\n\tdef _remove_subproccess(self, binary_name):\n\t\t\"\"\"\n\t\tRemoves subproccess started with specified binary name from list of\n\t\tmanaged subproccesses, effectively preventing daemon from\n\t\tauto-restarting it.\n\t\t\n\t\tShould be called while lock is acquired\n\t\t\"\"\"\n\t\tn = []\n\t\tfor i in self.subprocs:\n\t\t\tif i.binary_name == binary_name:\n\t\t\t\ti.mark_killed()\n\t\t\telse:\n\t\t\t\tn.append(i)\n\t\tself.subprocs = n\n\t\n\t\n\tdef _can_lock_action(self, mapper, what):\n\t\t\"\"\"\n\t\tReturns True if action assigned to axis,\n\t\tpad or button is not yet locked.\n\t\t\n\t\tShould be called while self.lock is acquired.\n\t\t\"\"\"\n\t\t# TODO: Probably move to mapper\n\t\tis_locked = (lambda a: isinstance(a, LockedAction) or\n\t\t\t(isinstance(a, ObservingAction) and isinstance(a.original_action, LockedAction)))\n\t\t\n\t\tif what == STICK:\n\t\t\tif is_locked(mapper.profile.buttons[SCButtons.STICKPRESS]):\n\t\t\t\treturn False\n\t\t\tif is_locked(mapper.profile.stick):\n\t\t\t\treturn False\n\t\t\treturn True\n\t\tif what == RSTICK:\n\t\t\tif is_locked(mapper.profile.buttons[SCButtons.RSTICKPRESS]):\n\t\t\t\treturn False\n\t\t\tif is_locked(mapper.profile.rstick):\n\t\t\t\treturn False\n\t\t\treturn True\n\t\tif what == SCButtons.LT:\n\t\t\treturn not is_locked(mapper.profile.triggers[LEFT])\n\t\tif what == SCButtons.RT:\n\t\t\treturn not is_locked(mapper.profile.triggers[RIGHT])\n\t\tif what in SCButtons.__members__.values():\n\t\t\treturn not is_locked(mapper.profile.buttons[what])\n\t\tif what in (LEFT, RIGHT, CPAD, DPAD):\n\t\t\treturn not is_locked(mapper.profile.pads[what])\n\t\treturn False\n\t\n\t\n\tdef _apply(self, mapper, what, callback, *args):\n\t\t\"\"\"\n\t\tApplies callback on action that is currently set to input specified\n\t\tby 'what'. Raises ValueError if what is not known.\n\t\t\n\t\tFor example, if what == STICK, executes\n\t\t\tmapper.profile.stick = callback(mapper.profile.stick, *args)\n\t\t\"\"\"\n\t\tif what == STICK:\n\t\t\tmapper.profile.stick = callback(mapper.profile.stick, *args)\n\t\telif what == RSTICK:\n\t\t\tmapper.profile.rstick = callback(mapper.profile.rstick, *args)\n\t\telif what == SCButtons.LT:\n\t\t\tmapper.profile.triggers[LEFT] = callback(mapper.profile.triggers[LEFT], *args)\n\t\telif what == SCButtons.RT:\n\t\t\tmapper.profile.triggers[RIGHT] = callback(mapper.profile.triggers[RIGHT], *args)\n\t\telif what in SCButtons.__members__.values():\n\t\t\tr = callback(mapper.profile.buttons[what], *args)\n\t\t\tmapper.profile.buttons[what] = r\n\t\telif what in (LEFT, RIGHT):\n\t\t\tif what == LEFT:\n\t\t\t\tmapper.buttons &= ~SCButtons.LPADTOUCH\n\t\t\telse:\n\t\t\t\tmapper.buttons &= ~SCButtons.RPADTOUCH\n\t\t\ta = callback(mapper.profile.pads[what], *args)\n\t\t\ta.whole(mapper, 0, 0, what)\n\t\t\tmapper.profile.pads[what] = a\n\t\telif what in (CPAD, DPAD):\n\t\t\ta = callback(mapper.profile.pads[what], *args)\n\t\t\ta.whole(mapper, 0, 0, what)\n\t\t\tmapper.profile.pads[what] = a\n\t\telse:\n\t\t\traise ValueError(\"Unknown source: %s\" % (what,))\n\t\n\t\n\t@staticmethod\n\tdef source_to_constant(s):\n\t\t\"\"\"\n\t\tTurns string as 'A', 'LEFT' or 'ABS_X' into one of SCButtons.*,\n\t\tLEFT, RIGHT or STICK constants.\n\t\t\n\t\tRaises ValueError if passed string cannot be converted.\n\t\t\n\t\tUsed when parsing `Lock: ...` message\n\t\t\"\"\"\n\t\ts = s.decode(\"utf-8\").strip(\" \\t\\r\\n\")\n\t\tif s in (STICK, LEFT, RIGHT, CPAD):\n\t\t\treturn s\n\t\tif s == \"STICKPRESS\":\n\t\t\t# Special case, as that button is actually named STICK :(\n\t\t\treturn SCButtons.STICKPRESS\n\t\tif hasattr(SCButtons, s):\n\t\t\treturn getattr(SCButtons, s)\n\t\traise ValueError(\"Unknown source: %s\" % (s,))\n\t\n\t\n\tdef _remove_socket(self):\n\t\tself.sserver.shutdown()\n\t\tif os.path.exists(self.socket_file):\n\t\t\tos.unlink(self.socket_file)\n\t\tlog.debug(\"Control socket removed\")\n\t\n\t\n\tdef debug(self):\n\t\tset_logging_level(True, True)\n\t\tself.on_start()\n\t\tself.write_pid()\n\t\ttry:\n\t\t\tself.run()\n\t\texcept KeyboardInterrupt:\n\t\t\tlog.debug(\"Break\")\n\t\tself.sigterm()\n\n\nclass Client(object):\n\tdef __init__(self, connection, mapper, rfile, wfile):\n\t\tself.connection = connection\n\t\tself.rfile = rfile\n\t\tself.wfile = wfile\n\t\tself.mapper = mapper\n\t\tself.gesture_action = None\n\t\tself.locked_actions = {}\n\t\n\t\n\tdef close(self):\n\t\t\"\"\" Closes connection to this client \"\"\"\n\t\ttry:\n\t\t\tself.connection.shutdown(True)\n\t\texcept:\n\t\t\tpass\n\t\n\t\n\tdef request_gesture(self, daemon, what, up_angle):\n\t\t\"\"\"\n\t\tHandler used when client requested gesture detection with\n\t\t\"Gesture:\" message.\n\t\t\n\t\tShould be called while daemon.lock is acquired.\n\t\t\"\"\"\n\t\tdef cb(gesture):\n\t\t\t# Called while lock is being held\n\t\t\ttry:\n\t\t\t\tself.wfile.write(b\"Gesture: %s %s\\n\" % (what, gesture))\n\t\t\texcept:\n\t\t\t\tpass\n\t\t\n\t\tgd = daemon._start_gesture(self.mapper, what, up_angle, cb)\n\t\tgd.enable()\n\t\tlog.debug(\"Gesture detection requested on %s\", what)\n\t\n\t\n\tdef lock_action(self, daemon, what):\n\t\t\"\"\"\n\t\tLocks action so event can be send to client instead of handling it.\n\t\t\n\t\tShould be called while daemon.lock is acquired.\n\t\t\"\"\"\n\t\tdef lock(action, what):\n\t\t\t# ObservingAction should be above LockedAction\n\t\t\tif isinstance(action, ObservingAction):\n\t\t\t\taction.original_action = LockedAction(what, self, action.original_action)\n\t\t\t\treturn action\n\t\t\treturn LockedAction(what, self, action)\n\t\t\n\t\tdaemon._apply(self.mapper, what, lock, what)\n\t\n\t\n\tdef observe_action(self, daemon, what):\n\t\t\"\"\"\n\t\tEnables observing of action so event is both sent to client and handled.\n\t\t\n\t\tShould be called while daemon.lock is acquired.\n\t\t\"\"\"\n\t\tdaemon._apply(self.mapper, what,\n\t\t\t\tlambda a : ObservingAction(what, self, a))\n\t\n\t\n\tdef replace_action(self, daemon, what, action):\n\t\t\"\"\"\n\t\tTemporally replaces action in way that allows reversing operation when\n\t\tclient disconnects.\n\t\t\n\t\tShould be called while daemon.lock is acquired.\n\t\t\"\"\"\n\t\tdaemon._apply(self.mapper, what,\n\t\t\t\tlambda a : ReplacedAction(what, self, action, a))\n\t\n\t\n\tdef unlock_actions(self, daemon):\n\t\t\"\"\" Should be called while daemon.lock is acquired \"\"\"\n\t\tlocked, self.locked_actions = self.locked_actions, {}\n\t\tfor mapper in locked:\n\t\t\ts = locked[mapper]\n\t\t\tfor a in s:\n\t\t\t\ta.unlock(daemon)\n\t\n\t\n\tdef reaply_locks(self, daemon, mapper):\n\t\t\"\"\"\n\t\tCalled after profile is changed.\n\t\tShould be called while daemon.lock is acquired\n\t\t\"\"\"\n\t\tif mapper in self.locked_actions:\n\t\t\ts, self.locked_actions[mapper] = self.locked_actions[mapper], set()\n\t\t\tfor a in s:\n\t\t\t\ta.reaply(self, daemon)\n\n\nclass ReportingAction(Action):\n\t\"\"\"\n\tAction used to send requested inputs to client.\n\tBase for LockedAction and ObservingAction\n\t\"\"\"\n\tMIN_DIFFERENCE = 300\n\t\n\tdef __init__(self, what, client):\n\t\tself.what = what\n\t\tself.client = client\n\t\tself.mapper = client.mapper\n\t\tself.old_pos = 0, 0\n\t\n\t\n\tdef _store_lock(self):\n\t\tif self.mapper not in self.client.locked_actions:\n\t\t\tself.client.locked_actions[self.mapper] = set()\n\t\tself.client.locked_actions[self.mapper].add(self)\n\t\n\t\n\tdef __repr__(self):\n\t\treturn \"<%s of %x>\" % (self.__class__.__name__, hash(self.client))\n\t__str__ = __repr__\n\t\n\t\n\tdef _report(self, message):\n\t\ttry:\n\t\t\tself.client.wfile.write(message.encode(\"utf-8\"))\n\t\texcept Exception as e:\n\t\t\t# May fail when client dies\n\t\t\tself.client.rfile.close()\n\t\t\tself.client.wfile.close()\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\tif mapper.get_controller():\n\t\t\tself._report(\"Event: %s %s %s %s\\n\" % (\n\t\t\t\tmapper.get_controller().get_id(),\n\t\t\t\tnameof(self.what), position, old_position\n\t\t\t))\n\t\n\t\n\tdef button_press(self, mapper, number=1):\n\t\tif mapper.get_controller():\n\t\t\tif self.what == SCButtons.STICKPRESS:\n\t\t\t\tself._report(\"Event: %s STICKPRESS %s\\n\" % (\n\t\t\t\t\tmapper.get_controller().get_id(),\n\t\t\t\t\tnumber\n\t\t\t\t))\n\t\t\telse:\n\t\t\t\tself._report(\"Event: %s %s %s\\n\" % (\n\t\t\t\t\tmapper.get_controller().get_id(),\n\t\t\t\t\tnameof(self.what),\n\t\t\t\t\tnumber\n\t\t\t\t))\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tReportingAction.button_press(self, mapper, 0)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tmin_difference = self.MIN_DIFFERENCE\n\t\tif what == CPAD: min_difference /= 10\n\t\tif (x == 0 or y == 0 or abs(x - self.old_pos[0]) > min_difference\n\t\t\t\t\t\t\tor abs(y - self.old_pos[1] > min_difference)):\n\t\t\tself.old_pos = x, y\n\t\t\tif mapper.get_controller():\n\t\t\t\tself._report(\"Event: %s %s %s %s\\n\" % (\n\t\t\t\t\tmapper.get_controller().get_id(),\n\t\t\t\t\twhat, x, y\n\t\t\t\t))\n\n\nclass LockedAction(ReportingAction):\n\t\"\"\" Temporal action used to send requested inputs to client \"\"\"\n\tdef __init__(self, what, client, original_action):\n\t\tReportingAction.__init__(self, what, client)\n\t\tself.original_action = original_action\n\t\toriginal_action.cancel(self.mapper)\n\t\tself._store_lock()\n\t\tlog.debug(\"%s locked by %s\", self.what, self.client)\n\t\n\t\n\tdef reaply(self, client, daemon):\n\t\tclient.lock_action(daemon, self.what)\n\t\n\t\n\tdef unlock(self, daemon):\n\t\tdef _unlock(a):\n\t\t\tif isinstance(a, ObservingAction):\n\t\t\t\t# Needs to be handled specifically\n\t\t\t\ta.original_action = _unlock(a.original_action)\n\t\t\t\treturn a\n\t\t\tif isinstance(a, LockedAction):\n\t\t\t\treturn a.original_action\n\t\t\treturn a\n\t\tdaemon._apply(self.mapper, self.what, _unlock)\n\t\tlog.debug(\"%s unlocked\", self.what)\n\n\nclass ReplacedAction(LockedAction):\n\tdef __init__(self, what, client, new_action, original_action):\n\t\tReportingAction.__init__(self, what, client)\n\t\tself.original_action = original_action\n\t\tself.new_action = new_action.compress()\n\t\toriginal_action.cancel(self.mapper)\n\t\tself._store_lock()\n\t\tlog.debug(\"%s replaced by %s\", self.what, self.client)\n\t\n\t\n\tdef reaply(self, client, daemon):\n\t\tclient.replace_action(daemon, self.what, self.new_action)\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\tself.new_action.trigger(mapper, position, old_position)\n\t\n\t\n\tdef button_press(self, mapper, number=1):\n\t\tself.new_action.button_press(mapper, mapper)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tself.new_action.button_release(mapper, mapper)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tself.new_action.whole(mapper, x, y, what)\n\n\nclass ObservingAction(ReportingAction):\n\t\"\"\"\n\tSimilar to LockedAction, send inputs to client *and* executes actions.\n\t\"\"\"\n\tdef __init__(self, what, client, original_action):\n\t\tReportingAction.__init__(self, what, client)\n\t\tself.original_action = original_action\n\t\tself._store_lock()\n\t\tlog.debug(\"%s on %s observed by %x\", self.what,\n\t\t\tclient.mapper.get_controller(), hash(self.client))\n\t\n\t\n\tdef reaply(self, client, daemon):\n\t\tclient.observe_action(daemon, self.what)\n\t\n\t\n\tdef cancel(self, mapper):\n\t\tself.original_action.cancel(mapper)\n\t\n\t\n\tdef unlock(self, daemon):\n\t\tdef _unobserve(a):\n\t\t\tif isinstance(a, ObservingAction):\n\t\t\t\tif a.client == self.client:\n\t\t\t\t\treturn a.original_action\n\t\t\t\ta.original_action = _unobserve(a.original_action)\n\t\t\t\treturn a\n\t\t\tif isinstance(a, LockedAction):\n\t\t\t\ta.original_action = _unobserve(a.original_action)\n\t\t\t\treturn a\n\t\t\treturn a\n\t\t\n\t\tdaemon._apply(self.mapper, self.what, _unobserve)\n\t\tlog.debug(\"%s on %s no longer observed by %x\", self.what,\n\t\t\tself.mapper.get_controller(), hash(self.client))\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\tReportingAction.trigger(self, mapper, position, old_position)\n\t\tself.original_action.trigger(mapper, position, old_position)\n\t\n\t\n\tdef button_press(self, mapper, number=1):\n\t\tReportingAction.button_press(self, mapper, number)\n\t\tself.original_action.button_press(mapper)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tReportingAction.button_release(self, mapper)\n\t\tself.original_action.button_release(mapper)\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tReportingAction.whole(self, mapper, x, y, what)\n\t\tself.original_action.whole(mapper, x, y, what)\n\n\nclass Subprocess(object):\n\t\"\"\"\n\tPart of scc-daemon executed as another process, killed along with scc-daemon.\n\tCurrently scc-osd-daemon and scc-windowswitch-daemon.\n\t\"\"\"\n\t\n\tdef __init__(self, binary_name, debug, restart_after=5):\n\t\tself.binary_name = binary_name\n\t\tself.restart_after = restart_after\n\t\tself.args = [ sys.executable, find_binary(binary_name) ]\n\t\tif debug:\n\t\t\tself.args.append('debug')\n\t\tself._killed = False\n\t\tself.p = None\n\t\tself.t = threading.Thread(target=self._threaded)\n\t\tself.t.daemon = True\n\t\tself.t.start()\n\t\n\t\n\tdef _threaded(self, *a):\n\t\twhile not self._killed:\n\t\t\tself.p = subprocess.Popen(self.args, stdin=None)\n\t\t\tself.p.communicate()\n\t\t\tif self.p and self.p.returncode == 8:\n\t\t\t\tlog.warning(\"%s exited with code 8; not restarting\",\n\t\t\t\t\tself.binary_name)\n\t\t\t\tself.p = None\n\t\t\t\treturn\n\t\t\tself.p = None\n\t\t\tif not self._killed:\n\t\t\t\tlog.warning(\"%s died; restarting after %ss\",\n\t\t\t\t\tself.binary_name, self.restart_after)\n\t\t\t\ttime.sleep(self.restart_after)\n\t\n\t\n\tdef mark_killed(self):\n\t\t\"\"\"\n\t\tPrevents subprocess from being automatically restarted, but doesn't\n\t\treally kill it.\n\t\t\"\"\"\n\t\tself._killed = True\n\t\n\t\n\tdef kill(self):\n\t\tself.mark_killed()\n\t\tif self.p:\n\t\t\tself.p.kill()\n\t\tself.p = None\n"
  },
  {
    "path": "scc/scheduler.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Scheduler\n\nCentralized scheduler that should be used everywhere.\nRuns in SCCDaemon's (single-threaded) mainloop. That means all callbacks are\nalso called on main thread.\n\nUse schedule(delay, callback, *data) to register one-time task.\n\"\"\"\nimport time, queue, logging\nlog = logging.getLogger(\"Scheduler\")\n\n# TODO: Maybe create actual thread for this? Use poler? Scrap everything and rewrite it in GO?\n\nclass Scheduler(object):\n\t\n\tdef __init__(self):\n\t\tself._scheduled = queue.PriorityQueue()\n\t\tself._next = None\n\t\tself._now = time.time()\n\t\n\t\n\tdef schedule(self, delay, callback, *data):\n\t\t\"\"\"\n\t\tSchedules one-time task to be executed no sooner than after 'delay' of\n\t\tseconds. Delay may be float number.\n\t\t'callback' is called as callback(*data).\n\t\t\n\t\tReturned Task instance can be used to cancel task once scheduled.\n\t\t\"\"\"\n\t\ttask = Task(self._now + delay, callback, data)\n\t\tif self._next is None or task.time < self._next.time:\n\t\t\tif self._next:\n\t\t\t\tself._scheduled.put(self._next)\n\t\t\tself._next = task\n\t\telse:\n\t\t\tself._scheduled.put(task)\n\t\treturn task\n\t\n\t\n\tdef cancel_task(self, task):\n\t\t\"\"\"\n\t\tReturns True if task was sucessfully removed or False if task was\n\t\talready executed or not known at all.\n\t\t\n\t\tNote that this is slow as hell and completly thread-unsafe,\n\t\tso it _has_ to be called on main thread.\n\t\t\"\"\"\n\t\tif task == self._next:\n\t\t\tself._next = None if self._scheduled.empty() else self._scheduled.get()\n\t\t\treturn True\n\t\t# Fun part: All tasks are removed from PriorityQueue\n\t\t# until correct is found. Then everything is put back\n\t\ttasks, found = [], False\n\t\twhile not self._scheduled.empty():\n\t\t\tt = self._scheduled.get()\n\t\t\tif t == task:\n\t\t\t\tfound = True\n\t\t\t\tbreak\n\t\t\ttasks.append(t)\n\t\tfor t in tasks:\n\t\t\tself._scheduled.put(t)\n\t\treturn found\n\t\n\t\n\tdef run(self):\n\t\tself._now = time.time()\n\t\twhile self._next and self._now >= self._next.time:\n\t\t\tcallback, data = self._next.callback, self._next.data\n\t\t\tself._next = None if self._scheduled.empty() else self._scheduled.get()\n\t\t\tcallback(*data)\n\n\nclass Task(object):\n\t\n\tdef __init__(self, time, callback, data):\n\t\tself.time = time\n\t\tself.callback = callback\n\t\tself.data = data\n\t\n\t\n\tdef cancel(self):\n\t\t\"\"\" Marks task as canceled, without actually removing it from scheduler \"\"\"\n\t\tself.callback = lambda *a, **b: False\n\t\tself.data = ()\n\n\tdef __lt__(self, other):\n\t\tself.time < other.time\n"
  },
  {
    "path": "scc/scripts.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Scripts\n\nContains code for most of what can be done using 'scc' script.\nCreated so scc-* stuff doesn't polute /usr/bin.\n\"\"\"\nfrom scc.tools import init_logging, set_logging_level, find_binary\nimport os, sys, subprocess\n\n\nclass InvalidArguments(Exception): pass\n\n\ndef cmd_daemon(argv0, argv):\n\t\"\"\" Controls scc-daemon \"\"\"\n\t# Actually just passes parameters to scc-daemon\n\tscc_daemon = find_binary(\"scc-daemon\")\n\tsubprocess.Popen([scc_daemon] + argv).communicate()\n\n\ndef help_daemon():\n\tscc_daemon = find_binary(\"scc-daemon\")\n\tsubprocess.Popen([scc_daemon, \"--help\"]).communicate()\n\n\ndef cmd_gui(argv0, argv):\n\t\"\"\" Starts GUI \"\"\"\n\t# Passes parameters to sc-controller\n\tscc_daemon = find_binary(\"sc-controller\")\n\tsubprocess.Popen([scc_daemon] + argv).communicate()\n\n\ndef help_gui():\n\tscc_daemon = find_binary(\"sc-controller\")\n\tsubprocess.Popen([scc_daemon, \"--help\"]).communicate()\n\n\ndef cmd_test_evdev(argv0, argv):\n\t\"\"\"\n\tEvdev driver test. Displays gamepad inputs using evdev driver.\n\n\tUsage: scc test-evdev /dev/input/node\n\tReturn codes:\n\t  0 - normal exit\n\t  1 - invalid arguments or other error\n\t  2 - failed to open device\n\t\"\"\"\n\tfrom scc.drivers.evdevdrv import evdevdrv_test\n\treturn evdevdrv_test(argv)\n\n\ndef cmd_test_hid(argv0, argv):\n\t\"\"\"\n\tHID driver test. Displays gamepad inputs using hid driver.\n\n\tUsage: scc test-hid vendor_id device_id\n\tReturn codes:\n\t  0 - normal exit\n\t  1 - invalid arguments or other error\n\t  2 - failed to open device\n\t  3 - device is not HID-compatibile\n\t  4 - failed to parse HID descriptor\n\t\"\"\"\n\tfrom scc.drivers.hiddrv import hiddrv_test, HIDController\n\treturn hiddrv_test(HIDController, argv)\n\n\ndef help_osd_keyboard():\n\timport_osd()\n\tfrom scc.osd.keyboard import Keyboard\n\treturn run_osd_tool(Keyboard(), \"osd-keyboard\", [\"--help\"])\n\n\ndef cmd_osd_keyboard(argv0, argv):\n\t\"\"\" Displays on-screen keyboard \"\"\"\n\timport_osd()\n\tfrom scc.osd.keyboard import Keyboard\n\treturn run_osd_tool(Keyboard(), argv0, argv)\n\n\ndef cmd_list_profiles(argv0, argv):\n\t\"\"\"\n\tLists available profiles\n\n\tUsage: scc list-profiles [-a]\n\n\tArguments:\n\t  -a   Include names begining with dot\n\t\"\"\"\n\tfrom scc.paths import get_profiles_path, get_default_profiles_path\n\tpaths = [ get_default_profiles_path(), get_profiles_path() ]\n\tinclude_hidden = \"-a\" in argv\n\tlst = set()\n\tfor path in paths:\n\t\ttry:\n\t\t\tfor x in os.listdir(path):\n\t\t\t\tif x.endswith(\".sccprofile\"):\n\t\t\t\t\tif not include_hidden and x.startswith(\".\"):\n\t\t\t\t\t\tcontinue\n\t\t\t\t\tlst.add(x[0:-len(\".sccprofile\")])\n\t\texcept OSError:\n\t\t\tpass\n\tfor x in sorted(lst):\n\t\tprint(x)\n\treturn 0\n\n\ndef cmd_set_profile(argv0, argv):\n\t\"\"\"\n\tSets controller profile\n\n\tUsage: scc set-profile [controller_id] \"profile name\"\n\t\"\"\"\n\tfrom scc.tools import find_profile\n\n\tif len(argv) < 1:\n\t\tshow_help(command = \"set_profile\", out=sys.stderr)\n\t\treturn 1\n\ts = connect_to_daemon()\n\tif s is None: return -1\n\tif len(argv) >= 2:\n\t\tprofile = find_profile(argv[1])\n\t\tif profile is None:\n\t\t\tprint(\"Unknown profile:\", argv[1], file=sys.stderr)\n\t\t\treturn 1\n\t\tprint(\"Controller: %s\" % (argv[0],), file=s)\n\t\tif not check_error(s): return 1\n\t\tprint(\"Profile: %s\" % (profile,), file=s)\n\t\tif not check_error(s): return 1\n\telse:\n\t\tprofile = find_profile(argv[0])\n\t\tif profile is None:\n\t\t\tprint(\"Unknown profile:\", argv[0], file=sys.stderr)\n\t\t\treturn 1\n\t\tprint(\"Profile: %s\" % (profile,), file=s)\n\t\tif not check_error(s): return 1\n\treturn 0\n\n\ndef cmd_info(argv0, argv):\n\t\"\"\" Displays basic information about running driver \"\"\"\n\ts = connect_to_daemon()\n\tif s is None: return -1\n\t# Daemon already sends situable info, so this is mostly reading\n\t# until \"Ready.\" message is recieved.\n\tglobal_profile = None\n\tany_controller = False\n\twhile True:\n\t\tline = s.readline()\n\t\tif len(line) == 0:\n\t\t\tbreak\n\t\tline = line.strip(\"\\r\\n\\t \")\n\t\tif line == \"Ready.\":\n\t\t\tbreak\n\t\telif line.startswith(\"Current profile:\"):\n\t\t\tglobal_profile = line\n\t\t\tcontinue\n\t\telif line.startswith(\"Controller:\"):\n\t\t\tcontinue\n\t\telif line.startswith(\"Controller profile:\"):\n\t\t\tany_controller = True\n\t\telif line.startswith(\"Error:\"):\n\t\t\tprint(line)\n\t\t\tbreak\n\t\tif \":\" in line:\n\t\t\tprint(line)\n\tif not any_controller and global_profile:\n\t\tprint(global_profile)\n\treturn 0\n\n\ndef cmd_dependency_check(argv0, argv):\n\t\"\"\" Checks if all required libraries are installed on this system \"\"\"\n\ttry:\n\t\timport gi\n\t\tgi.require_version('Gtk', '3.0')\n\t\tgi.require_version('GdkX11', '3.0')\n\t\tgi.require_version('Rsvg', '2.0')\n\texcept ValueError as e1:\n\t\tprint(e1, file=sys.stderr)\n\t\tif \"Rsvg\" in str(e1):\n\t\t\tprint(\"Please, install 'gir1.2-rsvg-2.0' package to use this application\", file=sys.stderr)\n\t\telse:\n\t\t\tprint(\"Please, install 'PyGObject' package to use this application\", file=sys.stderr)\n\texcept ImportError as e2:\n\t\tprint(e2, file=sys.stderr)\n\t\tif \"gi\" in str(e2):\n\t\t\tprint(\"Please, install 'PyGObject' package to use this application\", file=sys.stderr)\n\t\treturn 1\n\ttry:\n\t\timport evdev\n\texcept Exception as e:\n\t\tprint(e, file=sys.stderr)\n\t\tprint(\"Please, install python-evdev package to enable non-steam controller support\", file=sys.stderr)\n\ttry:\n\t\timport scc.lib.xwrappers as X\n\t\tX.Atom\n\texcept Exception as e:\n\t\tprint(e, file=sys.stderr)\n\t\tprint(\"Failed to load X11 helpers, please, check your X installation\", file=sys.stderr)\n\t\treturn 1\n\treturn 0\n\n\ndef cmd_lock_inputs(argv0, argv, lock=\"Lock: \"):\n\t\"\"\"\n\tLocks and prints pressed buttons, pads and sticks\n\n\tLocks controller inputs and prints buttons, pads and stick as they are\n\tpressed or moved on controller.\n\n\tUsage: scc lock-inputs [button1] [stick1] [button2] ... [buttonN]\n\n\tAvailable button, sticks and pads:\n\t\tA X B Y START C BACK RGRIP LGRIP   LB RB LT RT STICK LPAD RPAD\n\n\tReturn codes:\n\t\t-1  - failed to connect to daemon\n\t\t-2  - failed to lock inputs\n\t\t-3  - connection terminated\n\t\t-4  - daemon reported error\n\t\"\"\"\n\ts = connect_to_daemon()\n\tif s is None: return -1\n\ttry:\n\t\twhile True:\n\t\t\tline = s.readline()\n\t\t\tif line == \"\":\n\t\t\t\treturn -3\n\t\t\telif line.startswith(\"Ready.\"):\n\t\t\t\tprint(lock + \" \".join([ x.upper() for x in argv ]), file=s)\n\t\t\t\ts.flush()\n\t\t\telif line.startswith(\"Error:\"):\n\t\t\t\tprint(line.strip(), file=sys.stderr)\n\t\t\t\treturn -4\n\t\t\telif line.startswith(\"Fail:\"):\n\t\t\t\tprint(line.strip(), file=sys.stderr)\n\t\t\t\treturn -2\n\t\t\telif line.startswith(\"Event:\"):\n\t\t\t\tdata = line.strip().split(\" \")\n\t\t\t\ttry:\n\t\t\t\t\tprint(\" \".join(data[2:]), file=sys.stdout)\n\t\t\t\t\tsys.stdout.flush()\n\t\t\t\texcept IOError:\n\t\t\t\t\t# Output closed, bail out\n\t\t\t\t\treturn 0\n\tfinally:\n\t\ts.close()\n\n\ndef cmd_print_inputs(argv0, argv, lock=\"Lock: \"):\n\t\"\"\"\n\tPrints pressed buttons, pads and sticks\n\n\tPrints controller inputs and prints buttons, pads and stick as they are\n\tpressed or moved on controller, without locking them exclusivelly.\n\n\tUsage: scc lock-inputs [button1] [stick1] [button2] ... [buttonN]\n\n\tAvailable button, sticks and pads:\n\t\tA X B Y START C BACK RGRIP LGRIP   LB RB LT RT STICK LPAD RPAD\n\n\tReturn codes:\n\t\t-1  - failed to connect to daemon\n\t\t-2  - failed to lock inputs\n\t\t-3  - connection terminated\n\t\t-4  - daemon reported error\n\t\"\"\"\n\treturn cmd_lock_inputs(argv0, argv, lock=\"Observe: \")\n\n\ndef connect_to_daemon():\n\t\"\"\"\n\tReturns socket connected to daemon or None if connection failed.\n\tOutputs error message in later case.\n\t\"\"\"\n\timport socket\n\tfrom scc.paths import get_daemon_socket\n\ttry:\n\t\ts = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n\t\ts.connect(get_daemon_socket())\n\texcept Exception as e:\n\t\tprint(\"Connection to scc-daemon failed: %s\" % (e, ), file=sys.stderr)\n\t\treturn None\n\treturn s.makefile(mode=\"rw\")\n\n\ndef check_error(s):\n\t\"\"\"\n\tReads line(s) from socket until \"OK.\" or \"Fail:\" is read.\n\tThen return True if message is \"OK.\" or prints message to stderr\n\tand return False if message is \"Fail:\"\n\t\"\"\"\n\ts.flush()\n\twhile True:\n\t\tline = s.readline()\n\t\tif len(line) == 0:\n\t\t\tprint(\"Connection closed\", file=sys.stderr)\n\t\t\treturn False\n\t\tline = line.strip(\"\\n\\r\\t \")\n\t\tif line == \"OK.\":\n\t\t\treturn True\n\t\tif line.startswith(\"Fail:\"):\n\t\t\tif \"\\\\n\" in line:\n\t\t\t\tline = line.replace(\"\\\\n\", \"\\n\")\n\t\t\tprint(line, file=sys.stderr)\n\t\t\treturn False\n\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(0)\n\n\ndef import_osd():\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\n\ndef run_osd_tool(tool, argv0, argv):\n\timport signal, argparse\n\tsignal.signal(signal.SIGINT, sigint)\n\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\n\tsys.argv[0] = \"scc osd-keyboard\"\n\tif not tool.parse_argumets([argv0] + argv):\n\t\tsys.exit(1)\n\ttool.run()\n\tsys.exit(tool.get_exit_code())\n\n\ndef show_help(command = None, out=sys.stdout):\n\tnames = [ x[4:] for x in globals() if x.startswith(\"cmd_\") ]\n\tmax_len = max([ len(x) for x in names ])\n\tif command in names:\n\t\tif \"help_\" + command in globals():\n\t\t\treturn globals()[\"help_\" + command]()\n\t\thlp = (globals()[\"cmd_\" + command].__doc__ or \"\").strip(\"\\t \\r\\n\")\n\t\tif hlp:\n\t\t\tlines = hlp.split(\"\\n\")\n\t\t\tif len(lines) > 0:\n\t\t\t\tfor line in lines:\n\t\t\t\t\tline = (line\n\t\t\t\t\t\t.replace(\"Usage: scc\", \"Usage: %s\" % (sys.argv[0], )))\n\t\t\t\t\tif line.startswith(\"\\t\"): line = line[1:]\n\t\t\t\t\tprint(line, file=out)\n\t\t\t\treturn 0\n\tprint(\"Usage: %s <command> [ arguments ]\" % (sys.argv[0], ), file=out)\n\tprint(\"\", file=out)\n\tprint(\"List of commands:\", file=out)\n\tfor name in sorted(names):\n\t\thlp = ((globals()[\"cmd_\" + name].__doc__ or \"\")\n\t\t\t\t\t.strip(\"\\t \\r\\n\")\n\t\t\t\t\t.split(\"\\n\")[0])\n\t\tprint((\" - %%-%ss %%s\" % (max_len, )) % (\n\t\t\tname.replace(\"_\", \"-\"), hlp), file=out)\n\treturn 0\n\n\ndef main():\n\tinit_logging()\n\tif len(sys.argv) < 2:\n\t\tsys.exit(show_help())\n\tif \"-h\" in sys.argv or \"--help\" in sys.argv:\n\t\twhile \"-h\" in sys.argv:\n\t\t\tsys.argv.remove(\"-h\")\n\t\twhile \"--help\" in sys.argv:\n\t\t\tsys.argv.remove(\"--help\")\n\t\tsys.exit(show_help(sys.argv[1].replace(\"-\", \"_\") if len(sys.argv) > 1 else None))\n\tif \"-v\" in sys.argv:\n\t\twhile \"-v\" in sys.argv:\n\t\t\tsys.argv.remove(\"-v\")\n\t\tset_logging_level(True, True)\n\telse:\n\t\tset_logging_level(False, False)\n\ttry:\n\t\tcommand = globals()[\"cmd_\" + sys.argv[1].replace(\"-\", \"_\")]\n\texcept:\n\t\tprint(\"Unknown command: %s\" % (sys.argv[1], ), file=sys.stderr)\n\t\tsys.exit(show_help(out=sys.stderr))\n\n\ttry:\n\t\tsys.exit(command(sys.argv[0], sys.argv[2:]))\n\texcept KeyboardInterrupt:\n\t\tsys.exit(0)\n\texcept InvalidArguments:\n\t\tprint(\"Invalid arguments\", file=sys.stderr)\n\t\tprint(\"\", file=sys.stderr)\n\t\tshow_help(sys.argv[1], out=sys.stderr)\n\t\tsys.exit(1)\n"
  },
  {
    "path": "scc/special_actions.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC Controller - Special Actions\n\nSpecial Action is \"special\" since it cannot be handled by mapper alone.\nInstead, on_sa_<actionname> method on handler instance set by\nmapper.set_special_actions_handler() is called to do whatever action is supposed\nto do. If handler is not set, or doesn't have reqiuired method defined,\naction only prints warning to console.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.constants import FE_STICK, FE_TRIGGER, FE_PAD, SCButtons\nfrom scc.constants import LEFT, RIGHT, STICK, SAME\nfrom scc.constants import STICK_PAD_MAX, DEFAULT\nfrom scc.actions import Action, NoAction, SpecialAction, ButtonAction\nfrom scc.actions import HapticEnabledAction, OSDEnabledAction\nfrom scc.actions import MOUSE_BUTTONS\nfrom scc.tools import strip_gesture, nameof, clamp\nfrom scc.modifiers import Modifier, NameModifier\nfrom difflib import get_close_matches\nfrom math import sqrt\n\nimport sys, time, logging\nlog = logging.getLogger(\"SActions\")\n_ = lambda x : x\n\n\nclass ChangeProfileAction(Action, SpecialAction):\n\tSA = COMMAND = \"profile\"\n\t\n\tdef __init__(self, profile):\n\t\tAction.__init__(self, profile)\n\t\tself.profile = profile\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif context == Action.AC_OSD:\n\t\t\treturn _(\"Profile: %s\") % (self.profile,)\n\t\tif context == Action.AC_SWITCHER:\n\t\t\treturn _(\"Switch to %s\") % (self.profile,)\n\t\treturn _(\"Profile Change\")\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_OSD\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"%s('%s')\" % (self.COMMAND, self.profile)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\t# Execute only when button is released (executing this when button\n\t\t# is pressed would send following button_release event to another\n\t\t# action from loaded profile)\n\t\tself.execute(mapper)\n\t\n\t\n\tdef whole(self, mapper, *a):\n\t\tself.execute(mapper)\n\n\nclass ShellCommandAction(Action, SpecialAction):\n\tSA = COMMAND = \"shell\"\n\t\n\tdef __init__(self, command):\n\t\t#if type(command) == str:\n\t\t#\tcommand = command.decode(\"unicode_escape\")\n\t\t#assert type(command) == unicode\n\t\tAction.__init__(self, command)\n\t\tself.command = command\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Execute Command\")\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_OSD\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"%s('%s')\" % (self.COMMAND, self.parameters[0])\n\t\n\t\n\tdef button_press(self, mapper):\n\t\t# Executes only when button is pressed\n\t\treturn self.execute(mapper)\n\n\nclass TurnOffAction(Action, SpecialAction):\n\tSA = COMMAND = \"turnoff\"\n\t\n\tdef __init__(self):\n\t\tAction.__init__(self)\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif context == Action.AC_OSD:\n\t\t\treturn _(\"Turning controller OFF\")\n\t\treturn _(\"Turn Off the Controller\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"%s()\" % (self.COMMAND,)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_OSD\n\t\n\t\n\tdef button_release(self, mapper):\n\t\t# Execute only when button is released (executing this when button\n\t\t# is pressed would hold stuck any other action bound to same button,\n\t\t# as button_release is not sent after controller turns off)\n\t\tself.execute(mapper)\n\t\n\t\n\tdef whole(self, mapper, *a):\n\t\tself.execute(mapper)\n\n\nclass RestartDaemonAction(Action, SpecialAction):\n\tSA = COMMAND = \"restart\"\n\tALIASES = (\"exit\", )\n\t\n\tdef __init__(self):\n\t\tAction.__init__(self)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Restart SCC-Daemon\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"%s()\" % (self.COMMAND,)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\t# Execute only when button is released (for same reason as\n\t\t# TurnOffAction does)\n\t\tself.execute(mapper)\n\n\nclass LedAction(Action, SpecialAction):\n\tSA = COMMAND = \"led\"\n\t\n\tdef __init__(self, brightness):\n\t\tAction.__init__(self, brightness)\n\t\tself.brightness = clamp(0, int(brightness), 100)\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Set LED brightness\")\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_OSD\n\t\n\t\n\tdef button_press(self, mapper):\n\t\t# Execute only when button is pressed\n\t\tself.execute(mapper)\n\n\nclass OSDAction(Action, SpecialAction):\n\t\"\"\"\n\tDisplays text in OSD, or, if used as modifier, displays action description\n\tand executes that action.\n\t\"\"\"\n\tSA = COMMAND = \"osd\"\n\tDEFAULT_TIMEOUT = 5\n\tDEFAULT_SIZE = 3\n\tPROFILE_KEY_PRIORITY = -5\t# After XYAction, but beforee everything else\n\t\n\tdef __init__(self, *parameters):\n\t\tAction.__init__(self, *parameters)\n\t\tself.action = None\n\t\tself.timeout = self.DEFAULT_TIMEOUT\n\t\tself.size = self.DEFAULT_SIZE\n\t\tif len(parameters) > 1 and type(parameters[0]) in (int, float):\n\t\t\t# timeout parameter included\n\t\t\tself.timeout = float(parameters[0])\n\t\t\tparameters = parameters[1:]\n\t\tif len(parameters) > 1 and type(parameters[0]) in (int, float):\n\t\t\t# size parameter included\n\t\t\tself.size = int(parameters[0])\n\t\t\tparameters = parameters[1:]\n\t\tif isinstance(parameters[0], Action):\n\t\t\tself.action = parameters[0]\n\t\t\tself.text = self.action.describe(Action.AC_OSD)\n\t\telse:\n\t\t\tself.text = str(parameters[0])\n\t\tif self.action and isinstance(self.action, OSDEnabledAction):\n\t\t\tself.action.enable_osd(self.timeout)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\tif self.action:\n\t\t\treturn self.action.get_compatible_modifiers()\n\t\treturn 0\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\ta = OSDAction(a)\n\t\tif data[\"osd\"] is not True:\n\t\t\ta.timeout = float(data[\"osd\"])\n\t\treturn a\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif self.action:\n\t\t\treturn _(\"%s (with OSD)\") % (self.action.describe(context), )\n\t\telif context == Action.AC_OSD:\n\t\t\treturn _(\"Display '%s'\" % self.text)\n\t\treturn _(\"OSD Message\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tparameters = []\n\t\tif self.timeout != self.DEFAULT_TIMEOUT or self.size != self.DEFAULT_SIZE:\n\t\t\tparameters.append(str(self.timeout))\n\t\tif self.size != self.DEFAULT_SIZE:\n\t\t\tparameters.append(str(self.size))\n\t\tif self.action:\n\t\t\tparameters.append(self.action.to_string(multiline=multiline, pad=pad))\n\t\telse:\n\t\t\tparameters.append(\"'%s'\" % (str(self.text),))\n\t\treturn (\" \" * pad) + \"%s(%s)\" % (self.COMMAND, \",\".join(parameters))\n\t\n\t\n\tdef strip(self):\n\t\tif self.action:\n\t\t\treturn self.action.strip()\n\t\treturn self\n\t\n\t\n\tdef compress(self):\n\t\tif self.action:\n\t\t\tif isinstance(self.action, OSDEnabledAction):\n\t\t\t\treturn self.action.compress()\n\t\t\tself.action = self.action.compress()\n\t\treturn self\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.execute(mapper)\n\t\tif self.action:\n\t\t\treturn self.action.button_press(mapper)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tif self.action:\n\t\t\treturn self.action.button_release(mapper)\n\t\n\t\n\tdef trigger(self, mapper, position, old_position):\n\t\tif self.action:\n\t\t\treturn self.action.trigger(mapper, position, old_position)\n\t\n\tdef axis(self, mapper, position, what):\n\t\tif self.action:\n\t\t\treturn self.action.axis(mapper, position, what)\n\t\n\tdef pad(self, mapper, position, what):\n\t\tif self.action:\n\t\t\treturn self.action.pad(mapper, position, what)\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif self.action:\n\t\t\treturn self.action.whole(mapper, x, y, what)\n\n\nclass ClearOSDAction(Action, SpecialAction):\n\t\"\"\"\n\tClears all windows from OSD layer. Cancels all menus, clears all messages,\n\tetc, etc.\n\t\"\"\"\n\tSA = COMMAND = \"clearosd\"\n\t\n\tdef describe(self, context):\n\t\treturn _(\"Hide all OSD Menus and Messages\")\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.execute(mapper)\n\n\nclass MenuAction(Action, SpecialAction, HapticEnabledAction):\n\t\"\"\"\n\tDisplays menu defined in profile or globally.\n\t\"\"\"\n\tSA = COMMAND = \"menu\"\n\tMENU_TYPE = \"menu\"\n\tMIN_STICK_DISTANCE = STICK_PAD_MAX / 3\n\tDEFAULT_POSITION = 10, -10\n\t\n\tdef __init__(self, menu_id, control_with=DEFAULT, confirm_with=DEFAULT,\n\t\t\t\t\tcancel_with=DEFAULT, show_with_release=False, size = 0):\n\t\tif control_with == SAME:\n\t\t\t# Little touch of backwards compatibility\n\t\t\tcontrol_with, confirm_with = DEFAULT, SAME\n\t\tif type(control_with) == int:\n\t\t\t# Allow short form in case when menu is assigned to pad\n\t\t\t# eg.: menu(\"some-id\", 3) sets size to 3\n\t\t\tcontrol_with, size = DEFAULT, control_with\n\t\tAction.__init__(self, menu_id, control_with, confirm_with, cancel_with, show_with_release, size)\n\t\tHapticEnabledAction.__init__(self)\n\t\tself.menu_id = menu_id\n\t\tself.control_with = control_with\n\t\tself.confirm_with = confirm_with\n\t\tself.cancel_with = cancel_with\n\t\tself.size = size\n\t\tself.x, self.y = MenuAction.DEFAULT_POSITION\n\t\tself.show_with_release = bool(show_with_release)\n\t\tself._stick_distance = 0\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Menu\")\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_FEEDBACK\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tif self.control_with == DEFAULT:\n\t\t\tdflt = (DEFAULT, DEFAULT, False)\n\t\t\tvals = (self.confirm_with, self.cancel_with, self.show_with_release)\n\t\t\tif dflt == vals:\n\t\t\t\t# Special case when menu is assigned to pad\n\t\t\t\tif self.size == 0:\n\t\t\t\t\treturn \"%s%s('%s')\" % (\" \" * pad, self.COMMAND, self.menu_id)\n\t\t\t\telse:\n\t\t\t\t\treturn \"%s%s('%s', %s)\" % (\" \" * pad, self.COMMAND, self.menu_id, self.size)\n\t\t\n\t\treturn \"%s%s(%s)\" % (\n\t\t\t\" \" * pad,\n\t\t\tself.COMMAND,\n\t\t\t\",\".join(Action.encode_parameters(self.strip_defaults()))\n\t\t)\n\t\n\t\n\tdef get_previewable(self):\n\t\treturn True\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tif not self.show_with_release:\n\t\t\tconfirm_with = self.confirm_with\n\t\t\tcancel_with = self.cancel_with\n\t\t\targs = [ mapper ]\n\t\t\tif confirm_with == SAME:\n\t\t\t\tconfirm_with = mapper.get_pressed_button() or DEFAULT\n\t\t\telif confirm_with == DEFAULT:\n\t\t\t\tconfirm_with = DEFAULT\n\t\t\tif cancel_with == DEFAULT:\n\t\t\t\tcancel_with = DEFAULT\n\t\t\tif nameof(self.control_with) in (LEFT, RIGHT):\n\t\t\t\targs += [ '--use-cursor' ]\n\t\t\targs += [\n\t\t\t\t'--control-with', nameof(self.control_with),\n\t\t\t\t'-x', str(self.x), '-y', str(self.y),\n\t\t\t\t'--size', str(self.size),\n\t\t\t\t'--confirm-with', nameof(confirm_with),\n\t\t\t\t'--cancel-with', nameof(cancel_with)\n\t\t\t]\n\t\t\tself.execute(*args)\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tif self.show_with_release:\n\t\t\tself.execute(mapper, '-x', str(self.x), '-y', str(self.y))\n\t\n\t\n\tdef whole(self, mapper, x, y, what, *params):\n\t\tif x == 0 and y == 0:\n\t\t\t# Sent when pad is released - don't display menu then\n\t\t\treturn\n\t\tif self.haptic:\n\t\t\tparams = list(params) + [\n\t\t\t\t\"--feedback-amplitude\",\n\t\t\t\tstr(self.haptic.get_amplitude())\n\t\t\t]\n\t\tif what in (LEFT, RIGHT):\n\t\t\tconfirm_with = self.confirm_with\n\t\t\tcancel_with = self.cancel_with\n\t\t\tif what == LEFT:\n\t\t\t\tif confirm_with == DEFAULT: confirm_with = SCButtons.LPAD\n\t\t\t\tif cancel_with == DEFAULT:  cancel_with  = SCButtons.LPADTOUCH\n\t\t\telif what == RIGHT:\n\t\t\t\tif confirm_with == DEFAULT: confirm_with = SCButtons.RPAD\n\t\t\t\tif cancel_with == DEFAULT:  cancel_with  = SCButtons.RPADTOUCH\n\t\t\telse:\n\t\t\t\t# Stick\n\t\t\t\tif confirm_with == DEFAULT: confirm_with = SCButtons.STICKPRESS\n\t\t\t\tif cancel_with == DEFAULT:  cancel_with  = SCButtons.B\n\t\t\tif not mapper.was_pressed(cancel_with):\n\t\t\t\tself.execute(mapper,\n\t\t\t\t\t'--control-with', what,\n\t\t\t\t\t'-x', str(self.x), '-y', str(self.y),\n\t\t\t\t\t'--use-cursor',\n\t\t\t\t\t'--size', str(self.size),\n\t\t\t\t\t'--confirm-with', nameof(confirm_with),\n\t\t\t\t\t'--cancel-with', nameof(cancel_with),\n\t\t\t\t\t*params\n\t\t\t\t)\n\t\tif what == STICK:\n\t\t\t# Special case, menu is displayed only if is moved enought\n\t\t\tdistance = sqrt(x*x + y*y)\n\t\t\tif self._stick_distance < MenuAction.MIN_STICK_DISTANCE and distance > MenuAction.MIN_STICK_DISTANCE:\n\t\t\t\tself.execute(mapper,\n\t\t\t\t\t'--control-with', STICK,\n\t\t\t\t\t'-x', str(self.x), '-y', str(self.y),\n\t\t\t\t\t'--use-cursor',\n\t\t\t\t\t'--size', str(self.size),\n\t\t\t\t\t'--confirm-with', \"STICKPRESS\",\n\t\t\t\t\t'--cancel-with', STICK,\n\t\t\t\t\t*params\n\t\t\t\t)\n\t\t\tself._stick_distance = distance\n\n\nclass HorizontalMenuAction(MenuAction):\n\t\"\"\"\n\tSame as menu, but packed as row\n\t\"\"\"\n\tCOMMAND = \"hmenu\"\n\tMENU_TYPE = \"hmenu\"\n\n\nclass GridMenuAction(MenuAction):\n\t\"\"\"\n\tSame as menu, but displayed in grid\n\t\"\"\"\n\tCOMMAND = \"gridmenu\"\n\tMENU_TYPE = \"gridmenu\"\n\n\nclass QuickMenuAction(MenuAction):\n\t\"\"\"\n\tQuickmenu. Max.6 items, controller by buttons\n\t\"\"\"\n\tCOMMAND = \"quickmenu\"\n\tMENU_TYPE = \"quickmenu\"\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"QuickMenu\")\n\t\n\t\n\tdef button_press(self, mapper):\n\t\t# QuickMenu is always shown with release\n\t\tpass\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tself.execute(mapper, '-x', str(self.x), '-y', str(self.y))\n\n\nclass RadialMenuAction(MenuAction):\n\t\"\"\"\n\tSame as grid menu, which is same as menu but displayed in grid,\n\tbut displayed as circle.\n\t\"\"\"\n\tCOMMAND = \"radialmenu\"\n\tMENU_TYPE = \"radialmenu\"\n\t\n\tdef __init__(self, menu_id, control_with=DEFAULT, confirm_with=DEFAULT,\n\t\t\t\t\tcancel_with=DEFAULT, show_with_release=False, size = 0):\n\t\tMenuAction.__init__(self, menu_id, control_with, confirm_with,\n\t\t\t\t\t\tcancel_with, show_with_release, size)\n\t\tself.rotation = 0\n\t\n\t\n\tdef whole(self, mapper, x, y, what):\n\t\tif self.rotation:\n\t\t\tMenuAction.whole(self, mapper, x, y, what, \"--rotation\", self.rotation)\n\t\telse:\n\t\t\tMenuAction.whole(self, mapper, x, y, what)\n\t\n\t\n\tdef set_rotation(self, angle):\n\t\tself.rotation = angle\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn MenuAction.get_compatible_modifiers(self) or Action.MOD_ROTATE\n\n\nclass DialogAction(Action, SpecialAction):\n\t\"\"\"\n\tDialog is actually kind of menu, but options for it are different.\n\t\"\"\"\n\tSA = COMMAND = \"dialog\"\n\tDEFAULT_POSITION = 10, -10\n\t\n\tdef __init__(self, *pars):\n\t\tAction.__init__(self, pars)\n\t\t\n\t\tself.options = []\n\t\tself.confirm_with = DEFAULT\n\t\tself.cancel_with  = DEFAULT\n\t\tself.text = _(\"Dialog\")\n\t\tself.x, self.y = MenuAction.DEFAULT_POSITION\n\t\t# First and 2nd parameter may be confirm and cancel button\n\t\tif len(pars) > 0 and pars[0] in SCButtons.__members__.values():\n\t\t\tself.confirm_with, pars = pars[0], pars[1:]\n\t\t\tif len(pars) > 0 and pars[0] in SCButtons.__members__.values():\n\t\t\t\tself.cancel_with, pars = pars[0], pars[1:]\n\t\t# 1st always present argument is title\n\t\tif len(pars) > 0:\n\t\t\tself.text, pars = pars[0], pars[1:]\n\t\t# ... everything else are actions\n\t\tself.options = pars\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Dialog\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\trv = \"%s%s(\" % (\" \" * pad, self.COMMAND)\n\t\tif self.confirm_with != DEFAULT:\n\t\t\trv += \"%s, \" % (nameof(self.confirm_with),)\n\t\t\tif self.cancel_with != DEFAULT:\n\t\t\t\trv += \"%s, \" % (nameof(self.cancel_with),)\n\t\trv += \"'%s', \" % (self.text,)\n\t\tif multiline:\n\t\t\trv += \"\\n%s\" % (\" \" * (pad + 2))\n\t\tfor option in self.options:\n\t\t\trv += \"%s, \" % (option.to_string(False),)\n\t\t\tif multiline:\n\t\t\t\trv += \"\\n%s\" % (\" \" * (pad + 2))\n\t\t\n\t\trv = rv.strip(\"\\n ,\")\n\t\tif multiline:\n\t\t\trv += \"\\n)\"\n\t\telse:\n\t\t\trv += \")\"\n\t\treturn rv\n\t\n\t\n\tdef get_previewable(self):\n\t\treturn False\n\t\n\t\n\tdef button_release(self, mapper):\n\t\tconfirm_with = self.confirm_with\n\t\tcancel_with = self.cancel_with\n\t\targs = [\n\t\t\tmapper,\n\t\t\t'-x', str(self.x), '-y', str(self.y),\n\t\t\t'--confirm-with', nameof(confirm_with),\n\t\t\t'--cancel-with', nameof(cancel_with),\n\t\t\t'--text', self.text,\n\t\t]\n\t\tfor x in self.options:\n\t\t\targs.append(x)\n\t\tself.execute(*args)\n\n\nclass KeyboardAction(Action, SpecialAction):\n\t\"\"\"\n\tShows OSD keyboard.\n\t\"\"\"\n\tSA = COMMAND = \"keyboard\"\n\t\n\tdef __init__(self):\n\t\tAction.__init__(self)\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_POSITION\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\tif context == Action.AC_OSD:\n\t\t\treturn _(\"Display Keyboard\")\n\t\treturn _(\"OSD Keyboard\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\treturn (\" \" * pad) + \"%s()\" % (self.COMMAND,)\n\t\n\t\n\tdef button_press(self, mapper):\n\t\tself.execute(mapper)\n\n\nclass PositionModifier(Modifier):\n\t\"\"\"\n\tSets position for OSD menu.\n\t\"\"\"\n\tCOMMAND = \"position\"\n\t\n\tdef _mod_init(self, x, y):\n\t\tself.position = (x, y)\n\t\n\t\n\tdef compress(self):\n\t\tif isinstance(self.action, MenuAction):\n\t\t\tself.action.x, self.action.y = self.position\n\t\treturn self.action\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, *b):\n\t\tx, y = data[PositionModifier.COMMAND]\n\t\treturn PositionModifier(x, y, a)\n\t\n\t\n\tdef describe(self, context):\n\t\treturn self.action.describe(context)\n\n\nclass GesturesAction(Action, OSDEnabledAction, SpecialAction):\n\t\"\"\"\n\tStars gesture detection on pad. Recognition is handled by whatever\n\tis special_actions_handler and results are then sent back to this action\n\tas parameter of gesture() method.\n\t\"\"\"\n\tSA = COMMAND = \"gestures\"\n\tPROFILE_KEYS = (\"gestures\",)\n\tPROFILE_KEY_PRIORITY = 2\n\tDEFAULT_PRECISION = 1.0\n\t\n\tdef __init__(self, *stuff):\n\t\tOSDEnabledAction.__init__(self)\n\t\tAction.__init__(self, *stuff)\n\t\tself.gestures = {}\n\t\tself.precision = self.DEFAULT_PRECISION\n\t\tgstr = None\n\t\t\n\t\tif len(stuff) > 0 and type(stuff[0]) in (int, float):\n\t\t\tself.precision = clamp(0.0, float(stuff[0]), 1.0)\n\t\t\tstuff = stuff[1:]\n\t\t\n\t\tfor i in stuff:\n\t\t\tif gstr is None and type(i) == str:\n\t\t\t\tgstr = i\n\t\t\telif gstr is not None and isinstance(i, Action):\n\t\t\t\tself.gestures[gstr] = i\n\t\t\t\tgstr = None\n\t\t\telse:\n\t\t\t\traise ValueError(\"Invalid parameter for '%s': unexpected %s\" % (\n\t\t\t\t\t\tself.COMMAND, i))\n\t\n\t\n\tdef get_compatible_modifiers(self):\n\t\treturn Action.MOD_OSD\n\t\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"Gestures\")\n\t\n\t\n\tdef to_string(self, multiline=False, pad=0):\n\t\tif multiline:\n\t\t\trv = [ (\" \" * pad) + self.COMMAND + \"(\" ]\n\t\t\tif self.precision != self.DEFAULT_PRECISION:\n\t\t\t\trv[0] += \"%s,\" % (self.precision)\n\t\t\tfor gstr in self.gestures:\n\t\t\t\ta_str = self.gestures[gstr].to_string(True).split(\"\\n\")\n\t\t\t\ta_str[0] = (\" \" * pad) + \"  '\" + (gstr + \"',\").ljust(11) + a_str[0]\t# Key has to be one of SCButtons\n\t\t\t\tfor i in range(1, len(a_str)):\n\t\t\t\t\ta_str[i] = (\" \" * pad) + \"  \" + a_str[i]\n\t\t\t\ta_str[-1] = a_str[-1] + \",\"\n\t\t\t\trv += a_str\n\t\t\tif rv[-1][-1] == \",\":\n\t\t\t\trv[-1] = rv[-1][0:-1]\n\t\t\trv += [ (\" \" * pad) + \")\" ]\n\t\t\treturn \"\\n\".join(rv)\n\t\telse:\n\t\t\trv = [ ]\n\t\t\tif self.precision != self.DEFAULT_PRECISION:\n\t\t\t\trv.append(str(self.precision))\n\t\t\tfor gstr in self.gestures:\n\t\t\t\trv += [ \"'%s'\" % (gstr,), self.gestures[gstr].to_string(False) ]\n\t\t\treturn self.COMMAND + \"(\" + \", \".join(rv) + \")\"\t\n\t\n\t\n\tdef compress(self):\n\t\tfor gstr in self.gestures:\n\t\t\ta = self.gestures[gstr].compress()\n\t\t\tif \"i\" in gstr:\n\t\t\t\tdel self.gestures[gstr]\n\t\t\t\tgstr = strip_gesture(gstr)\n\t\t\tself.gestures[gstr] = a\n\t\treturn self\n\t\n\t\n\t@staticmethod\n\tdef decode(data, a, parser, *b):\n\t\targs = []\n\t\tga = GesturesAction()\n\t\tga.gestures = {\n\t\t\tgstr : parser.from_json_data(data[GesturesAction.PROFILE_KEYS[0]][gstr])\n\t\t\tfor gstr in data[GesturesAction.PROFILE_KEYS[0]]\n\t\t}\n\t\tif \"name\" in data:\n\t\t\tga.name = data[\"name\"]\n\t\tif \"osd\" in data:\n\t\t\tga = OSDAction(ga)\n\t\treturn ga\n\n\tdef _find_exact_gesture(self, gesture_string):\n\t\treturn self.gestures.get(gesture_string)\n\n\tdef _find_ignore_stroke_count_gesture(self, gesture_string):\n\t\tstripped_gesture_string = strip_gesture(gesture_string)\n\t\treturn self.gestures.get(stripped_gesture_string)\n\n\tdef _find_best_match_gesture(self, gesture_string):\n\t\tNUM_MATCHES_TO_RETURN = 1\n\t\n\t\tsimilar_gestures = get_close_matches(gesture_string, self.gestures.keys(), NUM_MATCHES_TO_RETURN, self.precision)\n\t\tbest_gesture = next(iter(similar_gestures), None)\n\n\t\tif best_gesture is not None:\n\t\t\treturn self.gestures[best_gesture]\n\t\telse:\n\t\t\treturn None\n\n\tdef find_gesture_action(self, gesture_string):\n\t\taction = None\n\t\taction = action or self._find_exact_gesture(gesture_string)\n\t\taction = action or self._find_ignore_stroke_count_gesture(gesture_string)\n\t\taction = action or self._find_best_match_gesture(gesture_string)\n\t\treturn action\n\n\tdef gesture(self, mapper, gesture_string):\n\t\taction = self.find_gesture_action(gesture_string)\n\t\tif action:\n\t\t\taction.button_press(mapper)\n\t\t\tmapper.schedule(0, action.button_release)\n\n\tdef whole(self, mapper, x, y, what):\n\t\tif (x, y) != (0, 0):\n\t\t\t# (0, 0) singlanizes released touchpad\n\t\t\tself.execute(mapper, x, y, what)\n\n\nclass CemuHookAction(Action, SpecialAction):\n\tSA = COMMAND = \"cemuhook\"\n\tMAGIC_GYRO = (2000.0 / 32768.0)\n\tACC_RES_PER_G = 16384.0\n\t\n\tdef gyro(self, mapper, *pyr):\n\t\tsa_data = (\n\t\t\t-mapper.state.accel_x / CemuHookAction.ACC_RES_PER_G, # AccelX\n\t\t\t-mapper.state.accel_z / CemuHookAction.ACC_RES_PER_G, # AccelZ\n\t\t\tmapper.state.accel_y / CemuHookAction.ACC_RES_PER_G, # AccelY\n\t\t\tpyr[0] * CemuHookAction.MAGIC_GYRO, # Gyro Pitch\n\t\t\t-pyr[1] * CemuHookAction.MAGIC_GYRO, # Gyro Yaw\n\t\t\t-pyr[2] * CemuHookAction.MAGIC_GYRO, # Gyro Roll\n\t\t)\n\t\t#log.debug(sa_data)\n\t\tself.execute(mapper, sa_data)\n\t\n\tdef describe(self, context):\n\t\tif self.name: return self.name\n\t\treturn _(\"CemuHook\")\n\n# Register actions from current module\nAction.register_all(sys.modules[__name__])\n\n"
  },
  {
    "path": "scc/tools.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - tools\n\nVarious stuff that I don't care to fit anywhere else.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.paths import get_controller_icons_path, get_default_controller_icons_path\nfrom scc.paths import get_menuicons_path, get_default_menuicons_path\nfrom scc.paths import get_profiles_path, get_default_profiles_path\nfrom scc.paths import get_menus_path, get_default_menus_path\nfrom scc.paths import get_button_images_path\nfrom math import pi as PI, sin, cos, atan2, sqrt\nimport os\nimport ctypes\nimport shlex\nimport logging\nimport importlib.machinery\n\nHAVE_POSIX1E = False\ntry:\n\timport posix1e\n\tHAVE_POSIX1E = True\nexcept ImportError:\n\tpass\n\nlog = logging.getLogger(\"tools.py\")\n_ = lambda x : x\n\nLOG_FORMAT\t\t\t\t= \"%(levelname)s %(name)-13s %(message)s\"\n\ndef init_logging(prefix=\"\", suffix=\"\"):\n\t\"\"\"\n\tInitializes logging, sets custom logging format and adds one\n\tlogging level with name and method to call.\n\t\n\tprefix and suffix arguments can be used to modify log level prefixes.\n\t\"\"\"\n\tlogging.basicConfig(format=LOG_FORMAT)\n\tlogger = logging.getLogger()\n\t# Rename levels\n\tlogging.addLevelName(10, prefix + \"D\" + suffix)\t# Debug\n\tlogging.addLevelName(20, prefix + \"I\" + suffix)\t# Info\n\tlogging.addLevelName(30, prefix + \"W\" + suffix)\t# Warning\n\tlogging.addLevelName(40, prefix + \"E\" + suffix)\t# Error\n\t# Create additional, \"verbose\" level\n\tlogging.addLevelName(15, prefix + \"V\" + suffix)\t# Verbose\n\t# Add 'logging.verbose' method\n\tdef verbose(self, msg, *args, **kwargs):\n\t\treturn self.log(15, msg, *args, **kwargs)\n\tlogging.Logger.verbose = verbose\n\t# Wrap Logger._log in something that can handle utf-8 exceptions\n\told_log = logging.Logger._log\n\tdef _log(self, level, msg, args, exc_info=None, extra=None):\n\t\targs = tuple([\n\t\t\t(c.decode(\"utf-8\") if type(c) is bytes else c)\n\t\t\tfor c in args\n\t\t])\n\t\t#msg = msg if type(msg) is str else msg.decode(\"utf-8\")\n\t\told_log(self, level, msg, args, exc_info, extra)\n\tlogging.Logger._log = _log\n\n\ndef set_logging_level(verbose, debug):\n\t\"\"\" Sets logging level \"\"\"\n\tlogger = logging.getLogger()\n\tif debug:\t\t# everything\n\t\tlogger.setLevel(0)\n\telif verbose:\t# everything but debug\n\t\tlogger.setLevel(11)\n\telse:\t\t\t# INFO and worse\n\t\tlogger.setLevel(20)\n\n\ndef ensure_size(n, lst, fill_with=None):\n\t\"\"\"\n\tReturns copy of lst with size 'n'.\n\tIf lst is shorter, None's are appended.\n\tIf lst is longer, it is cat.\n\t\"\"\"\n\tl = list(lst)\n\twhile len(l) < n : l.append(fill_with)\n\treturn l[0:n]\n\n\ndef quat2euler(q0, q1, q2, q3):\n\t\"\"\"\n\tConverts quaterion to (pitch, yaw, roll).\n\tValues are in -PI to PI range.\n\t\"\"\"\n\tqq0, qq1, qq2, qq3 = q0**2, q1**2, q2**2, q3**2\n\txa = qq0 - qq1 - qq2 + qq3\n\txb = 2 * (q0 * q1 + q2 * q3)\n\txn = 2 * (q0 * q2 - q1 * q3)\n\tyn = 2 * (q1 * q2 + q0 * q3)\n\tzn = qq3 + qq2 - qq0 - qq1\n\t\n\tpitch = atan2(xb , xa)\n\tyaw   = atan2(xn , sqrt(1 - xn**2))\n\troll  = atan2(yn , zn)\n\treturn pitch, yaw, roll\n\n\ndef point_in_gtkrect(rect, x, y):\n\treturn (x > rect.x and y > rect.y and\n\t\tx < rect.x + rect.width and y < rect.y + rect.height)\n\n\ndef anglediff(a1, a2):\n\t\"\"\" Excpects values in radians \"\"\"\n\treturn (a2 - a1 + PI) % (2.0*PI) - PI\n\n\ndef degdiff(a1, a2):\n\t\"\"\" Excpects values in degrees \"\"\"\n\treturn (a2 - a1 + 180.0) % 360.0 - 180.0\n\n\ndef nameof(e):\n\t\"\"\"\n\tIf 'e' is enum value, returns e.name.\n\tOtherwise, returns str(e).\n\t\"\"\"\n\treturn e.name if hasattr(e, \"name\") else str(e)\n\n\ndef shjoin(lst):\n\t\"\"\" Joins list into shell-escaped, utf-8 encoded string \"\"\"\n\ts = [ x.encode(\"utf-8\") for x in lst ]\n\t#   - escape quotes\n\ts = [ x.encode('unicode_escape') if (b'\"' in x or b\"'\" in x) else x for x in s ]\n\t#   - quote strings with spaces\n\ts = [ b\"'%s'\" % (x,) if b\" \" in x else x for x in s ]\n\treturn b\" \".join(s)\n\n\ndef shsplit(s):\n\t\"\"\" Returs original list from what shjoin returned \"\"\"\n\tlex = shlex.shlex(s, posix=True)\n\tlex.escapedquotes = '\"\\''\n\tlex.whitespace_split = True\n\treturn [ x for x in list(lex) ]\n\n\ndef static_vars(**kwargs):\n\t\"\"\"Static variable func decorator\"\"\"\n\n\tdef decorate(func):\n\t\t\"\"\"inner function used to add kwargs attribute to a func\"\"\"\n\t\tfor k in kwargs:\n\t\t\tsetattr(func, k, kwargs[k])\n\t\treturn func\n\treturn decorate\n\n\ndef profile_is_override(name):\n\t\"\"\"\n\tReturns True if named profile exists both in user config directory and\n\tdefault_profiles directory.\n\t\"\"\"\n\tfilename = \"%s.sccprofile\" % (name,)\n\tif os.path.exists(os.path.join(get_profiles_path(), filename)):\n\t\tif os.path.exists(os.path.join(get_default_profiles_path(), filename)):\n\t\t\treturn True\n\treturn False\n\n\ndef profile_is_default(name):\n\t\"\"\"\n\tReturns True if named profile exists in default_profiles directory, even\n\tif it is overrided by profile in user config directory.\n\t\"\"\"\n\tfilename = \"%s.sccprofile\" % (name,)\n\treturn os.path.exists(os.path.join(get_default_profiles_path(), filename))\n\n\ndef get_profile_name(path):\n\t\"\"\"\n\tReturns profile name for specified path. Basically removes path and\n\t.sccprofile and .mod extension.\n\t\"\"\"\n\tparts = os.path.split(path)[-1].split(\".\")\n\tif parts[-1] == \"mod\": parts = parts[0:-1]\n\tif parts[-1] == \"sccprofile\": parts = parts[0:-1]\n\treturn \".\".join(parts)\n\n\ndef find_profile(name):\n\t\"\"\"\n\tReturns filename for specified profile name.\n\tThis is done by searching for name + '.sccprofile' in ~/.config/scc/profiles\n\tfirst and in /usr/share/scc/default_profiles if file is not found in first\n\tlocation.\n\t\n\tReturns None if profile cannot be found.\n\t\"\"\"\n\tfilename = \"%s.sccprofile\" % (name,)\n\tfor p in (get_profiles_path(), get_default_profiles_path()):\n\t\tpath = os.path.join(p, filename)\n\t\tif os.path.exists(path):\n\t\t\treturn path\n\treturn None\n\n\ndef find_icon(name, prefer_bw=False, paths=None, extensions=(\"png\", \"svg\")):\n\t\"\"\"\n\tReturns (filename, has_colors) for specified icon name.\n\tThis is done by searching for name + '.png' and name + \".bw.png\"\n\tin user and default menu-icons folders. \".svg\" is also supported, but only\n\tif no pngs are found.\n\t\n\tIf both colored and grayscale version is found, colored is returned, unless\n\tprefer_bw is set to True.\n\t\n\tpaths defaults to icons for menuicons\n\t\n\tReturns (None, False) if icon cannot be found.\n\t\"\"\"\n\tif name is None:\n\t\t# Special case, so code can pass menuitem.icon directly\n\t\treturn None, False\n\tif paths is None:\n\t\tpaths = get_default_menuicons_path(), get_menuicons_path()\n\tif name.endswith(\".bw\"):\n\t\tname = name[0:-3]\n\tfor extension in extensions:\n\t\tgray_filename = \"%s.bw.%s\" % (name, extension)\n\t\tcolors_filename = \"%s.%s\" % (name, extension)\n\t\tgray, colors = None, None\n\t\tfor p in paths:\n\t\t\t# Check grayscale\n\t\t\tif gray is None:\n\t\t\t\tpath = os.path.join(p, gray_filename)\n\t\t\t\tif os.path.exists(path):\n\t\t\t\t\tif prefer_bw:\n\t\t\t\t\t\treturn path, False\n\t\t\t\t\tgray = path\n\t\t\t# Check colors\n\t\t\tif colors is None:\n\t\t\t\tpath = os.path.join(p, colors_filename)\n\t\t\t\tif os.path.exists(path):\n\t\t\t\t\tif not prefer_bw:\n\t\t\t\t\t\treturn path, True\n\t\t\t\t\tcolors = path\n\t\tif colors is not None:\n\t\t\treturn colors, True\n\t\tif gray is not None:\n\t\t\treturn gray, False\n\treturn None, False\n\n\ndef find_button_image(name, prefer_bw=False):\n\t\"\"\" Similar to find_icon, but searches for button image \"\"\"\n\treturn find_icon(nameof(name), prefer_bw,\n\t\t\tpaths=[get_button_images_path()], extensions=(\"svg\",))\n\n\ndef menu_is_default(name):\n\t\"\"\"\n\tReturns True if named menu exists in default_menus directory, even\n\tif it is overrided by menu in user config directory.\n\t\"\"\"\n\treturn os.path.exists(os.path.join(get_default_menus_path(), name))\n\n\ndef find_menu(name):\n\t\"\"\"\n\tReturns filename for specified menu name.\n\tThis is done by searching for name in ~/.config/scc/menus\n\tfirst and in /usr/share/scc/default_menus later.\n\t\n\tReturns None if menu cannot be found.\n\t\"\"\"\n\tfor p in (get_menus_path(), get_default_menus_path()):\n\t\tpath = os.path.join(p, name)\n\t\tif os.path.exists(path):\n\t\t\treturn path\n\treturn None\n\n\ndef find_controller_icon(name):\n\t\"\"\"\n\tReturns filename for specified controller icon name.\n\tThis is done by searching for name in ~/.config/controller-icons\n\tfirst and in /usr/share/scc/images/controller-icons later.\n\t\n\tReturns None if icon cannot be found.\n\t\"\"\"\n\tfor p in (get_controller_icons_path(), get_default_controller_icons_path()):\n\t\tpath = os.path.join(p, name)\n\t\tif os.path.exists(path):\n\t\t\treturn path\n\treturn None\n\n\ndef find_binary(name):\n\t\"\"\"\n\tReturns full path to script or binary.\n\t\n\tWith some exceptions, this is done simply by searching PATH environment variable.\n\t\"\"\"\n\tif name.startswith(\"scc-osd-daemon\"):\n\t\t# Special case, this one is not supposed to go to /usr/bin\n\t\treturn os.path.join(os.path.split(__file__)[0], \"x11\", \"scc-osd-daemon.py\")\n\tif name.startswith(\"scc-autoswitch-daemon\"):\n\t\t# As above\n\t\treturn os.path.join(os.path.split(__file__)[0], \"x11\", \"scc-autoswitch-daemon.py\")\n\tuser_path = os.environ['PATH'].split(\":\")\n\t# Try to add the standard binary paths if not present in PATH\n\tfor d in [\"/sbin\", \"/bin\", \"/usr/sbin\", \"/usr/bin\"]:\n\t\tif d not in user_path:\n\t\t\tuser_path.append(d)\n\tfor i in user_path:\n\t\tpath = os.path.join(i, name)\n\t\tif os.path.exists(path):\n\t\t\treturn path\n\t# Not found, return name back and hope for miracle\n\treturn name\n\n\ndef find_library(libname):\n\t\"\"\"\n\tSearch for 'libname.so'.\n\tReturns library loaded with ctypes.CDLL\n\tRaises OSError if library is not found\n\t\"\"\"\n\tbase_path = os.path.dirname(__file__)\n\tlib, search_paths = None, []\n\tso_extensions = importlib.machinery.EXTENSION_SUFFIXES\n\tfor extension in so_extensions:\n\t\tsearch_paths += [\n\t\t\tos.path.abspath(os.path.normpath(\n\t\t\t\tos.path.join( base_path, '..', libname + extension ))),\n\t\t\tos.path.abspath(os.path.normpath(\n\t\t\t\tos.path.join( base_path, '../..', libname + extension )))\n\t\t\t]\n\t\n\tfor path in search_paths:\n\t\tif os.path.exists(path):\n\t\t\tlib = path\n\t\t\tbreak\n\t\n\tif not lib:\n\t\traise OSError('Cant find %s.so. searched at:\\n %s' % (\n\t\t\tlibname, '\\n'.join(search_paths)))\n\treturn ctypes.CDLL(lib)\n\n\ndef find_gksudo():\n\t\"\"\"\n\tSearchs for gksudo or other known graphical sudoing tool.\n\tReturns list of arguments.\n\t\"\"\"\n\tSUDOS = [\"gksudo\", \"gksu\", \"kdesudo\", \"pkexec\", \"xdg-su\"]\n\tfor name in SUDOS:\n\t\targs = name.split(\" \")\n\t\tbin = find_binary(args[0])\n\t\tif bin != args[0]:\n\t\t\treturn args\n\treturn None\n\n\ndef check_access(filename, write_required=True):\n\t\"\"\"\n\tChecks if user has read and optionaly write access to specified file.\n\tUses acl first and possix file permisions if acl cannot be used.\n\tReturns true only if user has both required access rights.\n\t\"\"\"\n\tif HAVE_POSIX1E:\n\t\tfor pset in posix1e.ACL(file=filename):\n\t\t\tif pset.tag_type == posix1e.ACL_USER and pset.qualifier == os.geteuid():\n\t\t\t\tif pset.permset.test(posix1e.ACL_READ) and (not write_required or pset.permset.test(posix1e.ACL_WRITE)):\n\t\t\t\t\treturn True\n\t\t\tif pset.tag_type == posix1e.ACL_GROUP and pset.qualifier in os.getgroups():\n\t\t\t\tif pset.permset.test(posix1e.ACL_READ) and (not write_required or pset.permset.test(posix1e.ACL_WRITE)):\n\t\t\t\t\treturn True\n\tif write_required:\n\t\treturn os.access(filename, os.R_OK | os.W_OK)\n\treturn os.access(filename, os.R_OK)\n\n\ndef strip_gesture(gstr):\n\t\"\"\"\n\tConverts gesture string to version where stroke lenght is ignored.\n\t\n\tThat means removing repeating characters and adding 'i' to front.\n\t\"\"\"\n\tlast, uniq = None, []\n\tfor x in gstr:\n\t\tif x != last:\n\t\t\tuniq.append(x)\n\t\tlast = x\n\tif uniq[0] != 'i':\n\t\tuniq = [ 'i' ] + uniq\n\treturn \"\".join(uniq)\n\n\nclamp = lambda low, value, high : min(high, max(low, value))\n\n\nPId4 = PI / 4.0\ndef circle_to_square(x, y):\n\t\"\"\"\n\tProjects coordinate in circle (of radius 1.0) to coordinate in square.\n\t\"\"\"\n\t# Adapted from http://theinstructionlimit.com/squaring-the-thumbsticks\n\t\n\t# Determine the theta angle\n\tangle = atan2(y, x) + PI\n\t\n\tsquared = 0, 0\n\t# Scale according to which wall we're clamping to\n\t# X+ wall\n\tif angle <= PId4 or angle > 7.0 * PId4:\n\t\tsquared = x * (1.0 / cos(angle)), y * (1.0 / cos(angle))\n\t# Y+ wall\n\telif angle > PId4 and angle <= 3.0 * PId4:\n\t\tsquared = x * (1.0 / sin(angle)), y * (1.0 / sin(angle))\n\t# X- wall\n\telif angle > 3.0 * PId4 and angle <= 5.0 * PId4:\n\t\tsquared = x * (-1.0 / cos(angle)), y * (-1.0 / cos(angle))\n\t# Y- wall\n\telif angle > 5.0 * PId4 and angle <= 7.0 * PId4:\n\t\tsquared = x * (-1.0 / sin(angle)), y * (-1.0 / sin(angle))\n\telse:\n\t\traise ValueError(\"Invalid angle...?\")\n\t\n\treturn squared\n"
  },
  {
    "path": "scc/uinput.c",
    "content": "/*\n * The MIT License (MIT)\n *\n * Copyright (c) 2015 Stany MARCEL <stanypub@gmail.com>\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n * THE SOFTWARE.\n */\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/param.h>\n#include <fcntl.h>\n#include <linux/uinput.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include <unistd.h>\n\n#pragma GCC diagnostic ignored \"-Wunused-result\"\n#define UNPUT_MODULE_VERSION 9\n#define MAX_FF_EVENTS 4\n\n#define INFINITE_RUMBLE\t\t10000\t\t// Not really infinite, but longer than controller can handle\n\nstruct feedback_effect {\n\tbool in_use;\n\tbool continuous_rumble;\n\tint32_t duration;\n\tint32_t delay;\n\tint32_t repetitions;\n\tuint16_t type;\n\tint16_t level;\n};\n\nint uinput_init(\n\tint\t key_len,\n\t__u16 * key,\n\tint\t abs_len,\n\t__u16 * abs,\n\t__s32 * abs_min,\n\t__s32 * abs_max,\n\t__s32 * abs_fuzz,\n\t__s32 * abs_flat,\n\tint\t rel_len,\n\t__u16 * rel,\n\tint\t keyboard,\n\t__u16   vendor,\n\t__u16   product,\n\t__u16   version,\n\tint\tff_effects_max,\n\tchar *  name)\n{\n\tstruct uinput_user_dev uidev;\n\tint fd;\n\tint i;\n\n\tmemset(&uidev, 0, sizeof(uidev));\n\t\n\tint mode = O_WRONLY | O_NONBLOCK;\n\tif (ff_effects_max > 0)\n\t\tmode = O_RDWR | O_NONBLOCK;\n\tfd = open(\"/dev/uinput\", mode);\n\tif (fd < 0)\n\t\treturn -1;\n\n\tstrncpy(uidev.name, name, UINPUT_MAX_NAME_SIZE);\n\tuidev.id.bustype = BUS_USB;\n\tuidev.id.vendor = vendor;\n\tuidev.id.product = product;\n\tuidev.id.version = version;\n\tuidev.ff_effects_max = 1;\n\n\t/* Key Event initialisation */\n\tif (key_len > 0 && ioctl(fd, UI_SET_EVBIT, EV_KEY) < 0) {\n\t\tclose(fd);\n\t\treturn -2;\n\t}\n\n\tfor (i = 0; i < key_len; i++) {\n\t\tif (ioctl(fd, UI_SET_KEYBIT, key[i]) < 0) {\n\t\t\tclose(fd);\n\t\t\treturn -3;\n\t\t}\n\t}\n\n\t/* Absolute Event initialisation */\n\tif (abs_len > 0 && ioctl(fd, UI_SET_EVBIT, EV_ABS) < 0) {\n\t\tclose(fd);\n\t\treturn -4;\n\t}\n\n\tfor (i = 0; i < abs_len; i++) {\n\n\t\tif (ioctl(fd, UI_SET_ABSBIT, abs[i]) < 0) {\n\t\t\tclose(fd);\n\t\t\treturn -5;\n\t\t}\n\t\tuidev.absmin[abs[i]] = abs_min[i];\n\t\tuidev.absmax[abs[i]] = abs_max[i];\n\t\tuidev.absfuzz[abs[i]] = abs_fuzz[i];\n\t\tuidev.absflat[abs[i]] = abs_flat[i];\n\t}\n\n\t/* Relative Event initialisation */\n\tif (rel_len > 0 && ioctl(fd, UI_SET_EVBIT, EV_REL) < 0) {\n\t\tclose(fd);\n\t\treturn -6;\n\t}\n\n\tfor (i = 0; i < rel_len; i++) {\n\t\tif (ioctl(fd, UI_SET_RELBIT, rel[i]) < 0) {\n\t\t\tclose(fd);\n\t\t\treturn -7;\n\t\t}\n\t}\n\n\tif (keyboard) {\n\t\tif (ioctl(fd, UI_SET_EVBIT, EV_MSC) < 0) {\n\t\t\tclose(fd);\n\t\t\treturn -8;\n\t\t}\n\t\tif (ioctl(fd, UI_SET_MSCBIT, MSC_SCAN) < 0) {\n\t\t\tclose(fd);\n\t\t\treturn -9;\n\t\t}\n\t\tif (ioctl(fd, UI_SET_EVBIT,  EV_REP) < 0) {\n\t\t\tclose(fd);\n\t\t\treturn -10;\n\t\t}\n\t}\n\t\n\t/* rumble initialisation */\n\tif (ff_effects_max > 0) {\n\t\tif (ioctl (fd, UI_SET_EVBIT, EV_FF) < 0) return -13;\n\t\tif (ioctl (fd, UI_SET_FFBIT, FF_RUMBLE) < 0) return -13;\n\t\tif (ioctl (fd, UI_SET_FFBIT, FF_PERIODIC) < 0) return -13;\n\t\tif (ioctl (fd, UI_SET_FFBIT, FF_SQUARE) < 0) return -13;\n\t\tif (ioctl (fd, UI_SET_FFBIT, FF_TRIANGLE) < 0) return -13;\n\t\tif (ioctl (fd, UI_SET_FFBIT, FF_SINE) < 0) return -13;\n\t\tif (ioctl (fd, UI_SET_FFBIT, FF_GAIN) < 0) return -13;\n\t\t\n\t\tuidev.ff_effects_max = ff_effects_max;\n\t}\n\n\t/* submit the uidev */\n\tif (write(fd, &uidev, sizeof(uidev)) < 0) {\n\t\tclose(fd);\n\t\treturn -11;\n\t}\n\n\t/* create the device */\n\tif (ioctl(fd, UI_DEV_CREATE) < 0) {\n\t\tclose(fd);\n\t\treturn -12;\n\t}\n\n\treturn fd;\n}\n\nconst int uinput_module_version(void) {\n\treturn UNPUT_MODULE_VERSION;\n}\n\nvoid uinput_key(int fd, __u16 key, __s32 val)\n{\n\tstruct input_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\tev.type = EV_KEY;\n\tev.code = key;\n\tev.value = val;\n\twrite(fd, &ev, sizeof(ev));\n}\n\nvoid uinput_abs(int fd, __u16 abs, __s32 val)\n{\n\tstruct input_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\tev.type = EV_ABS;\n\tev.code = abs;\n\tev.value = val;\n\twrite(fd, &ev, sizeof(ev));\n}\n\nvoid uinput_rel(int fd, __u16 rel, __s32 val)\n{\n\tstruct input_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\tev.type = EV_REL;\n\tev.code = rel;\n\tev.value = val;\n\twrite(fd, &ev, sizeof(ev));\n}\n\nvoid uinput_scan(int fd, __s32 val)\n{\n\tstruct input_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\tev.type = EV_MSC;\n\tev.code = MSC_SCAN;\n\tev.value = val;\n\twrite(fd, &ev, sizeof(ev));\n}\n\nvoid uinput_set_delay_period(int fd, __s32 delay, __s32 period)\n{\n\tstruct input_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\tev.type = EV_REP;\n\tev.code = REP_DELAY;\n\tev.value = delay;\n\twrite(fd, &ev, sizeof(ev));\n\tev.code = REP_PERIOD;\n\tev.value = period;\n\twrite(fd, &ev, sizeof(ev));\n}\n\nvoid uinput_syn(int fd)\n{\n\tstruct input_event ev;\n\n\tmemset(&ev, 0, sizeof(ev));\n\tev.type = EV_SYN;\n\tev.code = SYN_REPORT;\n\tev.value = 0;\n\twrite(fd, &ev, sizeof(ev));\n}\n\n// #define RUMBLE_DEBUG(...) do { printf(__VA_ARGS__); } while (0)\n#define RUMBLE_DEBUG(...) do { } while (0)\n\nint uinput_ff_read(int fd, int ff_effects_max, struct feedback_effect** ff_effects) {\n\tstatic struct uinput_ff_upload upload;\n\tstatic struct uinput_ff_erase erase;\n\tstatic struct input_event event;\n\tint n = read(fd, &event, sizeof(event));\n\tint rv = -1;\n\tint eid;\n\tif (n == sizeof(struct input_event)) {\n\t\tswitch (event.type) {\n\t\t\tcase EV_UINPUT:\n\t\t\t\tswitch (event.code) {\n\t\t\t\t\tcase UI_FF_UPLOAD:\n\t\t\t\t\t\tmemset(&upload, 0, sizeof(struct uinput_ff_upload));\n\t\t\t\t\t\tupload.request_id = event.value;\n\t\t\t\t\t\tioctl(fd, UI_BEGIN_FF_UPLOAD, &upload);\n\t\t\t\t\t\t\n\t\t\t\t\t\tupload.effect.id = -1;\n\t\t\t\t\t\tif ((upload.old.type != 0) && (upload.old.id >= 0) && (upload.old.id < ff_effects_max) && (ff_effects[upload.old.id]->in_use)) {\n\t\t\t\t\t\t\t// Updating old effect\n\t\t\t\t\t\t\teid = upload.effect.id = upload.old.id;\n\t\t\t\t\t\t\tff_effects[eid]->in_use = true;\n\t\t\t\t\t\t\tRUMBLE_DEBUG(\"Updated effect id %i\\n\", upload.effect.id);\n\t\t\t\t\t\t} else if (upload.old.type == 0) {\n\t\t\t\t\t\t\t// Generating new effect\n\t\t\t\t\t\t\tfor (eid=0; eid<ff_effects_max; eid++) {\n\t\t\t\t\t\t\t\tif (!ff_effects[eid]->in_use) {\n\t\t\t\t\t\t\t\t\tff_effects[eid]->in_use = true;\n\t\t\t\t\t\t\t\t\tupload.effect.id = eid;\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"Generated new effect id %i\\n\", upload.effect.id);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (upload.effect.id >= 0) {\n\t\t\t\t\t\t\tint32_t avg;\n\t\t\t\t\t\t\teid = upload.effect.id;\n\t\t\t\t\t\t\tff_effects[eid]->duration = upload.effect.replay.length;\n\t\t\t\t\t\t\tff_effects[eid]->delay = upload.effect.replay.delay;\n\t\t\t\t\t\t\tff_effects[eid]->repetitions = 0;\n\t\t\t\t\t\t\tff_effects[eid]->type = upload.effect.type;\n\t\t\t\t\t\t\tff_effects[eid]->level = 0x4FFF;\n\t\t\t\t\t\t\t// This part converts all possible event types to one that\n\t\t\t\t\t\t\t// SCC and controller really supports. Only output level is used.\n\t\t\t\t\t\t\tswitch (upload.effect.type) {\n\t\t\t\t\t\t\t\tcase FF_CONSTANT:\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = upload.effect.u.constant.level;\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_CONSTANT [%i] %i\\n\", eid, ff_effects[eid]->level);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_PERIODIC:\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_PERIODIC [%i] %i %i %i %i %i length %i\\n\",\n\t\t\t\t\t\t\t\t\t\teid,\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.periodic.waveform,\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.periodic.period,\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.periodic.magnitude,\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.periodic.offset,\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.periodic.phase,\n\t\t\t\t\t\t\t\t\t\tff_effects[eid]->duration\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = upload.effect.u.periodic.magnitude;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_RAMP:\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = upload.effect.u.ramp.start_level;\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_RAMP [%i] %i\\n\", eid, ff_effects[eid]->level);\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_RUMBLE:\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_RUMBLE [%i] %i %i\\n\",\n\t\t\t\t\t\t\t\t\t\teid,\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.rumble.strong_magnitude,\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.rumble.weak_magnitude\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tavg = upload.effect.u.rumble.strong_magnitude / 3 +\n\t\t\t\t\t\t\t\t\t\tupload.effect.u.rumble.weak_magnitude / 6;\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = (int32_t)MIN(avg, 0x7FFF);\n\t\t\t\t\t\t\t\t\tif (ff_effects[eid]->continuous_rumble) {\n\t\t\t\t\t\t\t\t\t\t// See comment in case EV_FF: block\n\t\t\t\t\t\t\t\t\t\tff_effects[eid]->duration = INFINITE_RUMBLE;\n\t\t\t\t\t\t\t\t\t\tff_effects[eid]->repetitions = 1;\n\t\t\t\t\t\t\t\t\t\trv = eid;\n\t\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_PLAY_RUMBLE -> %i\\n\", eid);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_FRICTION:\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_FRICTION [%i] \\n\", eid);\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = 0x7FFF;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_DAMPER:\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_DAMPER [%i] \\n\", eid);\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = 0x7FFF;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_INERTIA:\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_INERTIA [%i] \\n\", eid);\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = 0x7FFF;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_SPRING:\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_SPRING [%i] \\n\", eid);\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = 0x7FFF;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\tcase FF_CUSTOM:\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_CUSTOM [%i] \\n\", eid);\n\t\t\t\t\t\t\t\t\tff_effects[eid]->level = 0x7FFF;\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tupload.retval = 0;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Upload failed\n\t\t\t\t\t\t\tRUMBLE_DEBUG(\"Cannot create more effects!\\n\");\n\t\t\t\t\t\t\tupload.retval = -1;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tioctl(fd, UI_END_FF_UPLOAD, &upload);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase UI_FF_ERASE:\n\t\t\t\t\t\tmemset(&erase, 0, sizeof(struct uinput_ff_erase));\n\t\t\t\t\t\terase.request_id = event.value;\n\t\t\t\t\t\tioctl(fd, UI_BEGIN_FF_ERASE, &erase);\n\t\t\t\t\t\tif ((erase.effect_id >= 0) && (erase.effect_id < ff_effects_max)) {\n\t\t\t\t\t\t\tff_effects[erase.effect_id]->in_use = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tRUMBLE_DEBUG(\"Erased effect id %i\\n\", upload.effect.id);\n\t\t\t\t\t\tioctl(fd, UI_END_FF_ERASE, &erase);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase EV_FF:\n\t\t\t\tswitch (event.code) {\n\t\t\t\t\tcase FF_GAIN:\n\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_GAIN\\n\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase FF_AUTOCENTER:\n\t\t\t\t\t\t// TODO: Maybe support theese?\n\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_AUTOCENTER\\n\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tif ((event.code >= 0) && (event.code < ff_effects_max)) {\n\t\t\t\t\t\t\tif (ff_effects[event.code]->in_use) {\n\t\t\t\t\t\t\t\trv = event.code;\n\t\t\t\t\t\t\t\tff_effects[rv]->repetitions = event.value;\n\t\t\t\t\t\t\t\tif ( (ff_effects[rv]->type == FF_RUMBLE) && (ff_effects[rv]->duration == 0) ) {\n\t\t\t\t\t\t\t\t\t// With FF_RUMBLE, duration of zero means infinite duration\n\t\t\t\t\t\t\t\t\tff_effects[rv]->duration = INFINITE_RUMBLE;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( (ff_effects[rv]->type == FF_RUMBLE) && (event.value > 0) && ( (ff_effects[rv]->duration >= INFINITE_RUMBLE) || (event.value > 1) )) {\n\t\t\t\t\t\t\t\t\t// continuous_rumble is special kind of effect used by some games.\n\t\t\t\t\t\t\t\t\t// This event is \"played\" infinitelly, but with zero amplitide and whenever actual rumble is\n\t\t\t\t\t\t\t\t\t// supposed to play, effect properties are updated on the fly.\n\t\t\t\t\t\t\t\t\tif (!ff_effects[rv]->continuous_rumble) {\n\t\t\t\t\t\t\t\t\t\tff_effects[rv]->continuous_rumble = true;\n\t\t\t\t\t\t\t\t\t\tff_effects[rv]->repetitions = 1;\n\t\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"CONTINUOUS RUMBLE enabled on %i\\n\", rv);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (ff_effects[rv]->continuous_rumble) {\n\t\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"CONTINUOUS RUMBLE disabled on %i\\n\", rv);\n\t\t\t\t\t\t\t\t\tff_effects[rv]->continuous_rumble = false;\n\t\t\t\t\t\t\t\t\tff_effects[rv]->repetitions = 0;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF_PLAY -> %i (type %i, lvl %i, dur. %i, reps. %i)\\n\", event.code,\n\t\t\t\t\t\t\t\t\tff_effects[rv]->type, ff_effects[rv]->level, ff_effects[rv]->duration,\n\t\t\t\t\t\t\t\t\tff_effects[rv]->repetitions, event.value);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// SDL uses this to turn rumble off - fake event\n\t\t\t\t\t\t\t\t// is generated here to achieve same effect\n\t\t\t\t\t\t\t\trv = event.code;\n\t\t\t\t\t\t\t\tff_effects[rv]->level = 0;\n\t\t\t\t\t\t\t\tff_effects[rv]->repetitions = 0;\n\t\t\t\t\t\t\t\tff_effects[rv]->duration = 0;\n\t\t\t\t\t\t\t\tRUMBLE_DEBUG(\"FF NOT IN USE! %i\\n\", event.code);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn rv;\n}\n\nvoid uinput_destroy(int fd)\n{\n\tioctl(fd, UI_DEV_DESTROY);\n\tclose(fd);\n}\n"
  },
  {
    "path": "scc/uinput.py",
    "content": "#!/usr/bin/env python2\n\n# The MIT License (MIT)\n#\n# Copyright (c) 2015 Stany MARCEL <stanypub@gmail.com>\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n#\n# all copies or substantial portions of the Software.\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\nimport os, ctypes, time\nfrom ctypes import Structure, POINTER, c_bool, c_int16, c_uint16, c_int32, byref\nfrom math import pi, copysign, sqrt, fmod\nfrom scc.lib.libusb1 import timeval\nfrom scc.tools import find_library\nfrom scc.cheader import defines\nfrom scc.lib import IntEnum\n\nUNPUT_MODULE_VERSION = 9\n\n# Get All defines from linux headers\nif os.path.exists('/usr/include/linux/input-event-codes.h'):\n\tCHEAD = defines('/usr/include', 'linux/input-event-codes.h')\nelif os.path.exists(os.path.split(__file__)[0] + '/input-event-codes.h'):\n\tCHEAD = defines(os.path.split(__file__)[0], 'input-event-codes.h')\nelse:\n\tCHEAD = defines('/usr/include', 'linux/input.h')\n\nMAX_FEEDBACK_EFFECTS = 4\n\n# Keys enum contains all keys and button from linux/uinput.h (KEY_* BTN_*)\nKeys = IntEnum('Keys', {i: CHEAD[i] for i in CHEAD.keys() if (i.startswith('KEY_') or\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\ti.startswith('BTN_'))})\n# Keys enum contains all keys and button from linux/uinput.h (KEY_* BTN_*)\nKeysOnly = IntEnum('KeysOnly', {i: CHEAD[i] for i in CHEAD.keys() if i.startswith('KEY_')})\n\n# Axes enum contains all axes from linux/uinput.h (ABS_*)\nAxes = IntEnum('Axes', {i: CHEAD[i] for i in CHEAD.keys() if i.startswith('ABS_')})\n\n# Rels enum contains all rels from linux/uinput.h (REL_*)\nRels = IntEnum('Rels', {i: CHEAD[i] for i in CHEAD.keys() if i.startswith('REL_')})\n\n# Scan codes for each keys (taken from a logitech keyboard)\nScans = {\n\tKeys.KEY_ESC: 0x70029,\n\tKeys.KEY_F1: 0x7003a,\n\tKeys.KEY_F2: 0x7003b,\n\tKeys.KEY_F3: 0x7003c,\n\tKeys.KEY_F4: 0x7003d,\n\tKeys.KEY_F5: 0x7003e,\n\tKeys.KEY_F6: 0x7003f,\n\tKeys.KEY_F7: 0x70040,\n\tKeys.KEY_F8: 0x70041,\n\tKeys.KEY_F9: 0x70042,\n\tKeys.KEY_F10: 0x70043,\n\tKeys.KEY_F11: 0x70044,\n\tKeys.KEY_F12: 0x70045,\n\tKeys.KEY_SYSRQ: 0x70046,\n\tKeys.KEY_SCROLLLOCK: 0x70047,\n\tKeys.KEY_PAUSE: 0x70048,\n\tKeys.KEY_GRAVE: 0x70035,\n\tKeys.KEY_1: 0x7001e,\n\tKeys.KEY_2: 0x7001f,\n\tKeys.KEY_3: 0x70020,\n\tKeys.KEY_4: 0x70021,\n\tKeys.KEY_5: 0x70022,\n\tKeys.KEY_6: 0x70023,\n\tKeys.KEY_7: 0x70024,\n\tKeys.KEY_8: 0x70025,\n\tKeys.KEY_9: 0x70026,\n\tKeys.KEY_0: 0x70027,\n\tKeys.KEY_MINUS: 0x7002d,\n\tKeys.KEY_EQUAL: 0x7002e,\n\tKeys.KEY_BACKSPACE: 0x7002a,\n\tKeys.KEY_TAB: 0x7002b,\n\tKeys.KEY_Q: 0x70014,\n\tKeys.KEY_W: 0x7001a,\n\tKeys.KEY_E: 0x70008,\n\tKeys.KEY_R: 0x70015,\n\tKeys.KEY_T: 0x70017,\n\tKeys.KEY_Y: 0x7001c,\n\tKeys.KEY_U: 0x70018,\n\tKeys.KEY_I: 0x7000c,\n\tKeys.KEY_O: 0x70012,\n\tKeys.KEY_P: 0x70013,\n\tKeys.KEY_LEFTBRACE: 0x7002f,\n\tKeys.KEY_RIGHTBRACE: 0x70030,\n\tKeys.KEY_ENTER: 0x70028,\n\tKeys.KEY_CAPSLOCK: 0x70039,\n\tKeys.KEY_A: 0x70004,\n\tKeys.KEY_S: 0x70016,\n\tKeys.KEY_D: 0x70007,\n\tKeys.KEY_F: 0x70009,\n\tKeys.KEY_G: 0x7000a,\n\tKeys.KEY_H: 0x7000b,\n\tKeys.KEY_J: 0x7000d,\n\tKeys.KEY_K: 0x7000e,\n\tKeys.KEY_L: 0x7000f,\n\tKeys.KEY_SEMICOLON: 0x70033,\n\tKeys.KEY_APOSTROPHE: 0x70034,\n\tKeys.KEY_BACKSLASH: 0x70032,\n\tKeys.KEY_LEFTSHIFT: 0x700e1,\n\tKeys.KEY_102ND: 0x70064,\n\tKeys.KEY_Z: 0x7001d,\n\tKeys.KEY_X: 0x7001b,\n\tKeys.KEY_C: 0x70006,\n\tKeys.KEY_V: 0x70019,\n\tKeys.KEY_B: 0x70005,\n\tKeys.KEY_N: 0x70011,\n\tKeys.KEY_M: 0x70010,\n\tKeys.KEY_COMMA: 0x70036,\n\tKeys.KEY_DOT: 0x70037,\n\tKeys.KEY_SLASH: 0x70038,\n\tKeys.KEY_RIGHTSHIFT: 0x700e5,\n\tKeys.KEY_LEFTCTRL: 0x700e0,\n\tKeys.KEY_LEFTMETA: 0x700e3,\n\tKeys.KEY_LEFTALT: 0x700e2,\n\tKeys.KEY_SPACE: 0x7002c,\n\tKeys.KEY_RIGHTALT: 0x700e6,\n\tKeys.KEY_RIGHTMETA: 0x700e7,\n\tKeys.KEY_COMPOSE: 0x70065,\n\tKeys.KEY_RIGHTCTRL: 0x700e4,\n\tKeys.KEY_INSERT: 0x70049,\n\tKeys.KEY_HOME: 0x7004a,\n\tKeys.KEY_PAGEUP: 0x7004b,\n\tKeys.KEY_DELETE: 0x7004c,\n\tKeys.KEY_END: 0x7004d,\n\tKeys.KEY_PAGEDOWN: 0x7004e,\n\tKeys.KEY_UP: 0x70052,\n\tKeys.KEY_LEFT: 0x70050,\n\tKeys.KEY_DOWN: 0x70051,\n\tKeys.KEY_RIGHT: 0x7004f,\n\tKeys.KEY_NUMLOCK: 0x70053,\n\tKeys.KEY_KPSLASH: 0x70054,\n\tKeys.KEY_KPASTERISK: 0x70055,\n\tKeys.KEY_KPMINUS: 0x70056,\n\tKeys.KEY_KP7: 0x7005f,\n\tKeys.KEY_KP8: 0x70060,\n\tKeys.KEY_KP9: 0x70061,\n\tKeys.KEY_KPPLUS: 0x70057,\n\tKeys.KEY_KP4: 0x7005c,\n\tKeys.KEY_KP5: 0x7005d,\n\tKeys.KEY_KP6: 0x7005e,\n\tKeys.KEY_KP1: 0x70059,\n\tKeys.KEY_KP2: 0x7005a,\n\tKeys.KEY_KP3: 0x7005b,\n\tKeys.KEY_KPENTER: 0x70058,\n\tKeys.KEY_KP0: 0x70062,\n\tKeys.KEY_KPDOT: 0x70063,\n\tKeys.KEY_CONFIG: 0xc0183,\n\tKeys.KEY_PLAYPAUSE: 0xc00cd,\n\tKeys.KEY_MUTE: 0xc00e2,\n\tKeys.KEY_VOLUMEDOWN: 0xc00ea,\n\tKeys.KEY_VOLUMEUP: 0xc00e9,\n\tKeys.KEY_HOMEPAGE: 0xc0223,\n\n\tKeys.KEY_PREVIOUSSONG: 0xc00f0,\n\tKeys.KEY_NEXTSONG: 0xc00f1,\n\n\tKeys.KEY_BACK: 0xc00f2,\n\tKeys.KEY_FORWARD: 0xc00f3,\n}\n\nclass InputEvent(ctypes.Structure):\n\t_fields_ = [\n\t\t('time', timeval),\n\t\t('type', c_uint16),\n\t\t('code', c_uint16),\n\t\t('value', c_int32)\n\t]\n\nclass FeedbackEvent(ctypes.Structure):\n\t_fields_ = [\n\t\t('in_use', c_bool),\n\t\t('continuous_rumble', c_bool),\n\t\t('duration', c_int32),\n\t\t('delay', c_int32),\n\t\t('repetitions', c_int32),\n\t\t('type', c_uint16),\n\t\t('level', c_int16),\n\t]\n\n\tdef __init__(self):\n\t\tself.in_use = False\n\n\nclass UInput(object):\n\t\"\"\"\n\tUInput class permits to create a uinput device.\n\n\tSee Gamepad, Mouse, Keyboard for examples\n\t\"\"\"\n\n\n\tdef __init__(self, vendor, product, version, name, keys, axes, rels, keyboard=False, rumble=False):\n\t\tself._lib = None\n\t\tself._k = keys\n\t\tself.name = name\n\t\tif not axes or len(axes) == 0:\n\t\t\tself._a, self._amin, self._amax, self._afuzz, self._aflat = [[]] * 5\n\t\telse:\n\t\t\tself._a, self._amin, self._amax, self._afuzz, self._aflat = zip(*axes)\n\n\t\tself._r = rels\n\n\t\tself._lib = find_library(\"libuinput\")\n\t\tself._ff_events = None\n\t\tif rumble:\n\t\t\tself._ff_events = (POINTER(FeedbackEvent) * MAX_FEEDBACK_EFFECTS)()\n\t\t\tfor i in range(MAX_FEEDBACK_EFFECTS):\n\t\t\t\tself._ff_events[i].contents = FeedbackEvent()\n\n\t\ttry:\n\t\t\tif self._lib.uinput_module_version() != UNPUT_MODULE_VERSION:\n\t\t\t\traise Exception()\n\t\texcept:\n\t\t\timport sys\n\t\t\tprint(\"Invalid native module version. Please, recompile 'libuinput.so'\", file=sys.stderr)\n\t\t\tprint(\"If you are running sc-controller from source, you can do this by removing 'build' directory\", file=sys.stderr)\n\t\t\tprint(\"and runinng 'python setup.py build' or 'run.sh' script\", file=sys.stderr)\n\t\t\traise Exception(\"Invalid native module version\")\n\n\t\tc_k\t\t= (ctypes.c_uint16 * len(self._k))(*self._k)\n\t\tc_a\t\t= (ctypes.c_uint16 * len(self._a))(*self._a)\n\t\tc_amin\t = (ctypes.c_int32  * len(self._amin ))(*self._amin )\n\t\tc_amax\t = (ctypes.c_int32  * len(self._amax ))(*self._amax )\n\t\tc_afuzz\t= (ctypes.c_int32  * len(self._afuzz))(*self._afuzz)\n\t\tc_aflat\t= (ctypes.c_int32  * len(self._aflat))(*self._aflat)\n\t\tc_r\t\t= (ctypes.c_uint16 * len(self._r))(*self._r)\n\t\tc_vendor   = ctypes.c_uint16(vendor)\n\t\tc_product  = ctypes.c_uint16(product)\n\t\tc_version  = ctypes.c_uint16(version)\n\t\tc_keyboard = ctypes.c_int(keyboard)\n\t\tc_rumble = ctypes.c_int(MAX_FEEDBACK_EFFECTS if rumble else 0)\n\t\tc_name = ctypes.c_char_p(name.encode(\"utf-8\") if type(name) is str else name)\n\n\t\tself._fd = self._lib.uinput_init(ctypes.c_int(len(self._k)),\n\t\t\t\t\t\t\t\t\t\t c_k,\n\t\t\t\t\t\t\t\t\t\t ctypes.c_int(len(self._a)),\n\t\t\t\t\t\t\t\t\t\t c_a,\n\t\t\t\t\t\t\t\t\t\t c_amin,\n\t\t\t\t\t\t\t\t\t\t c_amax,\n\t\t\t\t\t\t\t\t\t\t c_afuzz,\n\t\t\t\t\t\t\t\t\t\t c_aflat,\n\t\t\t\t\t\t\t\t\t\t ctypes.c_int(len(self._r)),\n\t\t\t\t\t\t\t\t\t\t c_r,\n\t\t\t\t\t\t\t\t\t\t c_keyboard,\n\t\t\t\t\t\t\t\t\t\t c_vendor,\n\t\t\t\t\t\t\t\t\t\t c_product,\n\t\t\t\t\t\t\t\t\t\t c_version,\n\t\t\t\t\t\t\t\t\t\t c_rumble,\n\t\t\t\t\t\t\t\t\t\t c_name)\n\t\tif self._fd < 0:\n\t\t\traise CannotCreateUInputException(\"Failed to create uinput device. Error code: %s\" % (self._fd,))\n\n\n\tdef getDescriptor(self):\n\t\treturn self._fd\n\n\n\tdef keyEvent(self, key, val):\n\t\t\"\"\"\n\t\tGenerate a key or btn event\n\n\t\t@param int axis\t\t key or btn event (KEY_* or BTN_*)\n\t\t@param int val\t\t  event value\n\t\t\"\"\"\n\t\tself._lib.uinput_key(self._fd,\n\t\t\t\t\t\t\t ctypes.c_uint16(key),\n\t\t\t\t\t\t\t ctypes.c_int32(val))\n\n\n\tdef axisEvent(self, axis, val):\n\t\t\"\"\"\n\t\tGenerate a abs event (joystick/pad axes)\n\n\t\t@param int axis\t\t abs event (ABS_*)\n\t\t@param int val\t\t  event value\n\t\t\"\"\"\n\t\tself._lib.uinput_abs(self._fd,\n\t\t\t\t\t\t\t ctypes.c_uint16(axis),\n\t\t\t\t\t\t\t ctypes.c_int32(val))\n\n\tdef relEvent(self, rel, val):\n\t\t\"\"\"\n\t\tGenerate a rel event (move move)\n\n\t\t@param int rel\t\t  rel event (REL_*)\n\t\t@param int val\t\t  event value\n\t\t\"\"\"\n\t\tself._lib.uinput_rel(self._fd,\n\t\t\t\t\t\t\t ctypes.c_uint16(rel),\n\t\t\t\t\t\t\t ctypes.c_int32(val))\n\n\tdef scanEvent(self, val):\n\t\t\"\"\"\n\t\tGenerate a scan event (MSC_SCAN)\n\n\t\t@param int val\t\t  scan event value (scancode)\n\t\t\"\"\"\n\t\tself._lib.uinput_scan(self._fd,\n\t\t\t\t\t\t\t  ctypes.c_int32(val))\n\n\tdef synEvent(self):\n\t\t\"\"\"\n\t\tGenerate a syn event\n\t\t\"\"\"\n\t\tself._lib.uinput_syn(self._fd)\n\n\n\tdef setDelayPeriod(self, delay, period):\n\t\t\"\"\"\n\t\tUpdate delay period values for keyboard\n\n\t\t@param int delay\t\tdelay in ms\n\t\t@param int period\t   period is ms\n\t\t\"\"\"\n\n\t\tself._lib.uinput_set_delay_period(self._fd,\n\t\t\t\t\t\t\t\t\t\t  ctypes.c_int32(delay),\n\t\t\t\t\t\t\t\t\t\t  ctypes.c_int32(period))\n\n\tdef keyManaged(self, ev):\n\t\treturn ev in self._k\n\n\tdef axisManaged(self, ev):\n\t\treturn ev in self._a\n\n\tdef relManaged(self, ev):\n\t\treturn ev in self._r\n\n\tdef ff_read(self):\n\t\t\"\"\"\n\t\tReturns effect that should be played or None if there were no such request.\n\t\t\"\"\"\n\t\tif self._ff_events:\n\t\t\tid = self._lib.uinput_ff_read(self._fd, MAX_FEEDBACK_EFFECTS, byref(self._ff_events))\n\t\t\tif id >= 0:\n\t\t\t\treturn self._ff_events[id].contents\n\t\treturn None\n\n\tdef __del__(self):\n\t\tif self._lib:\n\t\t\tself._lib.uinput_destroy(self._fd)\n\n\nclass Gamepad(UInput):\n\t\"\"\"\n\tGamepad uinput class, create a Xbox360 gamepad device\n\t\"\"\"\n\n\tdef __init__(self, name):\n\t\tsuper(Gamepad, self).__init__(vendor=0x045e,\n\t\t\t\t\t\t\t\t\t  product=0x028e,\n\t\t\t\t\t\t\t\t\t  version=1,\n\t\t\t\t\t\t\t\t\t  name=name,\n\t\t\t\t\t\t\t\t\t  keys=[Keys.BTN_START,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_MODE,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_SELECT,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_A,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_B,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_X,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_Y,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_TL,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_TR,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_THUMBL,\n\t\t\t\t\t\t\t\t\t\t\tKeys.BTN_THUMBR],\n\t\t\t\t\t\t\t\t\t  axes=[(Axes.ABS_X, -32768, 32767, 16, 128),\n\t\t\t\t\t\t\t\t\t\t\t(Axes.ABS_Y, -32768, 32767, 16, 128),\n\t\t\t\t\t\t\t\t\t\t\t(Axes.ABS_RX, -32768, 32767, 16, 128),\n\t\t\t\t\t\t\t\t\t\t\t(Axes.ABS_RY, -32768, 32767, 16, 128),\n\t\t\t\t\t\t\t\t\t\t\t(Axes.ABS_Z, 0, 255, 0, 0),\n\t\t\t\t\t\t\t\t\t\t\t(Axes.ABS_RZ, 0, 255, 0, 0),\n\t\t\t\t\t\t\t\t\t\t\t(Axes.ABS_HAT0X, -1, 1, 0, 0),\n\t\t\t\t\t\t\t\t\t\t\t(Axes.ABS_HAT0Y, -1, 1, 0, 0)],\n\t\t\t\t\t\t\t\t\t  rels=[])\n\n\nclass Mouse(UInput):\n\n\t\"\"\"\n\tMouse uinput class, create a mouse device\n\n\tmoveEvent can emulate free ball rotation of a track ball\n\tupdateParams permit to upgrade ball model and move scale\n\t\"\"\"\n\n\tDEFAULT_XSCALE = 0.006\n\tDEFAULT_YSCALE = 0.006\n\n\tDEFAULT_SCR_XSCALE = 0.0005\n\tDEFAULT_SCR_YSCALE = 0.0005\n\n\tdef __init__(self, name):\n\t\tsuper(Mouse, self).__init__(vendor=0x28de,\n\t\t\t\t\t\t\t\t\tproduct=0x1142,\n\t\t\t\t\t\t\t\t\tversion=1,\n\t\t\t\t\t\t\t\t\tname=name,\n\t\t\t\t\t\t\t\t\tkeys=[Keys.BTN_LEFT,\n\t\t\t\t\t\t\t\t\t\t  Keys.BTN_RIGHT,\n\t\t\t\t\t\t\t\t\t\t  Keys.BTN_MIDDLE,\n\t\t\t\t\t\t\t\t\t\t  Keys.BTN_SIDE,\n\t\t\t\t\t\t\t\t\t\t  Keys.BTN_EXTRA],\n\t\t\t\t\t\t\t\t\taxes=[],\n\t\t\t\t\t\t\t\t\trels=[Rels.REL_X,\n\t\t\t\t\t\t\t\t\t\t  Rels.REL_Y,\n\t\t\t\t\t\t\t\t\t\t  Rels.REL_WHEEL,\n\t\t\t\t\t\t\t\t\t\t  Rels.REL_HWHEEL])\n\t\tself.updateParams()\n\t\tself.updateScrollParams()\n\t\tself.reset()\n\n\tdef reset(self):\n\t\t\"\"\"\n\t\tResets internal counters, especially one used for wheel.\n\t\tFixes scroll wheel feedback desynchronisation, as reported\n\t\tin https://github.com/kozec/sc-controller/issues/222\n\t\t\"\"\"\n\t\tself._scr_dx = 0.0\n\t\tself._scr_dy = 0.0\n\t\tself._dx = 0.0\n\t\tself._dy = 0.0\n\n\tdef updateParams(self,\n\t\t\t\t\t xscale=DEFAULT_XSCALE,\n\t\t\t\t\t yscale=DEFAULT_YSCALE):\n\t\t\"\"\"\n\t\tUpdate Movement parameters\n\n\t\t@param float mass\t   mass in g of the ball\n\t\t@param float r\t\t  radius in m of the ball\n\t\t@param int ampli\t\tinteger amplitude for move from border to border\n\t\t@param float degree\t degree of rotation of the ball for move from border to border\n\t\t@param float xscale\t scale applied on move param to input event on x axis\n\t\t@param float yscale\t scale applied on move param to input event on y axis\n\t\t\"\"\"\n\t\tself._xscale = xscale\n\t\tself._yscale = yscale\n\n\tdef updateScrollParams(self,\n\t\t\t\t\t\t   xscale=DEFAULT_SCR_XSCALE,\n\t\t\t\t\t\t   yscale=DEFAULT_SCR_YSCALE):\n\t\t\"\"\"\n\t\tUpdate Scroll parameters\n\n\t\t@param float mass\t   mass in g of the ball\n\t\t@param float r\t\t  radius in m of the ball\n\t\t@param float friction   constat friction force applied to the ball\n\t\t@param int ampli\t\tinteger amplitude for move from border to border\n\t\t@param float degree\t degree of rotation of the ball for move from border to border\n\t\t@param float xscale\t scale applied on move param to input event on x axis\n\t\t@param float yscale\t scale applied on move param to input event on y axis\n\t\t\"\"\"\n\t\tself._scr_xscale = xscale\n\t\tself._scr_yscale = yscale\n\n\tdef moveEvent(self, dx=0, dy=0, time_elapsed=0.0):\n\t\t\"\"\"\n\t\tGenerate move events from parametters and displacement\n\n\t\t@param int dx\t\t   delta movement from last call on x axis\n\t\t@param int dy\t\t   delta movement from last call on y axis\n\n\t\t\"\"\"\n\t\t_syn = False\n\n\t\t# Clear mouse axis remainders if axis direction has changed\n\t\tif (dx == 0 or ((dx > 0) != (self._dx > 0))):\n\t\t\tself._dx = 0\n\n\t\t# Clear mouse axis remainders if axis direction has changed\n\t\tif (dy == 0 or ((dy > 0) != (self._dy > 0))):\n\t\t\tself._dy = 0\n\n\t\t# Base speed around 8 ms standard\n\t\t# (base USB poll rate for Steam Controller)\n\t\tbaseFactor = (time_elapsed * 125.0)\n\t\tself._dx += dx * self._xscale * baseFactor\n\t\tself._dy += dy * self._yscale * baseFactor\n\t\t#self._factorDeadzone(dx, dy, time_elapsed)\n\n\t\tif int(self._dx):\n\t\t\tself._dx = self._dx - (fmod(self._dx * 100.0, 1.0) / 100.0)\n\t\t\tself.relEvent(rel=Rels.REL_X, val=int(self._dx))\n\t\t\tself._dx -= int(self._dx)\n\t\t\t_syn = True\n\t\tif int(self._dy):\n\t\t\tself._dy = self._dy - (fmod(self._dy * 100.0, 1.0) / 100.0)\n\t\t\tself.relEvent(rel=Rels.REL_Y, val=int(self._dy))\n\t\t\tself._dy -= int(self._dy)\n\t\t\t_syn = True\n\t\tif _syn:\n\t\t\tself.synEvent()\n\n\tdef moveStickEvent(self, dx=0.0, dy=0.0, time_elapsed=0.0):\n\t\t\"\"\"\n\t\tGenerate move events from parametters and displacement\n\n\t\t@param float dx\t\t   delta movement from last call on x axis\n\t\t@param float dy\t\t   delta movement from last call on y axis\n\n\t\t\"\"\"\n\t\t_syn = False\n\n\t\t# Clear mouse axis remainders if axis direction has changed\n\t\tif (dx == 0 or ((dx > 0) != (self._dx > 0))):\n\t\t\tself._dx = 0\n\n\t\t# Clear mouse axis remainders if axis direction has changed\n\t\tif (dy == 0 or ((dy > 0) != (self._dy > 0))):\n\t\t\tself._dy = 0\n\n\t\tself._dx += dx\n\t\tself._dy += dy\n\t\t#self._factorDeadzone(dx, dy, time_elapsed)\n\n\t\tif int(self._dx):\n\t\t\tself._dx = self._dx - (fmod(self._dx * 100.0, 1.0) / 100.0)\n\t\t\tself.relEvent(rel=Rels.REL_X, val=int(self._dx))\n\t\t\tself._dx -= int(self._dx)\n\t\t\t_syn = True\n\t\tif int(self._dy):\n\t\t\tself._dy = self._dy - (fmod(self._dy * 100.0, 1.0) / 100.0)\n\t\t\tself.relEvent(rel=Rels.REL_Y, val=int(self._dy))\n\t\t\tself._dy -= int(self._dy)\n\t\t\t_syn = True\n\t\tif _syn:\n\t\t\tself.synEvent()\n\n\tdef clearRemainders(self):\n\t\tself._dx = 0\n\t\tself._dy = 0\n\n\tdef _factorDeadzone(self, dx, dy, time_elapsed):\n\t\t\"\"\"\n\t\tTake raw event and adjust based on assigned dead zone setting.\n\n\t\t@param int dx\t\t\t\tdelta movement from last call on x axis\n\t\t@param int dy\t\t\t\tdelta movement from last call on y axis\n\t\t@param double time_elapsed\t\ttime elapsed in sec.\n\t\t\"\"\"\n\n\t\t#print(\"COMING IN {} {}\".format(dx, dy))\n\t\t#deadzonetmp = 15\n\t\tdeadzonetmp = 22\n\t\t#offset = 0.297477440456902\n\t\toffset = 0.375 #0.8 #0.6 #0.45\n\n\t\t#if (dx == 0 or ((dx > 0) != (self._dx > 0))):\n\t\t#\tself._dx = 0\n\n\t\t#if (dy == 0 or ((dy > 0) != (self._dy > 0))):\n\t\t#\tself._dy = 0\n\n\t\t_hyp = sqrt((dx**2) + (dy**2))\n\t\tunitx = 0\n\t\tunity = 0\n\t\tdeadzoneX = deadzonetmp\n\t\tdeadzoneY = deadzonetmp\n\t\tif _hyp != 0.0:\n\t\t\tunitx = (dx / _hyp)\n\t\t\tunity = (dy / _hyp)\n\t\t\tdeadzoneX = int(deadzonetmp * unitx)\n\t\t\tdeadzoneY = int(deadzonetmp * unity)\n\n\t\tif (abs(dx) > abs(deadzoneX)):\n\t\t\tbeforedx = dx\n\t\t\tdx -= copysign(deadzoneX, dx)\n\t\t\t#print(\"DX: {} {} {} {}\".format(beforedx, dx, deadzoneX, unitx))\n\t\telse:\n\t\t\tdx = 0\n\n\t\tif (abs(dy) > abs(deadzoneY)):\n\t\t\tbeforedy = dy\n\t\t\tdy -= copysign(deadzoneY, dy)\n\t\t\t#print(\"DY: {} {} {} {}\".format(beforedy, dy, deadzoneY, unity))\n\t\telse:\n\t\t\tdy = 0\n\n\t\t#throttla = 1.43\n\t\tthrottla = 1.428\n\t\toffman = 28 #30\n\t\ttempx = 0.0\n\t\ttempy = 0.0\n\t\t# Throttle low end of X axis movement\n\t\tif (dx != 0.0):\n\t\t\tif abs(dx) < (abs(unitx) * offman):\n\t\t\t\tsignx = copysign(1.0, dx)\n\t\t\t\tratioX = abs(dx) / offman\n\t\t\t\t#print(\"OLD {} | NEW {}\".format(tempx, tempx ** 1.5))\n\t\t\t\tdx = ratioX ** throttla * signx * offman\n\n\t\tif (dx != 0.0):\n\t\t\ttempx = dx * (time_elapsed * 125.0) * self._xscale + (abs(unitx) * copysign(offset, dx))\n\t\t\tself._dx += tempx\n\t\telse:\n\t\t\t#print(\"UP IN HERE {}\".format(self._dx))\n\t\t\tself._dx = 0\n\n\t\t# Throttle low end of Y axis movement\n\t\tif (dy != 0.0):\n\t\t\tif abs(dy) < (abs(unity) * offman):\n\t\t\t\tsigny = copysign(1.0, dy)\n\t\t\t\tratioY = abs(dy) / offman\n\t\t\t\t#print(\"OLD {} | NEW {}\".format(tempy, tempy ** 1.5))\n\t\t\t\tdy = ratioY ** throttla * signy * offman\n\n\t\tif (dy != 0.0):\n\t\t\ttempy = dy * (time_elapsed * 125.0) * self._yscale + (abs(unity) * copysign(offset, dy))\n\t\t\tself._dy += tempy\n\t\telse:\n\t\t\tself._dy = 0\n\n\tdef scrollEvent(self, dx=0, dy=0):\n\t\t\"\"\"\n\t\tGenerate scroll events from parametters and displacement\n\n\t\t@param int dx\t\t   delta movement from last call on x axis\n\t\t@param int dy\t\t   delta movement from last call on y axis\n\n\t\t@return float\t\t   absolute distance moved this tick\n\n\t\t\"\"\"\n\t\t# Compute mouse mouvement from interger part of d * scale\n\t\tself._scr_dx += dx * self._scr_xscale\n\t\tself._scr_dy += dy * self._scr_yscale\n\t\t_syn = False\n\t\tif int(self._scr_dx):\n\t\t\tself.relEvent(rel=Rels.REL_HWHEEL, val=int(copysign(1, self._scr_dx)))\n\t\t\tself._scr_dx -= int(self._scr_dx)\n\t\t\t_syn = True\n\t\tif int(self._scr_dy):\n\t\t\tself.relEvent(rel=Rels.REL_WHEEL,  val=int(copysign(1, self._scr_dy)))\n\t\t\tself._scr_dy -= int(self._scr_dy)\n\t\t\t_syn = True\n\t\tif _syn:\n\t\t\tself.synEvent()\n\n\nclass Keyboard(UInput):\n\t\"\"\"\n\tKeyboard uinput class, create a keyboard device.\n\n\tpressEvent permit to generate a key pressed and with scan events\n\treleaseEvent permit to generate a key released and with scan events\n\n\tautorepead delay and period are preset respectively to 250ms and 33ms\n\tsetDelayPeriod permits to update these values\n\t\"\"\"\n\n\tdef __init__(self, name):\n\t\tsuper(Keyboard, self).__init__(vendor=0x28de,\n\t\t\t\t\t\t\t\t\t   product=0x1142,\n\t\t\t\t\t\t\t\t\t   version=1,\n\t\t\t\t\t\t\t\t\t   name=name,\n\t\t\t\t\t\t\t\t\t   keys=Scans.keys(),\n\t\t\t\t\t\t\t\t\t   axes=[],\n\t\t\t\t\t\t\t\t\t   rels=[],\n\t\t\t\t\t\t\t\t\t   keyboard=True)\n\t\tself.setDelayPeriod(250, 33)\n\t\tself._dx = 0.0\n\t\tself._pressed = set()\n\n\tdef pressEvent(self, keys):\n\t\t\"\"\"\n\t\tGenerate key press event with corresponding scan codes.\n\t\tEvents are generated only for new keys.\n\n\t\t@param list of Keys keys\t\tkeys to press\n\t\t\"\"\"\n\n\t\tnew = [k for k in keys if k not in self._pressed]\n\t\tfor i in new:\n\t\t\tself.scanEvent(Scans[i])\n\t\t\tself.keyEvent(i, 1)\n\t\tif len(new):\n\t\t\tself.synEvent()\n\t\t\tself._pressed |= set(new)\n\n\tdef releaseEvent(self, keys=None):\n\t\t\"\"\"\n\t\tGenerate key release event with corresponding scan codes.\n\t\tEvents are generated only for keys that was pressed\n\n\n\t\t@param list of Keys keys\t\tkeys to release, give None or empty list\n\t\t\t\t\t\t\t\t\t\tto release all\n\t\t\"\"\"\n\t\tif keys and len(keys):\n\t\t\trem = [k for k in keys if k in self._pressed]\n\t\telse:\n\t\t\trem = list(self._pressed)\n\t\tfor i in rem:\n\t\t\tself.scanEvent(Scans[i])\n\t\t\tself.keyEvent(i, 0)\n\t\tif len(rem):\n\t\t\tself.synEvent()\n\t\t\tself._pressed -= set(rem)\n\n\nclass Dummy(object):\n\t\"\"\" Fake uinput device that does nothing, but has all required methods \"\"\"\n\tdef __init__(self, *a, **b):\n\t\tpass\n\n\tdef keyEvent(self, *a, **b):\n\t\tpass\n\n\taxisEvent = keyEvent\n\trelEvent = keyEvent\n\tscanEvent = keyEvent\n\tsynEvent = keyEvent\n\tsetDelayPeriod = keyEvent\n\tupdateParams = keyEvent\n\tupdateScrollParams = keyEvent\n\tmoveEvent = keyEvent\n\tscrollEvent = keyEvent\n\tpressEvent = keyEvent\n\treleaseEvent = keyEvent\n\treset = keyEvent\n\n\tdef keyManaged(self, ev):\n\t\treturn False\n\n\taxisManaged = keyManaged\n\trelManaged = keyManaged\n\n\nclass CannotCreateUInputException(Exception):\n\t# Special case when message should be displayed in UI\n\tpass\n"
  },
  {
    "path": "scc/x11/__init__.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - X11\n\nDaemon-related stuff that really needs X server to work.\n\"\"\"\n\n# there is also absolutely nothing usefull in this file..."
  },
  {
    "path": "scc/x11/autoswitcher.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Autoswitch Daemon\n\nObserves active window and commands scc-daemon to change profiles as needed.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _\n\nfrom scc.menu_data import MenuGenerator, MenuItem, Separator, MENU_GENERATORS\nfrom scc.special_actions import ChangeProfileAction\nfrom scc.parser import TalkingActionParser\nfrom scc.paths import get_daemon_socket\nfrom scc.lib import xwrappers as X\nfrom scc.tools import find_profile\nfrom scc.actions import Action\nfrom scc.mapper import Mapper\nfrom scc.config import Config\n\nimport os, sys, re, time, socket, traceback, threading, logging\nlog = logging.getLogger(\"AutoSwitcher\")\n\nclass AutoSwitcher(object):\n\tINTERVAL = 1\n\t\n\tdef __init__(self):\n\t\tself.dpy = X.open_display(os.environ[\"DISPLAY\"].encode(\"utf-8\"))\n\t\tself.lock = threading.Lock()\n\t\tself.thread = threading.Thread(target=self.connect_daemon)\n\t\tself.config = Config()\n\t\tself.mapper = Mapper(None, None, keyboard=None, mouse=None, gamepad=None)\n\t\tself.mapper.set_special_actions_handler(self)\n\t\tself.enabled = False\n\t\tself.socket = None\n\t\tself.connected = False\n\t\tself.exit_code = None\n\t\tself.current_profile = None\n\t\tself.current_window = None\n\t\tself.conds = AutoSwitcher.parse_conditions(self.config)\n\t\n\t\n\t@staticmethod\n\tdef parse_conditions(config):\n\t\t\"\"\" Parses conditions from config \"\"\"\n\t\tparser = TalkingActionParser()\n\t\tconds = {}\n\t\tfor c in config['autoswitch']:\n\t\t\ttry:\n\t\t\t\tastr = c['action']\n\t\t\t\tif type(astr) == dict and \"action\" in astr:\n\t\t\t\t\t# Backwards compatibility\n\t\t\t\t\tastr = astr[\"action\"]\n\t\t\t\taction = parser.restart(astr).parse()\n\t\t\t\tconds[Condition.parse(c['condition'])] = action\n\t\t\texcept Exception as e:\n\t\t\t\t# Failure here is not fatal\n\t\t\t\tlog.error(\"Failed to parse autoswitcher condition '%s'\", c)\n\t\t\t\tlog.error(e)\n\t\tlog.debug(\"Parsed %s autoswitcher conditions\", len(conds))\n\t\treturn conds\n\t\n\t\n\t@staticmethod\n\tdef assign(conds, title, wm_class, profile):\n\t\tc = Condition(wm_class=wm_class[0])\n\t\tconds[c] = ChangeProfileAction(profile)\n\t\n\t\n\t@staticmethod\n\tdef unassign(conds, title, wm_class, action):\n\t\t\"\"\"\n\t\tRemoves any condition that matches given title/class/action combination.\n\t\t'action' can be None, in which case, removes removes any condition\n\t\tthat matches title or wm class.\n\t\t\"\"\"\n\t\tcount, cmpwith = 0, None\n\t\tif action is not None:\n\t\t\tcmpwith = action.to_string()\n\t\tfor c in conds.copy().keys():\n\t\t\tif action is None or conds[c].to_string() == cmpwith:\n\t\t\t\tif c.matches(title, wm_class):\n\t\t\t\t\tdel conds[c]\n\t\t\t\t\tcount += 1\n\t\tlog.debug(\"Removed %s autoswitcher conditions\", count)\n\t\n\t\n\tdef connect_daemon(self, *a):\n\t\ttry:\n\t\t\tself.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n\t\t\tself.socket.connect(get_daemon_socket())\n\t\t\tself.socket.send(b\"Register: autoswitch\\n\")\n\t\texcept Exception:\n\t\t\tlog.error(\"Failed to connect to scc-daemon\")\n\t\t\tos._exit(1)\n\t\t\treturn\n\t\tbuffer = \"\"\n\t\twhile self.exit_code is None:\n\t\t\tr = self.socket.recv(1024)\n\t\t\tself.lock.acquire()\n\t\t\tif len(r) == 0:\n\t\t\t\tself.lock.release()\n\t\t\t\tlog.error(\"Connection to daemon lost\")\n\t\t\t\tos._exit(2)\n\t\t\t\treturn\n\t\t\tbuffer += r.decode(\"utf-8\")\n\t\t\twhile \"\\n\" in buffer:\n\t\t\t\tline, buffer = buffer.split(\"\\n\", 1)\n\t\t\t\tif line.startswith(\"Version:\"):\n\t\t\t\t\tversion = line.split(\":\", 1)[-1].strip()\n\t\t\t\t\tlog.debug(\"Connected to daemon, version %s\", version)\n\t\t\t\telif line.startswith(\"Current profile:\"):\n\t\t\t\t\tprofile = line.split(\":\", 1)[-1].strip()\n\t\t\t\t\tlog.debug(\"Daemon reported profile change: %s\", profile)\n\t\t\t\t\tself.current_profile = profile\n\t\t\t\telif line.startswith(\"Reconfigured.\"):\n\t\t\t\t\tlog.debug(\"Reloading config...\")\n\t\t\t\t\tself.config = Config()\n\t\t\t\t\tself.conds = AutoSwitcher.parse_conditions(self.config)\n\t\t\t\telif line.startswith(\"Controller Count:\"):\n\t\t\t\t\tself.enabled = int(line.split(\":\")[-1]) > 0\n\t\t\t\t\tlog.debug(\"Enabled: %s\", self.enabled)\n\t\t\t\n\t\t\tself.lock.release()\n\t\n\t\n\tdef check(self, *a):\n\t\tw = X.get_current_window(self.dpy)\n\t\tif w == self.current_window or not self.current_profile:\n\t\t\t# Window not switched or profile is not known yet\n\t\t\treturn\n\t\tself.current_window = w\n\t\tlog.debug(\"Window switched: %s\", w)\n\t\tpars = X.get_window_title(self.dpy, w), X.get_window_class(self.dpy, w)\n\n\t\tif pars[0] is None:\n\t\t\tpars = (\"\",pars[1])\n\n\t\tif pars[1] is None:\n\t\t\tpars = (pars[0], (\"\",\"\"))\n\n\t\tfor c in self.conds:\n\t\t\tif c.matches(*pars):\n\t\t\t\taction = self.conds[c]\n\t\t\t\taction.button_press(self.mapper)\n\t\t\t\taction.button_release(self.mapper)\n\t\n\t\n\tdef on_sa_profile(self, mapper, action):\n\t\tprofile_name = action.profile\n\t\tpath = find_profile(profile_name)\n\t\tif path:\n\t\t\twith self.lock:\n\t\t\t\tif path != self.current_profile and not self.current_profile.endswith(\".mod\"):\n\t\t\t\t\t# Switch only if target profile is not active\n\t\t\t\t\t# and active profile is not being editted.\n\t\t\t\t\ttry:\n\t\t\t\t\t\tif self.config['autoswitch_osd']:\n\t\t\t\t\t\t\tmsg = (_(\"Switched to profile\") + \" \" + profile_name)\n\t\t\t\t\t\t\tself.socket.send(b\"OSD: \" + msg.encode('utf-8') + b\"\\n\")\n\t\t\t\t\t\tself.socket.send(b\"Profile: \" + path.encode('utf-8') + b\"\\n\")\n\t\t\t\t\texcept:\n\t\t\t\t\t\tlog.error(\"Socket write failed\")\n\t\t\t\t\t\tos._exit(2)\n\t\t\t\t\t\treturn\n\t\telse:\n\t\t\tlog.error(\"Cannot switch to profile '%s', profile file not found\", profile_name)\n\t\n\t\n\tdef on_sa_turnoff(self, mapper, action):\n\t\twith self.lock:\n\t\t\ttry:\n\t\t\t\tself.socket.send(b\"Turnoff.\\n\")\n\t\t\texcept:\n\t\t\t\tlog.error(\"Socket write failed\")\n\t\t\t\tos._exit(2)\n\t\n\t\n\tdef on_sa_restart(self, *a):\n\t\twith self.lock:\n\t\t\ttry:\n\t\t\t\tself.socket.send(b\"Restart.\\n\")\n\t\t\texcept:\n\t\t\t\tlog.error(\"Socket write failed\")\n\t\t\t\tos._exit(2)\n\t\n\t\n\tdef sigint(self, *a):\n\t\tlog.error(\"break\")\n\t\tos._exit(0)\n\t\n\t\n\tdef run(self):\n\t\tself.thread.start()\n\t\tlog.debug(\"AutoSwitcher started\")\n\t\twhile self.exit_code is None:\n\t\t\tif self.enabled:\n\t\t\t\tself.check()\n\t\t\ttime.sleep(self.INTERVAL)\n\t\treturn 1\n\n\nclass Condition(object):\n\t\"\"\"\n\tRepresents AutoSwitcher condition loaded from configuration file.\n\t\n\tCurrently, there are 4 ways to match window:\n\tBy exact title, by part of title, by regexp aplied on title and by matching\n\twindow class.\n\tIt's possible to combine all three types of title matching with window class\n\tmatching.\n\t\"\"\"\n\t\n\tdef __init__(self, exact_title=None, title=None, regexp=None, wm_class=None):\n\t\t\"\"\"\n\t\tAt least one parameter has to be specified; regexp has to be\n\t\tcompiled regular expression.\n\t\t\"\"\"\n\t\tself.exact_title = exact_title\n\t\tself.title = title\n\t\tself.regexp = regexp\n\t\tif type(self.regexp) is str:\n\t\t\tself.regexp = re.compile(self.regexp)\n\t\tself.wm_class = wm_class\n\t\tself.empty = not ( title or title or regexp or wm_class )\n\t\n\t\n\tdef __str__(self):\n\t\treturn \"<Condition title=%s, exact_title=%s, regexp=%s, wm_class=%s>\" % (\n\t\t\tself.title, self.exact_title, self.regexp, self.wm_class)\n\t\n\t\n\tdef describe(self):\n\t\t\"\"\"\n\t\tReturns string that describes condition in human-readable form.\n\t\tUsed in GUI.\n\t\t\"\"\"\n\t\trv = []\n\t\tif self.title:\n\t\t\trv += [ _(\"title contains '%s'\") % (self.title,) ]\n\t\tif self.exact_title:\n\t\t\trv += [ _(\"title is '%s'\") % (self.exact_title,) ]\n\t\tif self.regexp:\n\t\t\trv += [ _(\"title matches '%s'\") % (self.regexp.pattern,) ]\n\t\tif self.wm_class:\n\t\t\trv += [ _(\"class is '%s'\") % (self.wm_class,) ]\n\t\tif rv:\n\t\t\treturn _(\" and \").join(rv)\n\t\treturn _(\"matches nothing\")\n\t\n\t\n\t@staticmethod\n\tdef parse(data):\n\t\tif 'regexp' in data:\n\t\t\tdata = dict(data)\n\t\t\tdata['regexp'] = re.compile(data['regexp'])\n\t\treturn Condition(**data)\n\t\n\tdef encode(self):\n\t\t\"\"\"\n\t\tReturns Condition in dict that can be stored in json configuration\n\t\t\"\"\"\n\t\trv = {}\n\t\tif self.title:\n\t\t\trv['title'] = self.title\n\t\tif self.exact_title:\n\t\t\trv['exact_title'] = self.exact_title\n\t\tif self.regexp:\n\t\t\trv['regexp'] = self.regexp.pattern\n\t\tif self.wm_class:\n\t\t\trv['wm_class'] = self.wm_class\n\t\treturn rv\n\t\n\t\n\tdef matches(self, window_title, wm_class):\n\t\t\"\"\"\n\t\tReturns True if condition matches provided window properties.\n\t\t\n\t\twm_class is what xwrappers.get_window_class returns, tuple of two strings.\n\t\t\"\"\"\n\t\tif self.empty:\n\t\t\t# Empty condition matches nothing\n\t\t\treturn False\n\t\t\n\t\tif self.wm_class:\n\t\t\tif self.wm_class != wm_class[0] and self.wm_class != wm_class[1]:\n\t\t\t\t# Window class matching is enabled and window doesn't match\n\t\t\t\treturn False\n\t\t\t\n\t\tif self.exact_title and self.exact_title != window_title:\n\t\t\t# Matching exact title is enabled, but title doesn't match\n\t\t\treturn False\n\t\t\n\t\tif self.title and self.title not in window_title:\n\t\t\t# Matching part of title is enabled, but doesn't match\n\t\t\treturn False\n\t\t\n\t\tif self.regexp and not self.regexp.match(window_title):\n\t\t\t# Matching by regexp is enabled, but regexp doesn't match\n\t\t\treturn False\n\t\t\n\t\treturn True\n\n\nclass AutoswitchOptsMenuGenerator(MenuGenerator):\n\t\"\"\" Generates entire Autoswich Options submenu \"\"\"\n\tGENERATOR_NAME = \"autoswitch\"\n\t\n\tdef callback(self, menu, daemon, controller, menuitem):\n\t\tdef on_response(*a):\n\t\t\tmenu.quit(-2)\n\t\tif menuitem.id in (\"as::unassign\", \"as::assign\"):\n\t\t\tif menuitem.id == \"as::unassign\":\n\t\t\t\tAutoSwitcher.unassign(self.conds, self.title, self.wm_class, self.assigned_prof)\n\t\t\telse:\n\t\t\t\tif controller.get_profile():\n\t\t\t\t\tprofile = os.path.split(controller.get_profile())[-1]\n\t\t\t\t\tif profile.endswith(\".mod\"):\n\t\t\t\t\t\tprofile = profile[0:-4]\n\t\t\t\t\tif profile.endswith(\".sccprofile\"):\n\t\t\t\t\t\tprofile = profile[0:-11]\n\t\t\t\t\tAutoSwitcher.unassign(self.conds, self.title, self.wm_class, None)\n\t\t\t\t\tAutoSwitcher.assign(self.conds, self.title, self.wm_class, profile)\n\t\t\tcfg = Config()\n\t\t\tcfg[\"autoswitch\"] = [{\n\t\t\t\t\t\"condition\" : c.encode(),\n\t\t\t\t\t\"action\" : self.conds[c].to_string()\n\t\t\t\t} for c in self.conds\n\t\t\t]\n\t\t\tcfg.save()\n\t\t\tdaemon.request(b\"Reconfigure.\\n\", on_response, on_response)\n\t\telse:\n\t\t\ton_response()\n\t\n\t\n\tdef describe(self):\n\t\treturn _(\"[ All Profiles ]\")\n\t\n\t\n\tdef generate(self, menuhandler):\n\t\trv = []\n\t\twin = X.get_current_window(menuhandler.xdisplay)\n\t\tif not win:\n\t\t\t# Bail out if active window cannot be determined\n\t\t\trv.append(self.mk_item(None, _(\"No active window\")))\n\t\t\trv.append(self.mk_item(\"as::close\", _(\"Close\")))\n\t\t\treturn rv\n\t\t\n\t\tself.title = X.get_window_title(menuhandler.xdisplay, win)\n\t\tself.wm_class = X.get_window_class(menuhandler.xdisplay, win)\n\t\tself.assigned_prof = None\n\t\tself.conds = AutoSwitcher.parse_conditions(Config())\n\t\tif self.title and \"-\" in self.title:\n\t\t\tself.title = self.title.split(\"-\")[-1]\n\t\tfor c in self.conds:\n\t\t\tif c.matches(self.title, self.wm_class):\n\t\t\t\tself.assigned_prof = self.conds[c]\n\t\t\t\tbreak\n\t\tif win:\n\t\t\tdisplay_title = self.title or _(\"No Title\")\n\t\t\trv.append(self.mk_item(None, _(\"Current Window: %s\") % (self.title[0:25],)))\n\t\t\tif self.assigned_prof:\n\t\t\t\trv.append(self.mk_item(None, _(\"Assigned Profile: %s\") % (self.assigned_prof,)))\n\t\t\telse:\n\t\t\t\trv.append(self.mk_item(None, _(\"No Profile Assigned\")))\n\t\t\trv.append(Separator())\n\t\t\trv.append(Separator())\n\t\t\trv.append(Separator())\n\t\t\tif self.assigned_prof:\n\t\t\t\trv.append(self.mk_item(\"as::unassign\", _(\"Unassign Profile\")))\n\t\t\trv.append(self.mk_item(\"as::assign\", _(\"Assign Current Profile\")))\n\t\treturn rv\n\t\n\t\n\tdef mk_item(self, id, title, **kws):\n\t\t\"\"\" Creates menu item and assigns callback \"\"\"\n\t\tmenuitem = MenuItem(id, title)\n\t\tmenuitem.callback = self.callback\n\t\tfor k in kws:\n\t\t\tsetattr(menuitem, k, kws[k])\n\t\treturn menuitem\n\nMENU_GENERATORS[AutoswitchOptsMenuGenerator.GENERATOR_NAME] = AutoswitchOptsMenuGenerator\n"
  },
  {
    "path": "scc/x11/scc-autoswitch-daemon.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Autoswitch Daemon\n\nObserves active window and commands scc-daemon to change profiles as needed.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.x11.autoswitcher import AutoSwitcher\nfrom scc.lib import xwrappers as X\nfrom scc.tools import find_profile\nfrom scc.paths import get_daemon_socket\nfrom scc.config import Config\n\nimport os, sys, time, socket, threading, signal, logging\nlog = logging.getLogger(\"AS-Daemon\")\n\n\nif __name__ == \"__main__\":\n\tfrom scc.tools import init_logging, set_logging_level\n\tfrom scc.paths import get_share_path\n\tinit_logging(suffix=\" AS \")\n\tset_logging_level('debug' in sys.argv, 'debug' in sys.argv)\n\t\n\tif \"DISPLAY\" not in os.environ:\n\t\tlog.error(\"DISPLAY env variable not set.\")\n\t\tsys.exit(1)\n\t\n\td = AutoSwitcher()\n\tsignal.signal(signal.SIGINT, d.sigint)\n\td.run()\n\tsys.exit(d.exit_code)\n"
  },
  {
    "path": "scc/x11/scc-osd-daemon.py",
    "content": "#!/usr/bin/env python2\n\n\"\"\"\nSC-Controller - OSD Daemon\n\nControls stuff displayed as OSD.\n\"\"\"\nfrom __future__ import unicode_literals\nfrom scc.tools import _, set_logging_level\n\nimport gi\ngi.require_version('Gtk', '3.0')\ngi.require_version('Rsvg', '2.0')\ngi.require_version('GdkX11', '3.0')\n\nfrom gi.repository import Gtk, Gdk, GdkX11, GLib\nfrom scc.gui.daemon_manager import DaemonManager\nfrom scc.osd.gesture_display import GestureDisplay\nfrom scc.osd.radial_menu import RadialMenu\nfrom scc.osd.hmenu import HorizontalMenu\nfrom scc.osd.quick_menu import QuickMenu\nfrom scc.osd.grid_menu import GridMenu\nfrom scc.osd.keyboard import Keyboard\nfrom scc.osd.message import Message\nfrom scc.osd.dialog import Dialog\nfrom scc.osd import OSDWindow\nfrom scc.osd.menu import Menu\nfrom scc.osd.area import Area\nfrom scc.special_actions import OSDAction\nfrom scc.tools import shsplit, shjoin\nfrom scc.config import Config\n\nimport os, sys, logging, time, traceback\nlog = logging.getLogger(\"osd.daemon\")\n\nclass OSDDaemon(object):\n\tdef __init__(self):\n\t\tself.exit_code = -1\n\t\tself.mainloop = GLib.MainLoop()\n\t\tself.config = None\n\t\t# hash_of_colors is used to determine if css needs to be reapplied\n\t\t# after configuration change\n\t\tself._hash_of_colors = -1\n\t\tself._visible_messages = {}\n\t\tself._window = None\n\t\tself._registered = False\n\t\tself._last_profile_change = 0\n\t\tself._recent_profiles_undo = None\n\t\n\t\n\tdef quit(self, code=-1):\n\t\tself.exit_code = code\n\t\tself.mainloop.quit()\n\t\n\t\n\tdef get_exit_code(self):\n\t\treturn self.exit_code\n\t\n\t\n\tdef on_daemon_reconfigured(self, *a):\n\t\tlog.debug(\"Reloading config...\")\n\t\tself.config.reload()\n\t\tself._check_colorconfig_change()\n\t\n\t\n\tdef on_profile_changed(self, daemon, profile):\n\t\tname = os.path.split(profile)[-1]\n\t\tif name.endswith(\".sccprofile\") and not name.startswith(\".\"):\n\t\t\t# Ignore .mod and hidden files\n\t\t\tname = name[0:-11]\n\t\t\trecents = self.config['recent_profiles']\n\t\t\tif len(recents) and recents[0] == name:\n\t\t\t\t# Already first in recent list\n\t\t\t\treturn\n\t\t\t\n\t\t\tif time.time() - self._last_profile_change < 2.0:\n\t\t\t\t# Profiles are changing too fast, probably because user\n\t\t\t\t# is using scroll wheel over profile combobox\n\t\t\t\tif self._recent_profiles_undo:\n\t\t\t\t\trecents = [] + self._recent_profiles_undo\n\t\t\tself._last_profile_change = time.time()\n\t\t\tself._recent_profiles_undo = [] + recents\n\t\t\t\n\t\t\twhile name in recents:\n\t\t\t\trecents.remove(name)\n\t\t\trecents.insert(0, name)\n\t\t\tif len(recents) > self.config['recent_max']:\n\t\t\t\trecents = recents[0:self.config['recent_max']]\n\t\t\tself.config['recent_profiles'] = recents\n\t\t\tself.config.save()\n\t\t\tlog.debug(\"Updated recent profile list\")\n\t\t\tself.clear_messages()\n\t\n\t\n\tdef on_daemon_died(self, *a):\n\t\tlog.error(\"Connection to daemon lost\")\n\t\tself.quit(2)\n\t\n\t\n\tdef on_daemon_connected(self, *a):\n\t\tdef success(*a):\n\t\t\tlog.info(\"Sucessfully registered as scc-osd-daemon\")\n\t\t\tself._registered = True\n\t\tdef failure(why):\n\t\t\tlog.error(\"Failed to registered as scc-osd-daemon: %s\", why)\n\t\t\tself.quit(1)\n\t\t\n\t\tif not self._registered:\n\t\t\tself.daemon.request('Register: osd', success, failure)\n\t\n\t\n\tdef on_menu_closed(self, m):\n\t\t\"\"\" Called after OSD menu is hidden from screen \"\"\"\n\t\tself._window = None\n\t\tif m.get_exit_code() == 0:\n\t\t\t# 0 means that user selected item and confirmed selection\n\t\t\tself.daemon.request(\n\t\t\t\t'Selected: {} {}'.format(m.get_menuid(), m.get_selected_item_id()),\n\t\t\t\t#'Selected: %s' % ( shjoin([\n\t\t\t\t#\tm.get_menuid(), m.get_selected_item_id()\n\t\t\t\t#])),\n\t\t\t\tlambda *a : False, lambda *a : False)\n\t\n\t\n\tdef on_message_closed(self, m):\n\t\thsh = m.hash()\n\t\tif hsh in self._visible_messages:\n\t\t\tdel self._visible_messages[hsh]\n\t\n\t\n\tdef on_keyboard_closed(self, *a):\n\t\t\"\"\" Called after on-screen keyboard is hidden from the screen \"\"\"\n\t\tself._window = None\n\t\n\t\n\tdef on_gesture_recognized(self, gd):\n\t\t\"\"\" Called after on-screen keyboard is hidden from the screen \"\"\"\n\t\tself._window = None\n\t\tif gd.get_exit_code() == 0:\n\t\t\tself.daemon.request('Gestured: %s' % ( gd.get_gesture(), ),\n\t\t\t\tlambda *a : False, lambda *a : False)\n\t\telse:\n\t\t\tself.daemon.request('Gestured: x', lambda *a : False, lambda *a : False)\n\t\n\t\n\t@staticmethod\n\tdef _is_menu_message(m):\n\t\t\"\"\"\n\t\tReturns True if m starts with 'OSD: [grid|radial]menu'\n\t\tor \"OSD: dialog\"\n\t\t\"\"\"\n\t\treturn (\n\t\t\tm.startswith(\"OSD: menu\")\n\t\t\tor m.startswith(\"OSD: radialmenu\")\n\t\t\tor m.startswith(\"OSD: quickmenu\")\n\t\t\tor m.startswith(\"OSD: gridmenu\")\n\t\t\tor m.startswith(\"OSD: dialog\")\n\t\t\tor m.startswith(\"OSD: hmenu\")\n\t\t)\n\t\n\t\n\tdef on_unknown_message(self, daemon, message):\n\t\tif not message.startswith(\"OSD:\"):\n\t\t\treturn\n\t\tif message.startswith(\"OSD: message\"):\n\t\t\targs = shsplit(message)[1:]\n\t\t\tm = Message()\n\t\t\tm.parse_argumets(args)\n\t\t\thsh = m.hash()\n\t\t\tif hsh in self._visible_messages:\n\t\t\t\tself._visible_messages[hsh].extend()\n\t\t\t\tm.destroy()\n\t\t\telse:\n\t\t\t\t# TODO: Do this only for default position once changing\n\t\t\t\t# TODO: is allowed\n\t\t\t\tif len(self._visible_messages):\n\t\t\t\t\ttmp = list(self._visible_messages.values())\n\t\t\t\t\theight = tmp[0].get_size().height\n\t\t\t\t\tx, y = m.position\n\t\t\t\t\twhile y in [ i.position[1] for i in tmp ]:\n\t\t\t\t\t\ty -= height + 5\n\t\t\t\t\tm.position = x, y\n\t\t\t\tm.show()\n\t\t\t\tself._visible_messages[hsh] = m\n\t\t\t\tm.connect(\"destroy\", self.on_message_closed)\n\t\telif message.startswith(\"OSD: keyboard\"):\n\t\t\tif self._window:\n\t\t\t\tlog.warning(\"Another OSD is already visible - refusing to show keyboard\")\n\t\t\telse:\n\t\t\t\targs = shsplit(message)[1:]\n\t\t\t\tself._window = Keyboard(self.config)\n\t\t\t\tself._window.connect('destroy', self.on_keyboard_closed)\n\t\t\t\tself._window.parse_argumets(args)\n\t\t\t\tself._window.show()\n\t\t\t\tself._window.use_daemon(self.daemon)\n\t\telif message.startswith(\"OSD: gesture\"):\n\t\t\tif self._window:\n\t\t\t\tlog.warning(\"Another OSD is already visible - refusing to show keyboard\")\n\t\t\telse:\n\t\t\t\targs = shsplit(message)[1:]\n\t\t\t\tself._window = GestureDisplay(self.config)\n\t\t\t\tself._window.parse_argumets(args)\n\t\t\t\tself._window.use_daemon(self.daemon)\n\t\t\t\tself._window.show()\n\t\t\t\tself._window.connect('destroy', self.on_gesture_recognized)\n\t\telif self._is_menu_message(message):\n\t\t\targs = shsplit(message)[1:]\n\t\t\tif self._window:\n\t\t\t\tlog.warning(\"Another OSD is already visible - refusing to show menu\")\n\t\t\telse:\n\t\t\t\tif message.startswith(\"OSD: hmenu\"):\n\t\t\t\t\tself._window = HorizontalMenu()\n\t\t\t\telif message.startswith(\"OSD: radialmenu\"):\n\t\t\t\t\tself._window = RadialMenu()\n\t\t\t\telif message.startswith(\"OSD: quickmenu\"):\n\t\t\t\t\tself._window = QuickMenu()\n\t\t\t\telif message.startswith(\"OSD: gridmenu\"):\n\t\t\t\t\tself._window = GridMenu()\n\t\t\t\telif message.startswith(\"OSD: dialog\"):\n\t\t\t\t\tself._window = Dialog()\n\t\t\t\telse:\n\t\t\t\t\tself._window = Menu()\n\t\t\t\tself._window.connect('destroy', self.on_menu_closed)\n\t\t\t\tself._window.use_config(self.config)\n\t\t\t\ttry:\n\t\t\t\t\tif self._window.parse_argumets(args):\n\t\t\t\t\t\tself._window.show()\n\t\t\t\t\t\tself._window.use_daemon(self.daemon)\n\t\t\t\t\telse:\n\t\t\t\t\t\tlog.error(\"Failed to show menu\")\n\t\t\t\t\t\tself._window = None\n\t\t\t\texcept:\n\t\t\t\t\tlog.error(traceback.format_exc())\n\t\t\t\t\tlog.error(\"Failed to show menu\")\n\t\t\t\t\tself._window = None\n\t\telif message.startswith(\"OSD: area\"):\n\t\t\targs = shsplit(message)[1:]\n\t\t\tif self._window:\n\t\t\t\tlog.warning(\"Another OSD is already visible - refusing to show area\")\n\t\t\telse:\n\t\t\t\targs = shsplit(message)[1:]\n\t\t\t\tself._window = Area()\n\t\t\t\tself._window.connect('destroy', self.on_keyboard_closed)\n\t\t\t\tif self._window.parse_argumets(args):\n\t\t\t\t\tself._window.show()\n\t\t\t\telse:\n\t\t\t\t\tself._window.quit()\n\t\t\t\t\tself._window = None\n\t\telif message.startswith(\"OSD: clear\"):\n\t\t\t# Clears active OSD windows\n\t\t\tself.clear_windows()\n\t\telse:\n\t\t\tlog.warning(\"Unknown command from daemon: '%s'\", message)\n\t\n\t\n\tdef clear_windows(self):\n\t\tif self._window:\n\t\t\tself._window.quit()\n\t\t\tself._window = None\n\t\tself.clear_messages(only_long_lasting=False)\n\t\n\t\n\tdef clear_messages(self, only_long_lasting=True):\n\t\t\"\"\"\n\t\tClears all OSD messages from screen.\n\t\tIf only_long_lasting is True, which is default behaviour on profile\n\t\tchange, only messages set to last more than 10s are hidden.\n\t\t\"\"\"\n\t\tto_destroy = [] + list(self._visible_messages.values())\n\t\tfor m in to_destroy:\n\t\t\tif not only_long_lasting or m.timeout <= 0 or m.timeout > OSDAction.DEFAULT_TIMEOUT * 2:\n\t\t\t\tm.destroy()\n\t\n\t\n\tdef _check_colorconfig_change(self):\n\t\t\"\"\"\n\t\tChecks if OSD color configuration is changed and re-applies CSS\n\t\tif needed.\n\t\t\"\"\"\n\t\th = sum([ hash(self.config['osd_colors'][x]) for x in self.config['osd_colors'] ])\n\t\th += sum([ hash(self.config['osk_colors'][x]) for x in self.config['osk_colors'] ])\n\t\th += hash(self.config['osd_style'])\n\t\tif self._hash_of_colors != h:\n\t\t\tself._hash_of_colors = h\n\t\t\tOSDWindow._apply_css(self.config)\n\t\t\tif self._window and isinstance(self._window, Keyboard):\n\t\t\t\tself._window.recolor()\n\t\t\t\tself._window.update_labels()\n\t\t\t\tself._window.redraw_background()\n\t\n\t\n\tdef run(self):\n\t\tself.daemon = DaemonManager()\n\t\tself.config = Config()\n\t\tself._check_colorconfig_change()\n\t\tself.daemon.connect('alive', self.on_daemon_connected)\n\t\tself.daemon.connect('dead', self.on_daemon_died)\n\t\tself.daemon.connect('profile-changed', self.on_profile_changed)\n\t\tself.daemon.connect('reconfigured', self.on_daemon_reconfigured)\n\t\tself.daemon.connect('unknown-msg', self.on_unknown_message)\n\t\tself.mainloop.run()\n\n\nif __name__ == \"__main__\":\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging(suffix=\" OSD\")\n\tset_logging_level('debug' in sys.argv, 'debug' in sys.argv)\n\t\n\td = OSDDaemon()\n\td.run()\n\tsys.exit(d.get_exit_code())\n"
  },
  {
    "path": "scc/x11/scc_autoswitch_daemon.py",
    "content": "#!/usr/bin/env python2\n\"\"\"\nSC-Controller - Autoswitch Daemon\n\nObserves active window and commands scc-daemon to change profiles as needed.\n\"\"\"\nfrom __future__ import unicode_literals\n\nfrom scc.x11.autoswitcher import AutoSwitcher\nimport sys, os, logging\n\nlog = logging.getLogger(\"AutoSwitcher-Daemon\")\n\nif __name__ == \"__main__\":\n\tfrom scc.tools import init_logging, set_logging_level\n\tfrom scc.paths import get_share_path\n\tinit_logging(suffix=\" AutoSwitcher\")\n\tset_logging_level('debug' in sys.argv, 'debug' in sys.argv)\n\t\n\tif \"DISPLAY\" not in os.environ:\n\t\tlog.error(\"DISPLAY env variable not set.\")\n\t\tsys.exit(1)\n\t\n\td = AutoSwitcher()\n\td.run()\n\tsys.exit(d.exit_code)\n"
  },
  {
    "path": "scc-mime-types.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<mime-info xmlns=\"http://www.freedesktop.org/standards/shared-mime-info\">\n\t<mime-type type=\"application/x-steamcontrollerdb-profile\">\n\t\t<comment>Steam Controller Database Profile</comment>\n\t\t<sub-class-of type=\"text/plain\"/>\n\t\t<generic-icon name=\"sc-controller\"/>\n\t\t<glob priority=\"75\" pattern=\"*.vdffz\"/>\n\t</mime-type>\n\t<mime-type type=\"application/x-scc-profile\">\n\t\t<comment>SC Controller Profile</comment>\n\t\t<sub-class-of type=\"text/plain\"/>\n\t\t<generic-icon name=\"sc-controller\"/>\n\t\t<glob priority=\"75\" pattern=\"*.sccprofile\"/>\n\t</mime-type>\n\t<mime-type type=\"application/x-scc-profile-package\">\n\t\t<comment>SC Controller Profile Package</comment>\n\t\t<sub-class-of type=\"application/x-compressed-tar\"/>\n\t\t<generic-icon name=\"sc-controller\"/>\n\t\t<glob priority=\"75\" pattern=\"*.sccprofile.tar.gz\"/>\n\t</mime-type>\n</mime-info>\n"
  },
  {
    "path": "scripts/69-sc-controller.rules",
    "content": "# This file allows SC-Controller application and daemon to access Steam Controller or its USB dongle.\n# This is done by allowing read/write access to all users. You may want to change this to something like\n# MODE=\"0660\", GROUP=\"games\" to allow r/w access only to members of that group.\n\n# Valve USB devices\nSUBSYSTEM==\"usb\", ATTRS{idVendor}==\"28de\", MODE=\"0666\"\n# Valve HID devices over bluetooth hidraw\nKERNEL==\"hidraw*\", KERNELS==\"*28DE:*\", MODE=\"0666\", TAG+=\"uaccess\"\n# Sony USB devices\nSUBSYSTEM==\"usb\", ATTRS{idVendor}==\"054c\", MODE=\"0666\"\n# Sony input devices over bluetooth\nSUBSYSTEM==\"input\", KERNELS==\"*054C:09CC*\", MODE=\"0666\", TAG+=\"uaccess\"\nSUBSYSTEM==\"input\", KERNELS==\"*054C:0CE6*\", MODE=\"0666\", TAG+=\"uaccess\"\n# uinput kernel module write access (allows keyboard, mouse and gamepad emulation)\nKERNEL==\"uinput\", SUBSYSTEM==\"misc\", TAG+=\"uaccess\", OPTIONS+=\"static_node=uinput\", MODE=\"0666\"\n"
  },
  {
    "path": "scripts/appimage-AppRun.sh",
    "content": "#!/bin/bash\nexport PATH=${APPDIR}:${APPDIR}/usr/bin:$PATH\nexport LD_LIBRARY_PATH=${APPDIR}/usr/lib:$LD_LIBRARY_PATH\nexport LD_LIBRARY_PATH=${APPDIR}/usr/lib64:$LD_LIBRARY_PATH\nexport GI_TYPELIB_PATH=${APPDIR}/usr/lib/girepository-1.0\nexport GDK_PIXBUF_MODULEDIR=${APPDIR}/usr/lib/gdk-pixbuf-2.0/2.10.0/loaders\nexport PYTHONPATH=${APPDIR}/usr/lib/python3.10/site-packages:$PYTHONPATH\nexport PYTHONPATH=${APPDIR}/usr/lib64/python3.10/site-packages:$PYTHONPATH\nexport SCC_SHARED=${APPDIR}/usr/share/scc\n\nfunction dependency_check_failed() {\n\t# This checks 4 different ways to open error message in addition to\n\t# throwing it to screen directly\n\t>&2 cat /tmp/scc.depcheck.$$.txt\n\t\n\t[ -e /usr/bin/zenity ] && run_and_die /usr/bin/zenity --error --no-wrap --text \"$(cat /tmp/scc.depcheck.$$.txt)\"\n\t[ -e /usr/bin/yad ] && run_and_die /usr/bin/yad --error --text \"$(cat /tmp/scc.depcheck.$$.txt)\"\n\t[ -e /usr/bin/Xdialog ] && run_and_die /usr/bin/Xdialog --textbox \"/tmp/scc.depcheck.$$.txt\" 10 100\n\t[ -e /usr/bin/xdg ] && run_and_die /usr/bin/xdg-open \"/tmp/scc.depcheck.$$.txt\"\n\texit 1\n}\n\nfunction run_and_die() {\n\t\"$@\"\n\texit 1\n}\n\n# Check dependencies 1st\npython3 ${APPDIR}/usr/bin/scc dependency-check &>/tmp/scc.depcheck.$$.txt \\\n\t|| dependency_check_failed\nrm /tmp/scc.depcheck.$$.txt || true\n\n# Pre-parse arguments\nARG1=$1\nif [ \"x$ARG1\" == \"x\" ] ; then\n\t# Start gui if no arguments are passed\n\tARG1=\"gui\"\nelse\n\tshift\nfi\n\n# Start\nexport GDK_PIXBUF_MODULE_FILE=${APPDIR}/../$$-gdk-pixbuf-loaders.cache\ngdk-pixbuf-query-loaders >\"$GDK_PIXBUF_MODULE_FILE\"\npython3 ${APPDIR}/usr/bin/scc $ARG1 $@\nrm \"$GDK_PIXBUF_MODULE_FILE\" &>/dev/null\n\n"
  },
  {
    "path": "scripts/sc-controller",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(0)\n\nif __name__ == \"__main__\":\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0') \n\tgi.require_version('GdkX11', '3.0') \n\tgi.require_version('Rsvg', '2.0') \n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\n\tfrom gi.repository import Gtk, GObject\n\tglades = os.path.join(get_share_path(), \"glade\")\n\timages = os.path.join(get_share_path(), \"images\")\n\tif Gtk.IconTheme.get_default():\n\t\tGtk.IconTheme.get_default().append_search_path(images)\n\t#GObject.threads_init()\n\t\n\tfrom scc.gui.app import App\n\tApp(glades, images).run(sys.argv)\n"
  },
  {
    "path": "scripts/sc-controller.appdata.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop\">\n  <id>sc-controller.desktop</id>\n  <name>SC Controller</name>\n  <summary>Edits controller mappings</summary>\n  <developer_name>kozec</developer_name>\n  <description>\n  <p>\n   User-mode driver and GTK3 based GUI for Steam Controller.\n  </p>\n</description>\n  <metadata_license>CC0-1.0</metadata_license>\n  <project_license>GPL-2.0</project_license>\n  <url type=\"bugtracker\">https://github.com/kozec/sc-controller/issues</url>\n  <url type=\"donation\">https://www.paypal.com/cgi-bin/webscr?cmd=_donations&amp;business=77DQD3L9K8RPU&amp;lc=SK&amp;item_name=kozec&amp;item_number=scc&amp;currency_code=EUR&amp;bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted</url>\n  <url type=\"help\">https://github.com/kozec/sc-controller/wiki</url>\n  <url type=\"homepage\">https://github.com/kozec/sc-controller/</url>\n  <screenshots>\n    <screenshot type=\"default\">\n      <caption>Main Application Window</caption>\n      <image width=\"923\" height=\"538\">https://raw.githubusercontent.com/kozec/sc-controller/master/docs/screenshot1.png</image>\n    </screenshot>\n    <screenshot>\n      <caption>SC-Controller in action</caption>\n      <image width=\"887\" height=\"622\">https://raw.githubusercontent.com/kozec/sc-controller/master/docs/screenshot2.png</image>\n    </screenshot>\n  </screenshots>\n</component>\n"
  },
  {
    "path": "scripts/sc-controller.desktop",
    "content": "[Desktop Entry]\nName=SC Controller\nGenericName=SC Controller\nComment=Edits controller mappings\nComment[ru]=Настройка контроллера\nExec=sc-controller\nType=Application\nIcon=sc-controller\nCategories=GTK;Settings;HardwareSettings;\nMimeType=application/x-steamcontrollerdb-profile;application/x-scc-profile;application/x-scc-profile-package;\nStartupWMClass=SC Controller\n"
  },
  {
    "path": "scripts/scc",
    "content": "#!/usr/bin/env python3\n\"\"\"\nScript that integrates everything SCC can do in one executable.\nCreated so scc-* stuff doesn't polute /usr/bin.\n\"\"\"\nfrom scc.scripts import main\n\nif __name__ == '__main__':\n\tmain()\n"
  },
  {
    "path": "scripts/scc-daemon",
    "content": "#!/usr/bin/env python3\nfrom scc.sccdaemon import SCCDaemon\nfrom scc.paths import get_pid_file, get_daemon_socket\nfrom scc.tools import init_logging\n\nimport os, argparse\n\ndef main():\n\tinit_logging()\n\tparser = argparse.ArgumentParser()\n\tparser.add_argument('profile', type=str, nargs='*')\n\tparser.add_argument('command', type=str, choices=['start', 'stop', 'restart', 'debug'])\n\tparser.add_argument('--alone', action='store_true', help=\"prevent scc-daemon from launching osd-daemon and autoswitch-daemon\")\n\tparser.add_argument('--once', action='store_true', help=\"use with 'stop' to send single SIGTERM without waiting for daemon to exit\")\n\tdaemon = SCCDaemon(get_pid_file(), get_daemon_socket())\n\targs = parser.parse_args()\n\tdaemon.alone = args.alone\n\t\n\tprofile = \" \".join(args.profile)\n\tif profile:\n\t\tdaemon.set_default_profile(profile)\n\t\t# If no default_profile is set, daemon will try to load last used\n\t\t# from config\n\n\tif 'start' == args.command:\n\t\tdaemon.start()\n\telif 'stop' == args.command:\n\t\tdaemon.stop(once = args.once)\n\telif 'restart' == args.command:\n\t\tdaemon.restart()\n\telif 'debug' == args.command:\n\t\tdaemon.debug()\n\n\nif __name__ == '__main__':\n\tmain()\n"
  },
  {
    "path": "scripts/scc-osd-dialog",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(-1)\n\ndef main():\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tfrom scc.osd.dialog import Dialog\n\tm = Dialog()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tif m.get_exit_code() == 0:\n\t\tprint(m.get_selected_item_id())\n\tsys.exit(m.get_exit_code())\n\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "scripts/scc-osd-keyboard",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal, argparse\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(0)\n\nif __name__ == \"__main__\":\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tfrom scc.osd.keyboard import Keyboard\n\tk = Keyboard()\n\tif not k.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tk.run()\n\tsys.exit(k.get_exit_code())\n"
  },
  {
    "path": "scripts/scc-osd-launcher",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(-1)\n\ndef main():\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tfrom scc.osd.launcher import Launcher\n\tm = Launcher()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tsys.exit(m.get_exit_code())\n\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "scripts/scc-osd-menu",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(-1)\n\ndef main():\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tfrom scc.osd.menu import Menu\n\tm = Menu()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tif m.get_exit_code() == 0:\n\t\tprint(m.get_selected_item_id())\n\tsys.exit(m.get_exit_code())\n\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "scripts/scc-osd-message",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal, argparse\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(0)\n\nif __name__ == \"__main__\":\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tfrom scc.osd.message import Message\n\tm = Message()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tsys.exit(m.get_exit_code())\n"
  },
  {
    "path": "scripts/scc-osd-radial-menu",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal, argparse\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(-1)\n\ndef main():\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tfrom scc.osd.radial_menu import RadialMenu\n\tm = RadialMenu()\n\tif not m.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\tm.run()\n\tif m.get_exit_code() == 0:\n\t\tprint(m.get_selected_item_id())\n\tsys.exit(m.get_exit_code())\n\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "scripts/scc-osd-show-bindings",
    "content": "#!/usr/bin/env python3\nimport os, sys, signal\n\ndef sigint(*a):\n\tprint(\"\\n*break*\")\n\tsys.exit(-1)\n\ndef main():\n\tsignal.signal(signal.SIGINT, sigint)\n\n\timport gi\n\tgi.require_version('Gtk', '3.0')\n\tgi.require_version('Rsvg', '2.0')\n\tgi.require_version('GdkX11', '3.0')\n\t\n\tfrom scc.tools import init_logging\n\tfrom scc.paths import get_share_path\n\tinit_logging()\n\t\n\tfrom scc.osd.binding_display import BindingDisplay\n\td = BindingDisplay()\n\tif not d.parse_argumets(sys.argv):\n\t\tsys.exit(1)\n\td.run()\n\tsys.exit(d.get_exit_code())\n\n\nif __name__ == \"__main__\":\n\tmain()\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python3\nfrom setuptools import setup, Extension\nfrom scc.constants import DAEMON_VERSION\nimport glob\n\ndata_files = [\n\t\t\t\t('share/scc/glade', glob.glob(\"glade/*.glade\")),\n\t\t\t\t('share/scc/glade/ae', glob.glob(\"glade/ae/*.glade\")),\n\t\t\t\t('share/scc/images', glob.glob(\"images/*.svg\")),\n\t\t\t\t('share/scc/images', glob.glob(\"images/*.json\")),\n\t\t\t\t('share/scc/images/button-images', glob.glob(\"images/button-images/*.svg\")),\n\t\t\t\t('share/scc/images/button-images', glob.glob(\"images/button-images/*.json\")),\n\t\t\t\t('share/scc/images/controller-icons', glob.glob(\"images/controller-icons/*.svg\")),\n\t\t\t\t('share/scc/images/controller-images', glob.glob(\"images/controller-images/*.svg\")),\n\t\t\t\t('share/icons/hicolor/24x24/status', glob.glob(\"images/24x24/status/*.png\")),\n\t\t\t\t('share/icons/hicolor/256x256/status', glob.glob(\"images/256x256/status/*.png\")),\n\t\t\t\t('share/scc/default_profiles', glob.glob(\"default_profiles/*.sccprofile\")),\n\t\t\t\t('share/scc/default_profiles', glob.glob(\"default_profiles/.*.sccprofile\")),\n\t\t\t\t('share/scc/default_menus', glob.glob(\"default_menus/*.menu\")),\n\t\t\t\t('share/scc/default_menus', glob.glob(\"default_menus/.*.menu\")),\n\t\t\t\t('share/scc/osd-styles', glob.glob(\"osd-styles/*.json\")),\n\t\t\t\t('share/scc/osd-styles', glob.glob(\"osd-styles/*.css\")),\n\t\t\t\t('share/scc/', [\"gamecontrollerdb.txt\"]),\n\t\t\t\t('share/pixmaps', [ \"images/sc-controller.svg\" ]),\n\t\t\t\t('share/mime/packages', [ \"scc-mime-types.xml\" ]),\n\t\t\t\t('share/applications', ['scripts/sc-controller.desktop' ]),\n\t\t\t\t('lib/udev/rules.d', glob.glob('scripts/*.rules')),\n\t\t\t\t\n] + [ # menu icons subfolders\n\t(\n\t\t'share/scc/images/menu-icons/' + x.split(\"/\")[-1],\n\t\t[ x + \"/LICENCES\" ] + glob.glob(x + \"/*.png\")\n\t) for x in glob.glob(\"images/menu-icons/*\")\n]\n\n\npackages = [\n\t# Required\n\t'scc', 'scc.drivers', 'scc.lib',\n\t# Usefull\n\t'scc.x11', 'scc.osd', 'scc.foreign',\n\t# GUI\n\t'scc.gui', 'scc.gui.ae', 'scc.gui.importexport', \"scc.gui.creg\"\n]\n\nif __name__ == \"__main__\":\n\tsetup(name = 'sccontroller',\n\t\t\tversion = DAEMON_VERSION,\n\t\t\tdescription = 'Standalone controller maping tool',\n\t\t\tauthor = 'kozec',\n\t\t\tpackages = packages,\n\t\t\tdata_files = data_files,\n\t\t\tscripts = [\n\t\t\t\t'scripts/scc-daemon',\n\t\t\t\t'scripts/sc-controller',\n\t\t\t\t'scripts/scc',\n\t\t\t\t'scripts/scc-osd-dialog',\n\t\t\t\t'scripts/scc-osd-keyboard',\n\t\t\t\t'scripts/scc-osd-launcher',\n\t\t\t\t'scripts/scc-osd-menu',\n\t\t\t\t'scripts/scc-osd-message',\n\t\t\t\t'scripts/scc-osd-radial-menu',\n\t\t\t\t'scripts/scc-osd-show-bindings',\n\t\t\t],\n\t\t\tlicense = 'GPL2',\n\t\t\tplatforms = ['Linux'],\n\t\t\text_modules = [\n\t\t\t\tExtension('libuinput', sources = ['scc/uinput.c']),\n\t\t\t\tExtension('libcemuhook', define_macros = [('PYTHON', 1)],\n\t\t\t\t\t\t\tsources = ['scc/cemuhook_server.c'], libraries = [\"z\"]),\n\t\t\t\tExtension('libhiddrv', sources = ['scc/drivers/hiddrv.c']),\n\t\t\t\tExtension('libsc_by_bt', sources = ['scc/drivers/sc_by_bt.c']),\n\t\t\t\tExtension('libremotepad', sources = ['scc/drivers/remotepad_controller.c']),\n\t\t\t]\n\t)\n\n"
  },
  {
    "path": "tests/README.md",
    "content": "## TESTS\n\nTests are using [pytest framework](https://docs.pytest.org/en/latest/).\n\nTo run all of them, navigate to directory above and do\n`$ PYTHONPATH=. pytest tests`\n"
  },
  {
    "path": "tests/test_boolean.py",
    "content": "from scc.actions import Action, NoAction\n\nclass TestBoolean(object):\n\t\n\tdef test_noaction_is_false(self):\n\t\t\"\"\"\n\t\tTests if None can be used as False boolean value.\n\t\t\"\"\"\n\t\tassert not NoAction()\n\t\tif NoAction():\n\t\t\traise Exception(\"NoAction is True :(\")\n\t\n\t\n\tdef test_action_is_true(self):\n\t\t\"\"\"\n\t\tTests if random action works as True boolean value.\n\t\t\"\"\"\n\t\ta = Action()\n\t\tassert a\n\t\tif a:\n\t\t\treturn\n\t\traise Exception(\"Action is False :(\")\n\n"
  },
  {
    "path": "tests/test_compress.py",
    "content": "from scc.uinput import Keys, Axes, Rels\nfrom scc.constants import SCButtons, HapticPos\nfrom scc.modifiers import DoubleclickModifier\nfrom scc.actions import Action, AxisAction\nfrom scc.macros import Macro\nfrom scc.special_actions import MenuAction\nfrom scc.parser import ActionParser\n\nparser = ActionParser()\n\nCASES = {\n\t# Contains all test cases that are tested by\n\t# test_sensitivity and test_feedback\n\t# This should contain key for every Action that supports setting feedback\n\t# or sensitivity. test_tests method tests whether it realy does.\n\t'axis'  : {\n\t\t'action' : 'axis(ABS_RX)',\n\t\t'sensitivity' : (2.0,)\n\t},\n\t'raxis'  : {\n\t\t'action' : 'axis(ABS_RX)',\n\t\t'sensitivity' : (2.0,)\n\t},\n\t'mouse' : {\n\t\t'action' : 'mouse',\n\t\t'sensitivity' : (2.0, 3.0,),\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'mouseabs' : {\n\t\t'action' : 'mouseabs(REL_X)',\n\t\t'sensitivity' : (2.0, 3.0,)\n\t},\n\t'gyro' : {\n\t\t'action' : 'gyro(ABS_X, ABS_Y, ABS_Z)',\n\t\t'sensitivity' : (2.0, 3.0, 4.0,)\n\t},\n\t'tilt' : {\n\t\t'action' : 'tilt( button(KEY_D), button(KEY_U), button(KEY_L), button(KEY_R) )',\n\t\t'sensitivity' : (2.0, 3.0, 4.0,)\n\t},\n\t'gyroabs' : {\n\t\t'action' : 'gyroabs(ABS_X, ABS_Y, ABS_Z)',\n\t\t'sensitivity' : (2.0, 3.0, 4.0),\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'hatup' :    { 'action' : 'hatup(ABS_X)',    'sensitivity' : (2.0,) },\n\t'hatdown' :  { 'action' : 'hatdown(ABS_X)',  'sensitivity' : (2.0,) },\n\t'hatleft' :  { 'action' : 'hatleft(ABS_X)',  'sensitivity' : (2.0,) },\n\t'hatright' : { 'action' : 'hatright(ABS_X)', 'sensitivity' : (2.0,) },\n\t'button' : {\n\t\t'action' : 'button(KEY_X)',\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'circular' : {\n\t\t'action' : 'mouse(REL_HWHEEL)',\n\t\t'circular' : True,\n\t\t'sensitivity' : (2.0,),\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'circularabs' : {\n\t\t'action' : 'mouse(REL_HWHEEL)',\n\t\t'circularabs' : True,\n\t\t'sensitivity' : (2.0,),\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'XY' : {\n\t\t'X' : { 'action' : 'axis(ABS_X)' },\n\t\t'Y' : { 'action' : 'axis(ABS_Y)' },\n\t\t'sensitivity' : (2.0, 3.0,),\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'relXY' : {\n\t\t'X' : { 'action' : 'axis(ABS_RX)' },\n\t\t'Y' : { 'action' : 'axis(ABS_RY)' },\n\t\t'sensitivity' : (2.0, 3.0,),\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'trigger' : {\n\t\t'action' : 'button(KEY_X)',\n\t\t'levels' : [ 10, 80 ],\n\t\t'feedback' : ('BOTH',)\n\t},\n\t'tilt' : {\n\t\t'action' : 'tilt( button(KEY_D), button(KEY_U), button(KEY_L), button(KEY_R) )',\n\t\t'sensitivity' : (2.0, 3.0, 4.0,)\n\t},\n\t'ball' : {\n\t\t'action' : 'ball(XY(axis(Axes.ABS_RX), axis(Axes.ABS_RY)))',\n\t\t'sensitivity' : (2.0, 3.0),\n\t\t'feedback' : ('BOTH',)\n\t},\n\t\"dpad\" : {\n\t\t\"dpad\": [\n\t\t\t{ \"action\": \"button(Keys.KEY_W)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_S)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_A)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_D)\" }\n\t\t],\n\t\t\"feedback\": [\"LEFT\", 32640]\n\t},\n\t\"dpad8\" : {\n\t\t\"dpad\": [\n\t\t\t{ \"action\": \"button(Keys.KEY_1)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_2)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_3)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_4)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_5)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_6)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_7)\" },\n\t\t\t{ \"action\": \"button(Keys.KEY_8)\" }\n\t\t],\n\t\t\"feedback\": [\"LEFT\", 32640]\n\t},\n\t\"menu\" : {\n\t\t\"action\": \"menu('Default.menu')\", \n\t\t\"feedback\": [\"LEFT\", 32640]\n\t},\n\t\"hold\": {\n\t\t\"action\": \"button(Keys.KEY_W)\",\n\t\t\"hold\": {\n\t\t\t\"action\": \"menu('Default.menu')\"\n\t\t},\n\t\t\"feedback\": [\"LEFT\", 32640]\n\t},\n\t\"hipfire\": {\n                'action': 'hipfire( button(KEY_A), button(KEY_B) )',\n                \"feedback\": ['BOTH']\n\t}\n}\n\nclass TestCompress(object):\n\t\"\"\"\n\tTests Aciton.compress method.\n\tBasically, tests how various combinations of modifiers interacts together.\n\t\"\"\"\n\t\n\tdef test_tests(self):\n\t\t# Test if there is key in CASES for every action that suppports\n\t\t# setting feedback or sensitivity.\n\t\tfor cls in Action.ALL.values():\n\t\t\tif Macro in cls.__bases__:\n\t\t\t\t# Skip macros, they are handled separately\n\t\t\t\tcontinue\n\t\t\tif MenuAction in cls.__bases__ and cls != MenuAction:\n\t\t\t\t# Skip alternate menu types, they all behave in same way\n\t\t\t\tcontinue\n\t\t\tif cls == DoubleclickModifier:\n\t\t\t\t# Tested along with hold\n\t\t\t\tcontinue\n\t\t\tif hasattr(cls, \"set_speed\"):\n\t\t\t\tassert cls.COMMAND in CASES, (\n\t\t\t\t\t\"%s supports setting sensitivity, but \"\n\t\t\t\t\t\"there is no test case it\" % (cls.COMMAND,))\n\t\t\t\tassert 'sensitivity' in CASES[cls.COMMAND], (\n\t\t\t\t\t\"%s supports setting sensitivity, but \"\n\t\t\t\t\t\"case for it has no 'sensitivity' key it\" % (\n\t\t\t\t\tcls.COMMAND,))\n\t\t\tif hasattr(cls, \"set_haptic\"):\n\t\t\t\tassert cls.COMMAND in CASES, (\n\t\t\t\t\t\"%s supports feedback, but there is \"\n\t\t\t\t\t\"no test case it\" % (cls.COMMAND,))\n\t\t\t\tassert 'feedback' in CASES[cls.COMMAND], (\n\t\t\t\t\t\"%s supports feedback, but case for it has \"\n\t\t\t\t\t\"no 'feedback' key it\" % (\n\t\t\t\t\tcls.COMMAND,))\t\t\t\n\t\n\t\n\tdef test_hold_doubleclick(self):\n\t\t\"\"\"\n\t\tTests parsing of hold & doubleclick combination.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : 'axis(ABS_RX)',\n\t\t\t'hold' : { 'action' : \"axis(ABS_X)\" },\n\t\t\t'doubleclick' : { 'action' : \"axis(ABS_Z)\" }\n\t\t}).compress()\n\t\t\n\t\tassert isinstance(a, DoubleclickModifier)\n\t\tassert isinstance(a.normalaction, AxisAction)\n\t\tassert isinstance(a.action, AxisAction)\n\t\tassert isinstance(a.holdaction, AxisAction)\n\t\tassert a.normalaction.id == Axes.ABS_RX\n\t\tassert a.action.id == Axes.ABS_Z\n\t\tassert a.holdaction.id == Axes.ABS_X\n\t\n\t\n\tdef test_sensitivity(self):\n\t\t\"\"\"\n\t\tTests if all sensitivity setting are parsed and applied\n\t\tafter .compress() is called.\n\t\t\"\"\"\n\t\tfor case in CASES:\n\t\t\tif 'sensitivity' in CASES[case]:\n\t\t\t\tprint(\"Testing 'sensitivity' on %s\" % (case,))\n\t\t\t\ta = parser.from_json_data(CASES[case]).compress()\n\t\t\t\tassert (\n\t\t\t\t\ta.get_speed() == CASES[case]['sensitivity']\n\t\t\t\t\tor\n\t\t\t\t\ta.strip().get_speed() == CASES[case]['sensitivity']\n\t\t\t\t)\n\t\n\t\n\tdef test_feedback(self):\n\t\t\"\"\"\n\t\tTests if all feedback setting are parsed and applied\n\t\tafter .compress() is called.\n\t\t\"\"\"\n\t\tfor case in CASES:\n\t\t\tif 'feedback' in CASES[case]:\n\t\t\t\tprint(\"Testing 'feedback' on %s\" % (case,))\n\t\t\t\ta = parser.from_json_data(CASES[case]).compress()\n\t\t\t\tassert a.get_haptic().get_position().name == CASES[case]['feedback'][0]\n\t\n\t\n\tdef test_multi(self):\n\t\t\"\"\"\n\t\tTests if feedback and sensitivity setting are parsed and applied\n\t\tto actions in multiaciton.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : 'circular(REL_HWHEEL) and gyroabs(None, ABS_Y, ABS_Z)',\n\t\t\t'sensitivity' : (2.0, 3.0, 4.0),\n\t\t\t'feedback' : ('BOTH',)\n\t\t}).compress()\n\t\tassert a.actions[0].get_haptic().get_position().name == \"BOTH\"\n\t\tfor action in a.actions:\n\t\t\tassert action.get_speed()[0] == 2.0\n\t\n\t\n\tdef test_macro(self):\n\t\t\"\"\"\n\t\tTests if feedback and sensitivity setting are parsed and applied\n\t\tto actions in basic macro.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : 'circular(REL_HWHEEL) ; gyroabs(None, ABS_Y, ABS_Z)',\n\t\t\t'sensitivity' : (2.0, 3.0, 4.0),\n\t\t\t'feedback' : ('BOTH',)\n\t\t}).compress()\n\t\tfor action in a.actions:\n\t\t\tassert action.get_haptic().get_position().name == \"BOTH\"\n\t\t\tassert action.get_speed()[0] == 2.0\n"
  },
  {
    "path": "tests/test_docs.py",
    "content": "from scc.actions import Action\n\nimport os\n\nclass TestDocs(object):\n\t\"\"\"\n\tTests every glade file in glade/ directory (and subdirectories) for known\n\tproblems that may cause GUI to crash in some environments.\n\t\n\t(one case on one environment so far)\n\t\"\"\"\n\t\n\tdef test_every_action_has_docs(self):\n\t\t\"\"\"\n\t\tTests if every known Action is documentated in docs/actions.md\n\t\t\"\"\"\n\t\t# Read docs first\n\t\twith open(\"docs/actions.md\") as f:\n\t\t\tactions_md = f.read()\n\t\twith open(\"docs/profile-file.md\") as f:\n\t\t\tprofile_md = f.read()\n\t\t\n\t\t# Do stupid fulltext search, because currently it's simply fast enough\n\t\tfor command in Action.ALL:\n\t\t\tif command in (None, 'None', 'exit'):\n\t\t\t\t# Woo for special cases\n\t\t\t\tcontinue\n\t\t\tanchor = '<a name=\"%s\">' % (command,)\n\t\t\tassert anchor in actions_md, \"Action '%s' is not documented in actions.md\" % (command,)\n\t\t\n\t\tfor key in Action.PKEYS:\n\t\t\tanchor = '#### `%s`' % (key,)\n\t\t\tassert key in profile_md, \"Key '%s' is not documented in profile-file.md\" % (key,)\n"
  },
  {
    "path": "tests/test_glade.py",
    "content": "import xml.etree.cElementTree as ET\nimport os\n\ndef _get_files():\n\t\"\"\"\n\tGenerates list of all glade files in glade/ directory.\n\t\"\"\"\n\t# TODO: Caching, when there is more than one test using this\n\trv = []\n\tdef recursive(path):\n\t\tfor f in os.listdir(path):\n\t\t\tfilename = os.path.join(path, f)\n\t\t\tif os.path.isdir(filename):\n\t\t\t\trecursive(filename)\n\t\t\telif filename.endswith(\".glade\"):\n\t\t\t\trv.append(filename)\n\t\n\trecursive(\"glade/\")\n\treturn rv\n\n\ndef _check_ids(el, filename, parent_id):\n\t\"\"\" Recursively walks through tree and check if every object has ID \"\"\"\n\tfor child in el:\n\t\tif child.tag == \"object\":\n\t\t\tmsg = \"Widget has no ID in %s; class %s; Parent id: %s\" % (\n\t\t\t\t\tfilename,\n\t\t\t\t\tchild.attrib['class'],\n\t\t\t\t\tparent_id\n\t\t\t\t)\n\t\t\tassert 'id' in child.attrib and child.attrib['id'], msg\n\t\t\tfor subel in child:\n\t\t\t\tif subel.tag == \"child\":\n\t\t\t\t\t_check_ids(subel, filename, child.attrib['id'])\n\nclass TestGlade(object):\n\t\"\"\"\n\tTests every glade file in glade/ directory (and subdirectories) for known\n\tproblems that may cause GUI to crash in some environments.\n\t\n\t(one case on one environment so far)\n\t\"\"\"\n\t\n\tdef test_every_widget_has_id(self):\n\t\t\"\"\"\n\t\tTests if every defined widget has ID.\n\t\tDummy widgets without ID are OK, in theory, but Ubuntu version\n\t\tof libglade crashes witht them :(\n\t\t\"\"\"\n\t\tfor filename in _get_files():\n\t\t\troot = ET.parse(filename).getroot()\n\t\t\t_check_ids(root, filename, \"<root element>\")\n"
  },
  {
    "path": "tests/test_inputs.py",
    "content": "from scc.constants import STICK_PAD_MIN, STICK_PAD_MAX\nfrom scc.drivers.fake import FakeController\nfrom scc.uinput import Dummy, Keys, Axes\nfrom scc.constants import SCButtons\nfrom scc.parser import ActionParser\nfrom scc.profile import Profile\nfrom scc.scheduler import Scheduler\nfrom scc.mapper import Mapper\nfrom collections import namedtuple\nimport time\n\n\"\"\"\nTests various inputs for crashes and incorrect behaviour,\nmostly using dummy outputs and FakeController\n\"\"\"\n\nFakeControllerInput = namedtuple('FakeControllerInput',\n\t'buttons ltrig rtrig stick_x stick_y lpad_x lpad_y rpad_x rpad_y '\n\t'gpitch groll gyaw q1 q2 q3 q4 '\n)\nZERO_STATE = FakeControllerInput( *[0] * len(FakeControllerInput._fields) )\nparser = ActionParser()\n\ndef input_test(fn):\n\t\"\"\" Decorator that creates usable mapper \"\"\"\n\tdef wrapper(*a):\n\t\t_time = time.time\n\t\t\n\t\tdef fake_time():\n\t\t\treturn fake_time.t\n\t\tdef add(n):\n\t\t\tfake_time.t += n\n\t\tfake_time.t = _time()\n\t\tfake_time.add = add\n\t\ttime.time = fake_time\n\t\t\n\t\tcontroller = FakeController(0)\n\t\tprofile = Profile(parser)\n\t\tscheduler = Scheduler()\n\t\tmapper = Mapper(profile, scheduler, keyboard=False, mouse=False, gamepad=False, poller=None)\n\t\tmapper.keyboard = RememberingDummy()\n\t\tmapper.gamepad = RememberingDummy()\n\t\tmapper.mouse = RememberingDummy()\n\t\tmapper.set_controller(controller)\n\t\tmapper._testing = True\n\t\tmapper._tick_rate = 0.01\n\t\t\n\t\t_mapper_input = mapper.input\n\t\tdef mapper_input(*a):\n\t\t\tadd(mapper._tick_rate)\n\t\t\t_mapper_input(*a)\n\t\t\tscheduler.run()\n\t\tmapper.input = mapper_input\n\t\t\n\t\ta = list(a) + [ mapper ]\n\t\ttry:\n\t\t\treturn fn(*a)\n\t\tfinally:\n\t\t\ttime.time = _time\n\treturn wrapper\n\n\nclass RememberingDummy(Dummy):\n\tdef __init__(self, *a, **b):\n\t\tDummy.__init__(self, *a, **b)\n\t\tself.pressed = set([])\n\t\tself.mouse_x = 0\n\t\tself.mouse_y = 0\n\t\tself.scroll_x = 0\n\t\tself.scroll_y = 0\n\t\tself.axes = {}\n\t\n\tdef axisEvent(self, axis, val):\n\t\tself.axes[axis] = val\n\t\n\t\n\tdef moveEvent(self, dx=0, dy=0):\n\t\tself.mouse_x += dx\n\t\tself.mouse_y += dy\n\t\n\t\n\tdef scrollEvent(self, dx=0, dy=0):\n\t\tself.scroll_x += dx\n\t\tself.scroll_y += dx\n\t\n\t\n\tdef pressEvent(self, keys):\n\t\tfor k in keys:\n\t\t\tassert k not in self.pressed\n\t\t\tself.pressed.add(k)\n\t\n\t\n\tdef releaseEvent(self, keys=[]):\n\t\tfor k in keys:\n\t\t\tif k in self.pressed:\n\t\t\t\tself.pressed.remove(k)\n\n\tdef clearRemainders(self):\n\t\tpass\n\n\nclass TestInputs(object):\n\t@input_test\n\tdef test_button(self, mapper):\n\t\t\"\"\"\n\t\tJust test for test, this should work every time.\n\t\t\"\"\"\n\t\tmapper.profile.buttons[SCButtons.A] = (parser\n\t\t\t.restart(\"button(Keys.KEY_ENTER)\")).parse()\n\t\tstate = ZERO_STATE._replace(buttons=SCButtons.A)\n\t\tmapper.input(mapper.controller, ZERO_STATE, state)\n\t\tassert Keys.KEY_ENTER in mapper.keyboard.pressed\n\t\tmapper.input(mapper.controller, state, state._replace(buttons=0))\n\t\tassert Keys.KEY_ENTER not in mapper.keyboard.pressed\n\t\n\t\n\t@input_test\n\tdef test_trackball(self, mapper):\n\t\t\"\"\"\n\t\tTests trackball emulation\n\t\t\"\"\"\n\t\tmapper.profile.pads[Profile.LEFT] = (parser.restart(\n\t\t\t\"ball(XY(\"\n\t\t\t\"\tmouse(Rels.REL_HWHEEL, 1.0), \"\n\t\t\t\"\tmouse(Rels.REL_WHEEL, 1.0)\"\n\t\t\t\"))\"\n\t\t)).parse()\n\t\t\n\t\t# Create movement over left pad\n\t\tstate = ZERO_STATE\n\t\tfor x in reversed(range(STICK_PAD_MIN * 2 // 3, -10, 1000)):\n\t\t\tnew_state = state._replace(buttons=SCButtons.LPADTOUCH, lpad_x=x)\n\t\t\tmapper.input(mapper.controller, state, new_state)\n\t\t\tstate = new_state\n\t\tassert mapper.mouse.scroll_x == -21000.0\n\t\t# Release left pad\n\t\tmapper.input(mapper.controller, state, ZERO_STATE)\n\t\t# 'Wait' for 2s\n\t\tfor x in range(20):\n\t\t\tmapper.input(mapper.controller, ZERO_STATE, ZERO_STATE)\n\t\tassert int(mapper.mouse.scroll_x) == -24479\n\t\n\t\n\t@input_test\n\tdef test_dpad(self, mapper):\n\t\t\"\"\"\n\t\tTests WSAD\n\t\t\"\"\"\n\t\tmapper.profile.pads[Profile.LEFT] = (parser.restart(\n\t\t\t\"dpad(\"\n\t\t\t\"\tbutton(Keys.KEY_W), button(Keys.KEY_S),\"\n\t\t\t\"\tbutton(Keys.KEY_A), button(Keys.KEY_D))\"\n\t\t)).parse()\n\t\t\n\t\t# Create movements over left pad\n\t\t# - A\n\t\tstate = ZERO_STATE._replace(buttons=SCButtons.LPADTOUCH, lpad_x=STICK_PAD_MIN)\n\t\tmapper.input(mapper.controller, ZERO_STATE, state)\n\t\tassert Keys.KEY_A in mapper.keyboard.pressed\n\t\tmapper.input(mapper.controller, state, ZERO_STATE)\n\t\t# - S\n\t\tstate = ZERO_STATE._replace(buttons=SCButtons.LPADTOUCH, lpad_y=STICK_PAD_MIN)\n\t\tmapper.input(mapper.controller, ZERO_STATE, state)\n\t\tassert Keys.KEY_S in mapper.keyboard.pressed\n\t\tmapper.input(mapper.controller, state, ZERO_STATE)\n\t\t# - D\n\t\tstate = ZERO_STATE._replace(buttons=SCButtons.LPADTOUCH, lpad_x=STICK_PAD_MAX)\n\t\tmapper.input(mapper.controller, ZERO_STATE, state)\n\t\tassert Keys.KEY_D in mapper.keyboard.pressed\n\t\tmapper.input(mapper.controller, state, ZERO_STATE)\n\t\n\t\n\t@input_test\n\tdef test_joystick_camera(self, mapper):\n\t\t\"\"\"\n\t\tTests joystick camera, mapping trackball to right joystick\n\t\t\"\"\"\n\t\tmapper.profile.pads[Profile.RIGHT] = (parser.restart(\n\t\t\t\"ball(XY(\"\n\t\t\t\"\taxis(Axes.ABS_RX),\"\n\t\t\t\"\taxis(Axes.ABS_RY)\"\n\t\t\t\"))\"\n\t\t)).parse()\n\t\t\n\t\t# Create movement over right pad\n\t\tstate = ZERO_STATE\n\t\tfor x in range(10, STICK_PAD_MAX * 2 // 3, 3000):\n\t\t\tnew_state = state._replace(buttons=SCButtons.RPADTOUCH, rpad_x=x)\n\t\t\tmapper.input(mapper.controller, state, new_state)\n\t\t\tstate = new_state\n\t\tassert mapper.gamepad.axes[Axes.ABS_RX] == 3000\n\t\t# Release left pad\n\t\tmapper._tick_rate = 0.001\n\t\tmapper.input(mapper.controller, state, ZERO_STATE)\n\t\t# 'Wait' for 1s\n\t\tfor x in range(100):\n\t\t\tmapper.input(mapper.controller, ZERO_STATE, ZERO_STATE)\n\t\tassert mapper.gamepad.axes[Axes.ABS_RX] == 3510\n\t\t# 'Wait' for another 0.5s\n\t\tfor x in range(50):\n\t\t\tmapper.input(mapper.controller, ZERO_STATE, ZERO_STATE)\n\t\tassert mapper.gamepad.axes[Axes.ABS_RX] == 1570\n\t\t# 'Wait' for long time so stick recenters\n\t\tfor x in range(100):\n\t\t\tmapper.input(mapper.controller, ZERO_STATE, ZERO_STATE)\n\t\tassert mapper.gamepad.axes[Axes.ABS_RX] == 0\n\t\n\t\n\t@input_test\n\tdef test_modeshift(self, mapper):\n\t\t\"\"\"\n\t\tTests WSAD\n\t\t\"\"\"\n\t\tmapper.profile.buttons[SCButtons.A] = (parser.restart(\n\t\t\t\"mode(B, button(Keys.KEY_V), button(Keys.KEY_Y))\"\n\t\t)).parse()\n\t\t\n\t\t# Press single button\n\t\tstate = ZERO_STATE._replace(buttons=SCButtons.A)\n\t\tmapper.input(mapper.controller, ZERO_STATE, state)\n\t\tassert Keys.KEY_Y in mapper.keyboard.pressed\n\t\tmapper.input(mapper.controller, state, ZERO_STATE)\n\t\tassert Keys.KEY_Y not in mapper.keyboard.pressed\n\t\t\n\t\t# Press modeshifting button\n\t\tstate = ZERO_STATE._replace(buttons=SCButtons.B)\n\t\tmapper.input(mapper.controller, ZERO_STATE, state)\n\t\tassert Keys.KEY_Y not in mapper.keyboard.pressed\n\t\tassert Keys.KEY_V not in mapper.keyboard.pressed\n\t\t\n\t\t# Press button again\n\t\t_state, state = state, state._replace(buttons=SCButtons.B | SCButtons.A)\n\t\tmapper.input(mapper.controller, _state, state)\n\t\tassert Keys.KEY_V in mapper.keyboard.pressed\n\t\tassert Keys.KEY_Y not in mapper.keyboard.pressed\n\t\t\n\t\t# Release modeshifting button\n\t\t_state, state = state, state._replace(buttons=SCButtons.A)\n\t\tmapper.input(mapper.controller, _state, state)\n\t\tassert Keys.KEY_V in mapper.keyboard.pressed\n\t\tassert Keys.KEY_Y not in mapper.keyboard.pressed\n\t\t\n\t\t# Release original button and press it again\n\t\t_state, state = state, state._replace(buttons=0)\n\t\tmapper.input(mapper.controller, _state, state)\n\t\tassert Keys.KEY_V not in mapper.keyboard.pressed\n\t\tassert Keys.KEY_Y not in mapper.keyboard.pressed\n\n\t\t_state, state = state, state._replace(buttons=SCButtons.A)\n\t\tmapper.input(mapper.controller, _state, state)\n\t\tassert Keys.KEY_Y in mapper.keyboard.pressed\n"
  },
  {
    "path": "tests/test_parser/__init__.py",
    "content": "from scc.parser import ActionParser\r\n\r\nparser = ActionParser()\r\n\r\ndef _parses_as_itself(action):\r\n\t\"\"\"\r\n\tTests if provided action can be converted to string and\r\n\tparsed back to same action.\r\n\t\"\"\"\r\n\t# Simple\r\n\ta_str = action.to_string()\r\n\tassert parser.restart(a_str).parse().to_string() == a_str\r\n\t# Multiline\r\n\tm_str = action.to_string(True)\r\n\tassert parser.restart(m_str).parse().to_string() == a_str\r\n\treturn True\r\n\r\ndef _parse_compressed(a_str):\r\n\treturn parser.restart(a_str).parse().compress()\r\n"
  },
  {
    "path": "tests/test_parser/test_actions.py",
    "content": "from scc.uinput import Keys, Axes, Rels\nfrom scc.actions import *\nfrom scc.constants import HIPFIRE_SENSIBLE\nfrom . import _parses_as_itself, parser\nimport inspect\n\nclass TestActions(object):\n\t\n\tdef test_tests(self):\n\t\t\"\"\"\n\t\tTests if this class has test for every Action defined in acitons.py.\n\t\t\"\"\"\n\t\tfor cls in Action.ALL.values():\n\t\t\tif \"/actions.py\" in inspect.getfile(cls):\n\t\t\t\tif HatAction in cls.__bases__ or cls in (NoAction,) :\n\t\t\t\t\t# Skip over some hard-coded cases, these have\n\t\t\t\t\t# tests merged together under weird names\n\t\t\t\t\tcontinue\n\t\t\t\tmethod_name = \"test_%s\" % (cls.COMMAND,)\n\t\t\t\tassert hasattr(self, method_name), \\\n\t\t\t\t\t\"There is no test for %s\" % (cls.COMMAND)\n\t\n\tdef test_none(self):\n\t\t\"\"\"\n\t\tTests if everything what should parse as NoAction parses as NoAction.\n\t\t\"\"\"\n\t\tassert not parser.restart(\"None\").parse()\n\t\tassert not parser.restart(\"None()\").parse()\n\t\n\t\n\tdef test_axis(self):\n\t\t\"\"\"\n\t\tTests if AxisAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\t\n\t\t# With no optional parameters\n\t\tassert _parses_as_itself(AxisAction(Axes.ABS_X))\n\t\t# With min and max set\n\t\tassert _parses_as_itself(AxisAction(Axes.ABS_X, -10, 10.0))\n\t\n\t\n\tdef test_raxis(self):\n\t\t\"\"\"\n\t\tTests if RAxisAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\t\n\t\t# With no optional parameters\n\t\tassert _parses_as_itself(RAxisAction(Axes.ABS_X))\n\t\t# With min and max set\n\t\tassert _parses_as_itself(RAxisAction(Axes.ABS_X, -10, 10.0))\n\t\n\t\n\tdef test_hats(self):\n\t\t\"\"\"\n\t\tTests if every Hat* actions can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\t\n\t\tassert _parses_as_itself(HatUpAction(Axes.ABS_X))\n\t\tassert _parses_as_itself(HatDownAction(Axes.ABS_X))\n\t\tassert _parses_as_itself(HatLeftAction(Axes.ABS_X))\n\t\tassert _parses_as_itself(HatRightAction(Axes.ABS_X))\n\n\n\tdef test_mouse(self):\n\t\t\"\"\"\n\t\tTests if MouseAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\t# With axis specified\n\t\tassert _parses_as_itself(MouseAction(Rels.REL_WHEEL))\n\t\t# Without axis (when used as trackbal)\n\t\tassert _parses_as_itself(MouseAction())\n\t\n\t\n\tdef test_mouseabs(self):\n\t\t\"\"\"\n\t\tTests if MouseAbsAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\t# With axis specified\n\t\tassert _parses_as_itself(MouseAbsAction(Rels.REL_X))\n\t\t# Without axis (when used on pad directly)\n\t\tassert _parses_as_itself(MouseAbsAction())\n\t\n\t\n\tdef test_area(self):\n\t\t\"\"\"\n\t\tTests if AreaAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(AreaAction(10, 10, 50, 50))\n\t\n\t\n\tdef test_relarea(self):\n\t\t\"\"\"\n\t\tTests if RelAreaAction can be converted to string\n\t\tand parsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(RelAreaAction(10, 10, 50, 50))\n\t\n\t\n\tdef test_winarea(self):\n\t\t\"\"\"\n\t\tTests if WinAreaAction can be converted to string\n\t\tand parsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(WinAreaAction(10, 10, 50, 50))\n\t\n\t\n\tdef test_relwinarea(self):\n\t\t\"\"\"\n\t\tTests if RelWinAreaAction can be converted to\n\t\tstring and parsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(RelWinAreaAction(10, 10, 50, 50))\n\t\n\t\n\tdef test_gyro(self):\n\t\t\"\"\"\n\t\tTests if GyroAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\t# With one, two and three axes set\n\t\tassert _parses_as_itself(GyroAction(Axes.ABS_X))\n\t\tassert _parses_as_itself(GyroAction(Axes.ABS_X, Axes.ABS_Y))\n\t\tassert _parses_as_itself(GyroAction(Axes.ABS_X, Axes.ABS_Y, Axes.ABS_Z))\n\t\n\t\n\tdef test_gyroabs(self):\n\t\t\"\"\"\n\t\tTests if GyroAbsAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(GyroAbsAction(Axes.ABS_X))\n\t\tassert _parses_as_itself(GyroAbsAction(Axes.ABS_X, Axes.ABS_Y))\n\t\tassert _parses_as_itself(GyroAbsAction(Axes.ABS_X, Axes.ABS_Y, Axes.ABS_Z))\n\t\n\t\n\tdef test_resetgyro(self):\n\t\t\"\"\"\n\t\tTests if ResetGyroAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(ResetGyroAction())\n\t\n\t\n\tdef test_tilt(self):\n\t\t\"\"\"\n\t\tTests if TiltAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\t# With only one button\n\t\tassert _parses_as_itself(TiltAction( ButtonAction(Keys.KEY_D) ))\n\t\t# With all buttons\n\t\tassert _parses_as_itself(TiltAction(\n\t\t\tButtonAction(Keys.KEY_D), ButtonAction(Keys.KEY_U),\n\t\t\tButtonAction(Keys.KEY_L), ButtonAction(Keys.KEY_R)\n\t\t))\n\t\n\t\n\tdef test_trackball(self):\n\t\t\"\"\"\n\t\tTests if TrackballAction can be converted to string and parsed\n\t\tback to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(TrackballAction())\n\t\n\t\n\tdef test_button(self):\n\t\t\"\"\"\n\t\tTests if ButtonAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\t# Simple\n\t\tassert _parses_as_itself(ButtonAction(Keys.BTN_LEFT))\n\t\t# Two buttons\n\t\tassert _parses_as_itself(ButtonAction(Keys.BTN_LEFT, Keys.BTN_RIGHT))\n\t\t# With one trigger setting\n\t\tassert _parses_as_itself(ButtonAction(Keys.BTN_LEFT, Keys.BTN_RIGHT, 10))\n\t\t# With two trigger settings\n\t\tassert _parses_as_itself(ButtonAction(Keys.BTN_LEFT, Keys.BTN_RIGHT, 10, 90))\n\n\n\tdef test_multiaction(self):\n\t\t\"\"\"\n\t\tTests if MultiAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(MultiAction(\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.BTN_MIDDLE)\n\t\t))\n\n\n\tdef test_dpad(self):\n\t\t\"\"\"\n\t\tTests if DPadAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\t# Default diagonal rage\n\t\tassert _parses_as_itself(DPadAction(\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.BTN_MIDDLE),\n\t\t\tButtonAction(Keys.KEY_A)\n\t\t))\n\t\t# Modified diagonal rage\n\t\tassert _parses_as_itself(DPadAction(33,\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.KEY_A),\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.BTN_MIDDLE),\n\t\t))\n\t\n\t\n\tdef test_ring(self):\n\t\t\"\"\"\n\t\tTests if DPadAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(RingAction(0.1,\n\t\t\tDPadAction(\n\t\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\t\tButtonAction(Keys.BTN_MIDDLE),\n\t\t\t\tButtonAction(Keys.KEY_A)\n\t\t\t),\n\t\t\tXYAction(\n\t\t\t\tAxisAction(Axes.ABS_X),\n\t\t\t\tAxisAction(Axes.ABS_Y)\n\t\t\t)\n\t\t))\n\t\n\t\n\tdef test_dpad8(self):\n\t\t\"\"\"\n\t\tTests if DPad8Action can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\t# Default diagonal rage\n\t\tassert _parses_as_itself(DPad8Action(\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.BTN_MIDDLE),\n\t\t\tButtonAction(Keys.KEY_A),\n\t\t\tButtonAction(Keys.KEY_B),\n\t\t\tButtonAction(Keys.KEY_C),\n\t\t\tButtonAction(Keys.KEY_D),\n\t\t\tButtonAction(Keys.KEY_E)\n\t\t))\n\t\t# Modified diagonal rage\n\t\tassert _parses_as_itself(DPad8Action(61,\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.KEY_C),\n\t\t\tButtonAction(Keys.KEY_A),\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.KEY_E),\n\t\t\tButtonAction(Keys.KEY_B),\n\t\t\tButtonAction(Keys.KEY_D),\n\t\t\tButtonAction(Keys.BTN_MIDDLE),\n\t\t))\n\t\n\t\n\tdef test_XY(self):\n\t\t\"\"\"\n\t\tTests if XYAciton can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(XYAction(\n\t\t\tAxisAction(Axes.ABS_X),\n\t\t\tAxisAction(Axes.ABS_Y)\n\t\t))\n\t\n\t\n\tdef test_relXY(self):\n\t\t\"\"\"\n\t\tTests if relXYAciton can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(RelXYAction(\n\t\t\tAxisAction(Axes.ABS_RX),\n\t\t\tAxisAction(Axes.ABS_RY)\n\t\t))\n\t\n\t\n\tdef test_trigger(self):\n\t\t\"\"\"\n\t\tTests if TriggerAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(TriggerAction(\n\t\t\t15, 234,\n\t\t\tButtonAction(Keys.KEY_A)\n\t\t))\n\n\tdef test_hipfire(self):\n\t\t\"\"\"\n\t\tTests if HipfireAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\ta = ButtonAction(Keys.KEY_A)\n\t\tb = ButtonAction(Keys.KEY_B)\n\t\tassert _parses_as_itself(HipfireAction(a, b))\n\t\tassert _parses_as_itself(HipfireAction(49, 253, a, b))\n\t\tassert _parses_as_itself(HipfireAction(a, b, HIPFIRE_SENSIBLE))\n\t\tassert _parses_as_itself(HipfireAction(a, b, HIPFIRE_SENSIBLE, 0.14))\n\t\tassert _parses_as_itself(HipfireAction(49, a, b, HIPFIRE_SENSIBLE, 0.14))\n\t\tassert _parses_as_itself(HipfireAction(49, 253, a, b, HIPFIRE_SENSIBLE, 0.14))\n"
  },
  {
    "path": "tests/test_parser/test_macros.py",
    "content": "from scc.uinput import Keys\nfrom scc.actions import ButtonAction, AxisAction\nfrom scc.macros import *\nfrom . import _parses_as_itself, parser\nimport inspect\n\nclass TestMacros(object):\n\t\n\tdef test_tests(self):\n\t\t\"\"\"\n\t\tTests if this class has test for each known macro-related action defined.\n\t\t\"\"\"\n\t\tfor cls in Action.ALL.values():\n\t\t\tif \"/macros.py\" in inspect.getfile(cls):\n\t\t\t\tmethod_name = \"test_%s\" % (cls.COMMAND,)\n\t\t\t\tassert hasattr(self, method_name), \\\n\t\t\t\t\t\"There is no test for %s\" % (cls.COMMAND)\n\t\n\t\n\tdef test_macro(self):\n\t\t\"\"\"\n\t\tTests if Macro can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(Macro(\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.BTN_MIDDLE)\n\t\t))\n\t\n\t\n\tdef test_type(self):\n\t\t\"\"\"\n\t\tTests if Type macro can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(Type(\"ilovecandy\"))\n\t\n\t\n\tdef test_cycle(self):\n\t\t\"\"\"\n\t\tTests if Cycle can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(Cycle(\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.BTN_MIDDLE)\n\t\t))\n\t\n\t\n\tdef test_repeat(self):\n\t\t\"\"\"\n\t\tTests if Repeat can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(Repeat(ButtonAction(Keys.BTN_LEFT)))\n\t\tassert _parses_as_itself(Repeat(Macro(\n\t\t\tButtonAction(Keys.BTN_LEFT),\n\t\t\tButtonAction(Keys.BTN_RIGHT),\n\t\t\tButtonAction(Keys.BTN_MIDDLE)\n\t\t)))\n\t\n\t\n\tdef test_sleep(self):\n\t\t\"\"\"\n\t\tTests if SleepAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(SleepAction(1.5))\n\t\n\t\n\tdef test_press(self):\n\t\t\"\"\"\n\t\tTests if PressAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(PressAction(Keys.BTN_LEFT))\n\t\n\t\n\tdef test_release(self):\n\t\t\"\"\"\n\t\tTests if ReleaseAction can be converted to string\n\t\tand parsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(ReleaseAction(Keys.BTN_LEFT))\n\t\n\t\n\tdef test_tap(self):\n\t\t\"\"\"\n\t\tTests if TapAction can be converted to string\n\t\tand parsed back to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(TapAction(Keys.BTN_LEFT))\n"
  },
  {
    "path": "tests/test_parser/test_modifiers.py",
    "content": "from scc.actions import Action, ButtonAction, AxisAction, MouseAction, GyroAction\nfrom scc.constants import SCButtons, STICK, HapticPos\nfrom scc.uinput import Keys, Axes, Rels\nfrom scc.modifiers import *\nfrom . import _parses_as_itself, _parse_compressed, parser\nimport inspect\n\nclass TestModifiers(object):\n\t\n\tdef test_tests(self):\n\t\t\"\"\"\n\t\tTests if this class has test for each known modifier defined.\n\t\t\"\"\"\n\t\tfor cls in Action.ALL.values():\n\t\t\tif \"/modifiers.py\" in inspect.getfile(cls):\n\t\t\t\tmethod_name = \"test_%s\" % (cls.COMMAND,)\n\t\t\t\tassert hasattr(self, method_name), \\\n\t\t\t\t\t\"There is no test for %s modifier\" % (cls.COMMAND)\n\t\n\t\n\tdef test_name(self):\n\t\t\"\"\"\n\t\tTests if NameModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"name('Not A Button', button(KEY_A))\").compress()\n\t\tassert isinstance(a, ButtonAction)\n\t\tassert a.name == \"Not A Button\"\n\t\n\t\n\tdef test_click(self):\n\t\t\"\"\"\n\t\tTests if ClickModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"click(button(KEY_A))\")\n\t\tassert isinstance(a, ClickModifier)\n\t\n\t\n\tdef test_pressed(self):\n\t\t\"\"\"\n\t\tTests if ReleasedModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"released(button(KEY_A))\")\n\t\tassert isinstance(a, ReleasedModifier)\n\t\n\t\n\tdef test_released(self):\n\t\t\"\"\"\n\t\tTests if PressedModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"pressed(axis(KEY_A))\")\n\t\tassert isinstance(a, PressedModifier)\t\n\t\n\t\n\tdef test_touched(self):\n\t\t\"\"\"\n\t\tTests if TouchedModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"touched(button(KEY_A))\")\n\t\tassert isinstance(a, TouchedModifier)\n\t\n\t\n\tdef test_untouched(self):\n\t\t\"\"\"\n\t\tTests if UntouchedModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"untouched(button(KEY_A))\")\n\t\tassert isinstance(a, UntouchedModifier)\n\t\n\t\n\tdef test_circular(self):\n\t\t\"\"\"\n\t\tTests if CircularModifier is parsed\n\t\t\"\"\"\n\t\tassert isinstance(_parse_compressed(\"circular(axis(ABS_X))\"), CircularModifier)\n\t\tassert isinstance(_parse_compressed(\"circular(axis(REL_WHEEL))\"), CircularModifier)\n\t\n\t\n\tdef test_circularabs(self):\n\t\t\"\"\"\n\t\tTests if CircularAbsModifier is parsed\n\t\t\"\"\"\n\t\tassert isinstance(_parse_compressed(\"circularabs(axis(ABS_X))\"), CircularAbsModifier)\n\t\tassert isinstance(_parse_compressed(\"circularabs(axis(REL_WHEEL))\"), CircularAbsModifier)\n\t\n\t\n\tdef test_ball(self):\n\t\t\"\"\"\n\t\tTests if BallModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"ball(axis(ABS_X))\")\n\t\tassert isinstance(a, BallModifier)\n\t\tassert isinstance(a.action, AxisAction)\n\t\tassert a.action.id == Axes.ABS_X\n\t\ta = _parse_compressed(\"ball(mouse())\")\n\t\tassert isinstance(a, BallModifier)\n\t\tassert isinstance(a.action, MouseAction)\n\t\n\t\n\tdef test_smooth(self):\n\t\t\"\"\"\n\t\tTests if SmoothModifier is parsed\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"smooth(5, 0.3, axis(ABS_X))\")\n\t\tassert isinstance(a, SmoothModifier)\n\t\tassert isinstance(a.action, AxisAction)\n\t\tassert a.action.id == Axes.ABS_X\n\t\tassert a.level == 5\n\t\tassert a.multiplier == 0.3\n\t\n\t\n\tdef test_deadzone(self):\n\t\t\"\"\"\n\t\tTests if DeadzoneModifier is parsed\n\t\t\"\"\"\n\t\t# Lower only\n\t\ta = _parse_compressed(\"deadzone(100, axis(ABS_X))\")\n\t\tassert isinstance(a, DeadzoneModifier)\n\t\tassert a.lower == 100 and a.upper == STICK_PAD_MAX\n\t\tassert isinstance(a.action, AxisAction)\n\t\tassert a.action.id == Axes.ABS_X\n\t\t# Lower and upper\n\t\ta = _parse_compressed(\"deadzone(100, 2000, axis(ABS_X))\")\n\t\tassert isinstance(a, DeadzoneModifier)\n\t\tassert a.lower == 100 and a.upper == 2000\n\t\tassert isinstance(a.action, AxisAction)\n\t\tassert a.action.id == Axes.ABS_X\n\t\n\t\n\tdef test_mode(self):\n\t\t\"\"\"\n\t\tTests if ModeModifier is parsed\n\t\t\"\"\"\n\t\t# Without default\n\t\ta = _parse_compressed(\"\"\"mode(\n\t\t\tA, axis(ABS_X),\n\t\t\tB, axis(ABS_Y)\n\t\t)\"\"\")\n\t\tassert isinstance(a, ModeModifier)\n\t\tassert isinstance(a.mods[SCButtons.A], AxisAction)\n\t\tassert a.mods[SCButtons.A].id == Axes.ABS_X\n\t\t\n\t\t# With default\n\t\ta = _parse_compressed(\"\"\"mode(\n\t\t\tA, axis(ABS_X),\n\t\t\tB, axis(ABS_Y),\n\t\t\tbutton(KEY_A)\n\t\t)\"\"\")\n\t\tassert isinstance(a, ModeModifier)\n\t\tassert isinstance(a.mods[SCButtons.A], AxisAction)\n\t\tassert isinstance(a.default, ButtonAction)\n\t\tassert a.default.button == Keys.KEY_A\n\t\n\t\n\tdef test_doubleclick(self):\n\t\t\"\"\"\n\t\tTests if DoubleclickModifier is parsed\n\t\t\"\"\"\n\t\t# With doubleclick action only\n\t\ta = _parse_compressed(\"doubleclick(axis(ABS_X))\")\n\t\tassert isinstance(a.action, AxisAction) and a.action.id == Axes.ABS_X\n\t\tassert not a.holdaction and not a.normalaction\n\t\t# With doubleclick and normal action\n\t\ta = _parse_compressed(\"doubleclick(axis(ABS_X), axis(ABS_Y))\")\n\t\tassert isinstance(a.action, AxisAction) and a.action.id == Axes.ABS_X\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_Y\n\t\tassert not a.holdaction\n\t\t# With all parameters\n\t\ta = _parse_compressed(\"doubleclick(axis(ABS_X), axis(ABS_Y), 1.5)\")\n\t\tassert isinstance(a.action, AxisAction) and a.action.id == Axes.ABS_X\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_Y\n\t\tassert not a.holdaction\n\t\tassert a.timeout == 1.5\n\t\n\t\n\tdef test_hold(self):\n\t\t\"\"\"\n\t\tTests if HoldModifier is parsed\n\t\t\"\"\"\n\t\t# With hold action only\n\t\ta = _parse_compressed(\"hold(axis(ABS_X))\")\n\t\tassert isinstance(a.holdaction, AxisAction) and a.holdaction.id == Axes.ABS_X\n\t\tassert not a.action and not a.normalaction\n\t\t# With hold and normal action\n\t\ta = _parse_compressed(\"hold(axis(ABS_X), axis(ABS_Y))\")\n\t\tassert isinstance(a.holdaction, AxisAction) and a.holdaction.id == Axes.ABS_X\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_Y\n\t\tassert not a.action\n\t\t# With all parameters\n\t\ta = _parse_compressed(\"hold(axis(ABS_X), axis(ABS_Y), 1.5)\")\n\t\tassert isinstance(a.holdaction, AxisAction) and a.holdaction.id == Axes.ABS_X\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_Y\n\t\tassert not a.action\n\t\tassert a.timeout == 1.5\t\n\t\n\t\n\tdef test_hold_doubleclick_combinations(self):\n\t\t\"\"\"\n\t\tTests if combinations of DoubleclickModifier and HoldModifier\n\t\tare parsed as expected\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"doubleclick(axis(ABS_X), hold(axis(ABS_Y), axis(ABS_Z)))\")\n\t\tassert isinstance(a.action, AxisAction) and a.action.id == Axes.ABS_X\n\t\tassert isinstance(a.holdaction, AxisAction) and a.holdaction.id == Axes.ABS_Y\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_Z\n\t\ta = _parse_compressed(\"hold(axis(ABS_X), doubleclick(axis(ABS_Y), axis(ABS_Z)))\")\n\t\tassert isinstance(a.holdaction, AxisAction) and a.holdaction.id == Axes.ABS_X\n\t\tassert isinstance(a.action, AxisAction) and a.action.id == Axes.ABS_Y\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_Z\n\t\ta = _parse_compressed(\"doubleclick(hold(axis(ABS_RX), axis(ABS_RY)), axis(ABS_Z))\")\n\t\tassert isinstance(a.action, AxisAction) and a.action.id == Axes.ABS_RY\n\t\tassert isinstance(a.holdaction, AxisAction) and a.holdaction.id == Axes.ABS_RX\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_Z\n\t\ta = _parse_compressed(\"hold(doubleclick(axis(ABS_Z), axis(ABS_RZ)), axis(ABS_X))\")\n\t\tassert isinstance(a.action, AxisAction) and a.action.id == Axes.ABS_Z\n\t\tassert isinstance(a.holdaction, AxisAction) and a.holdaction.id == Axes.ABS_RZ\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.id == Axes.ABS_X\n\t\n\t\n\tdef test_sens(self):\n\t\t\"\"\"\n\t\tTests if SensitivityModifier can be converted to string and parsed\n\t\tback to same.\n\t\t\"\"\"\n\t\t# Simple stuff\n\t\tassert _parse_compressed(\"sens(2, axis(ABS_X))\").strip().get_speed() == (2.0,)\n\t\tassert _parse_compressed(\"sens(2, 3, mouse())\").strip().get_speed() == (2.0, 3.0)\n\t\tassert _parse_compressed(\"sens(2, 3, 4, gyro(ABS_RZ, ABS_RX, ABS_Z))\").strip().get_speed() == (2.0, 3.0, 4.0)\n\t\t\n\t\t# Basic modifiers, sensitivity should always end applied to mouse() action\n\t\ta = _parse_compressed(\"sens(2, 3, click(mouse()))\")\n\t\tassert isinstance(a.action, MouseAction) and a.action.get_speed() == (2.0, 3.0)\n\t\ta = _parse_compressed(\"sens(2, 3, deadzone(2.0, mouse()))\")\n\t\tassert isinstance(a.action, MouseAction) and a.action.get_speed() == (2.0, 3.0)\n\t\t\n\t\t# Special case, sensitivity should be applied to ball(), not mouse()\n\t\ta = _parse_compressed(\"sens(2, 3, ball(mouse()))\")\n\t\tassert isinstance(a.action, MouseAction) and a.action.get_speed() == (1.0, 1.0)\n\t\tassert isinstance(a, BallModifier) and a.get_speed() == (2.0, 3.0)\n\t\n\t\n\tdef test_feedback(self):\n\t\t\"\"\"\n\t\tTests if FeedbackModifier can be converted to string and parsed\n\t\tback to same.\n\t\t\"\"\"\n\t\t# TODO: Here, with actual tests\n\t\tassert _parses_as_itself(FeedbackModifier(HapticPos.BOTH, MouseAction()))\n\t\tassert _parses_as_itself(FeedbackModifier(HapticPos.BOTH, 10, MouseAction()))\n\t\tassert _parses_as_itself(FeedbackModifier(HapticPos.BOTH, 10, 8, MouseAction()))\n\t\tassert _parses_as_itself(FeedbackModifier(HapticPos.BOTH, 10, 8, 512, MouseAction()))\n\t\t# Bellow was failing in past\n\t\tassert _parses_as_itself(FeedbackModifier(HapticPos.LEFT, MouseAction()))\n\t\tassert _parses_as_itself(FeedbackModifier(HapticPos.RIGHT, MouseAction()))\n\t\n\t\n\tdef test_rotate(self):\n\t\t\"\"\"\n\t\tTests if RotateInputModifier can be converted to string and parsed\n\t\tback to same.\n\t\t\"\"\"\n\t\ta = _parse_compressed(\"rotate(61, mouse())\")\n\t\tassert isinstance(a, RotateInputModifier)\n\n"
  },
  {
    "path": "tests/test_parser/test_special_actions.py",
    "content": "from scc.uinput import Keys, Axes, Rels\nfrom scc.constants import SCButtons, STICK\nfrom scc.special_actions import *\nfrom . import _parses_as_itself, parser\nimport inspect\n\nMENU_CLASSES = (MenuAction, HorizontalMenuAction, GridMenuAction,\n\tRadialMenuAction, QuickMenuAction)\n\nclass TestSpecialActions(object):\n\t\n\tdef test_tests(self):\n\t\t\"\"\"\n\t\tTests if this class has test for each known SpecialAction defined.\n\t\t\"\"\"\n\t\tfor cls in Action.ALL.values():\n\t\t\tif \"/special_actions.py\" in inspect.getfile(cls):\n\t\t\t\tif cls in MENU_CLASSES:\n\t\t\t\t\t# Skip over some hard-coded cases, these have\n\t\t\t\t\t# tests merged together under weird names\n\t\t\t\t\tcontinue\n\t\t\t\tmethod_name = \"test_%s\" % (cls.COMMAND,)\n\t\t\t\tassert hasattr(self, method_name), \\\n\t\t\t\t\t\"There is no test for %s\" % (cls.COMMAND)\n\t\n\t\n\tdef test_profile(self):\n\t\t\"\"\"\n\t\tTests if ChangeProfileAction can be converted to string and parsed\n\t\tback to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(ChangeProfileAction(\"profile\"))\n\t\n\t\n\tdef test_shell(self):\n\t\t\"\"\"\n\t\tTests if ShellAction can be converted to string and parsed\n\t\tback to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(ShellCommandAction(\"ls -la\"))\n\t\n\t\n\tdef test_turnoff(self):\n\t\t\"\"\"\n\t\tTests if TurnOffAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(TurnOffAction())\n\t\n\t\n\tdef test_restart(self):\n\t\t\"\"\"\n\t\tTests if RestartDaemonAction can be converted to string and parsed\n\t\tback to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(RestartDaemonAction())\n\t\n\t\n\tdef test_led(self):\n\t\t\"\"\"\n\t\tTests if LockedAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(LedAction(66))\n\t\n\t\n\tdef test_osd(self):\n\t\t\"\"\"\n\t\tTests if OSDAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\t# With text\n\t\tassert _parses_as_itself(OSDAction(\"Hello\"))\n\t\t# With subaction\n\t\tassert _parses_as_itself(OSDAction(TurnOffAction()))\n\t\n\t\n\tdef test_clearosd(self):\n\t\t\"\"\"\n\t\tTests if ClearOSDAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\t# With text\n\t\tassert _parses_as_itself(ClearOSDAction())\n\t\n\t\n\tdef test_menus(self):\n\t\t\"\"\"\n\t\tTests if all Menu*Actions can be converted to string and parsed\n\t\tback to same action.\n\t\t\"\"\"\n\t\tfor cls in MENU_CLASSES:\n\t\t\t# Simple\n\t\t\tassert _parses_as_itself(cls('menu1'))\n\t\t\t# With arguments\n\t\t\tassert _parses_as_itself(cls('menu1', STICK))\n\t\t\tassert _parses_as_itself(cls('menu1', STICK, SCButtons.X))\n\t\t\tassert _parses_as_itself(cls('menu1', STICK, SCButtons.X,\n\t\t\t\tSCButtons.Y))\n\t\t\tassert _parses_as_itself(cls('menu1', STICK, SCButtons.X,\n\t\t\t\tSCButtons.Y, True))\n\t\n\t\n\tdef test_dialog(self):\n\t\t\"\"\"\n\t\tTests if all Menu*Actions can be converted to string and parsed\n\t\tback to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(DialogAction(\"Some Text\",\n\t\t\tNameModifier('Option', OSDAction('display this'))))\n\t\tassert _parses_as_itself(DialogAction(SCButtons.X, \"Some Text\",\n\t\t\tNameModifier('Option', OSDAction('display this'))))\n\t\tassert _parses_as_itself(DialogAction(SCButtons.X, SCButtons.Y,\n\t\t\t\"Some Text\", NameModifier('Option', OSDAction('display this'))))\n\t\tassert _parses_as_itself(DialogAction(SCButtons.X, SCButtons.Y,\n\t\t\t\"Some Text\", NameModifier('Option', OSDAction('display this'))))\n\t\n\t\n\tdef test_position(self):\n\t\t\"\"\"\n\t\tTests if PositionModifier can be converted to string and parsed\n\t\tback to same action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(PositionModifier(14, -34, MenuAction('menu1')))\n\t\n\t\n\tdef test_keyboard(self):\n\t\t\"\"\"\n\t\tTests if KeyboardAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(KeyboardAction())\n\t\n\t\n\tdef test_gestures(self):\n\t\t\"\"\"\n\t\tTests if GesturesAction can be converted to string and parsed back to\n\t\tsame action.\n\t\t\"\"\"\n\t\tassert _parses_as_itself(\n\t\t\tGesturesAction(\n\t\t\t\t'UUDD', KeyboardAction(),\n\t\t\t\t'LRLR', TurnOffAction()\n\t\t\t)\n\t\t)\n\t\n\t\n\tdef test_cemuhook(self):\n\t\t\"\"\"\n\t\tNothing to test here\n\t\t\"\"\"\n\t\tpass\n\n"
  },
  {
    "path": "tests/test_profile/__init__.py",
    "content": "from scc.parser import ActionParser\r\n\r\nparser = ActionParser()\r\n"
  },
  {
    "path": "tests/test_profile/test_actions.py",
    "content": "from scc.uinput import Keys, Axes, Rels\nfrom scc.actions import *\nfrom scc.modifiers import BallModifier\nfrom . import parser\nimport inspect\n\nclass TestActions(object):\n\t\n\t# def test_tests(self):\n\t#\tTests if this class has test for every Action defined in actions.py.\n\t#\tRemoved: profile is not parsed this way anymore, so newly added actions\n\t#\t\t\tdon't have to support what's tested.\n\t\n\t\n\tdef test_none(self):\n\t\t\"\"\"\n\t\tTests if empty json dict or dict without action is parsed NoAction.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({}), NoAction)\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'None' }), NoAction)\n\t\tassert isinstance(parser.from_json_data({ '___' : 'Invalid' }), NoAction)\n\t\n\t\n\tdef test_axis(self):\n\t\t\"\"\"\n\t\tTests if AxisAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'axis(ABS_X)' }), AxisAction)\n\t\tassert parser.from_json_data({ 'action' : 'axis(ABS_X)' }).id == Axes.ABS_X\n\t\n\t\n\tdef test_raxis(self):\n\t\t\"\"\"\n\t\tTests if RAxisAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'raxis(ABS_X)' }), RAxisAction)\n\t\tassert parser.from_json_data({ 'action' : 'raxis(ABS_X)' }).id == Axes.ABS_X\n\t\n\t\n\tdef test_hats(self):\n\t\t\"\"\"\n\t\tTests if every Hat* actions can be parsed correctly from json.\n\t\t\"\"\"\t\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'hatup(ABS_X)' }), HatUpAction)\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'hatdown(ABS_X)' }), HatDownAction)\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'hatleft(ABS_X)' }), HatLeftAction)\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'hatright(ABS_X)' }), HatRightAction)\n\t\t\n\t\tassert parser.from_json_data({ 'action' : 'hatup(ABS_X)' }).id == Axes.ABS_X\n\t\tassert parser.from_json_data({ 'action' : 'hatdown(ABS_X)' }).id == Axes.ABS_X\n\t\tassert parser.from_json_data({ 'action' : 'hatleft(ABS_X)' }).id == Axes.ABS_X\n\t\tassert parser.from_json_data({ 'action' : 'hatright(ABS_X)' }).id == Axes.ABS_X\n\t\t\n\t\tassert parser.from_json_data({ 'action' : 'hatup(ABS_X)' }).min == 0\n\t\tassert parser.from_json_data({ 'action' : 'hatdown(ABS_X)' }).min == 0\n\t\tassert parser.from_json_data({ 'action' : 'hatleft(ABS_X)' }).min == 0\n\t\tassert parser.from_json_data({ 'action' : 'hatright(ABS_X)' }).min == 0\n\t\t\n\t\tassert parser.from_json_data({ 'action' : 'hatup(ABS_X)' }).max == STICK_PAD_MIN + 1\n\t\tassert parser.from_json_data({ 'action' : 'hatdown(ABS_X)' }).max == STICK_PAD_MAX - 1\n\t\tassert parser.from_json_data({ 'action' : 'hatleft(ABS_X)' }).max == STICK_PAD_MIN + 1\n\t\tassert parser.from_json_data({ 'action' : 'hatright(ABS_X)' }).max == STICK_PAD_MAX - 1\n\t\n\t\n\tdef test_mouse(self):\n\t\t\"\"\"\n\t\tTests if MouseAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert parser.from_json_data({ 'action' : 'mouse()' })._mouse_axis == None\n\t\tassert parser.from_json_data({ 'action' : 'trackpad()' })._mouse_axis == None\n\t\tassert parser.from_json_data({ 'action' : 'mouse(REL_WHEEL)' })._mouse_axis == Rels.REL_WHEEL\n\n\n\tdef test_mouseabs(self):\n\t\t\"\"\"\n\t\tTests if MouseAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert parser.from_json_data({ 'action' : 'mouseabs(REL_X)' })._mouse_axis == Rels.REL_X\n\t\tassert parser.from_json_data({ 'action' : 'mouseabs()' })._mouse_axis is None\n\t\n\t\n\tdef test_area(self):\n\t\t\"\"\"\n\t\tTests if AreaAction are parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'area(10, 10, 50, 50)' }), AreaAction)\n\t\tassert parser.from_json_data({ 'action' : 'area(10, 10, 50, 50)' }).coords == (10, 10, 50, 50)\n\t\n\t\n\tdef test_relarea(self):\n\t\t\"\"\"\n\t\tTests if  RelAreaAction are parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'relarea(10, 10, 50, 50)' }), RelAreaAction)\n\t\tassert parser.from_json_data({ 'action' : 'relarea(10, 10, 50, 50)' }).coords == (10, 10, 50, 50)\n\t\n\t\n\tdef test_winarea(self):\n\t\t\"\"\"\n\t\tTests if WinAreaAction are parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'winarea(10, 10, 50, 50)' }), WinAreaAction)\n\t\tassert parser.from_json_data({ 'action' : 'winarea(10, 10, 50, 50)' }).coords == (10, 10, 50, 50)\n\t\n\t\n\tdef test_relwinarea(self):\n\t\t\"\"\"\n\t\tTests if RelWinAreaAction are parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'relwinarea(10, 10, 50, 50)' }), RelWinAreaAction)\n\t\tassert parser.from_json_data({ 'action' : 'relwinarea(10, 10, 50, 50)' }).coords == (10, 10, 50, 50)\n\t\n\t\n\tdef test_gyro(self):\n\t\t\"\"\"\n\t\tTests if GyroAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'gyro(ABS_X)' }), GyroAction)\n\t\t\n\t\tassert parser.from_json_data({ 'action' : 'gyro(ABS_X)' }).axes[0] == Axes.ABS_X\n\t\tassert parser.from_json_data({ 'action' : 'gyro(ABS_X)' }).axes[1] is None\n\t\tassert parser.from_json_data({ 'action' : 'gyro(ABS_X, ABS_Y)' }).axes[1] == Axes.ABS_Y\n\t\tassert parser.from_json_data({ 'action' : 'gyro(ABS_X, ABS_Y)' }).axes[2] is None\n\t\tassert parser.from_json_data({ 'action' : 'gyro(ABS_X, ABS_Y, ABS_Z)' }).axes[2] == Axes.ABS_Z\n\t\n\t\n\tdef test_gyroabs(self):\n\t\t\"\"\"\n\t\tTests if GyroAbsAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'gyroabs(ABS_X)' }), GyroAbsAction)\n\t\t\n\t\tassert parser.from_json_data({ 'action' : 'gyroabs(ABS_X)' }).axes[0] == Axes.ABS_X\n\t\tassert parser.from_json_data({ 'action' : 'gyroabs(ABS_X)' }).axes[1] is None\n\t\tassert parser.from_json_data({ 'action' : 'gyroabs(ABS_X, ABS_Y)' }).axes[1] == Axes.ABS_Y\n\t\tassert parser.from_json_data({ 'action' : 'gyroabs(ABS_X, ABS_Y)' }).axes[2] is None\n\t\tassert parser.from_json_data({ 'action' : 'gyroabs(ABS_X, ABS_Y, ABS_Z)' }).axes[2] == Axes.ABS_Z\n\t\n\t\n\tdef test_resetgyro(self):\n\t\t\"\"\"\n\t\tTests if ResetGyroAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'resetgyro()' }), ResetGyroAction)\n\t\n\t\n\tdef test_tilt(self):\n\t\t\"\"\"\n\t\tTests if TiltAction can be converted to string and\n\t\tparsed back to same action.\n\t\t\"\"\"\n\t\t# With only one button\n\t\tassert parser.from_json_data({\n\t\t\t'action' : 'tilt( button(KEY_D) )'\n\t\t}).actions[0].button == Keys.KEY_D\n\t\t\n\t\t# With all buttons\n\t\tassert parser.from_json_data({\n\t\t\t'action' : 'tilt( button(KEY_D), button(KEY_U), button(KEY_L), button(KEY_R))'\n\t\t}).actions[3].button == Keys.KEY_R\n\t\n\t\n\tdef test_trackball(self):\n\t\t\"\"\"\n\t\tTests if TrackballAction is parsed correctly from json.\n\t\t\"\"\"\n\t\t# assert isinstance(parser.from_json_data({ 'action' : 'trackball' }), TrackballAction)\n\t\ta = parser.from_json_data({ 'action' : 'trackball' })\n\t\tassert isinstance(a, BallModifier)\n\t\tassert isinstance(a.action, MouseAction)\n\t\n\t\n\tdef test_button(self):\n\t\t\"\"\"\n\t\tTests if ButtonAction is parsed correctly from json.\n\t\t\"\"\"\n\t\tassert isinstance(parser.from_json_data({ 'action' : 'button(KEY_X)' }), ButtonAction)\n\t\t\n\t\tassert parser.from_json_data({ 'action' : 'button(KEY_X)' }).button == Keys.KEY_X\n\t\tassert parser.from_json_data({ 'action' : 'button(KEY_X)' }).button2 is None\n\t\tassert parser.from_json_data({ 'action' : 'button(KEY_X, KEY_Z)' }).button == Keys.KEY_X\n\t\tassert parser.from_json_data({ 'action' : 'button(KEY_X, KEY_Z)' }).button2 == Keys.KEY_Z\n\t\n\t\n\tdef test_multiaction(self):\n\t\t\"\"\"\n\t\tTests if MultiAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : 'button(KEY_X) and button(KEY_Y)' })\n\t\tassert isinstance(a, MultiAction)\n\t\tassert isinstance(a.actions[0], ButtonAction)\n\t\tassert a.actions[0].button == Keys.KEY_X\n\t\tassert isinstance(a.actions[1], ButtonAction)\n\t\tassert a.actions[1].button == Keys.KEY_Y\n\t\n\t\n\tdef test_dpad(self):\n\t\t\"\"\"\n\t\tTests if DPadAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'dpad' : [{\n\t\t\t\t'action' : 'button(KEY_A)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_B)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_C)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_D)'\n\t\t\t}]\n\t\t})\n\t\t\n\t\tassert isinstance(a, DPadAction)\n\t\tfor sub in a.actions:\n\t\t\tassert isinstance(sub, ButtonAction)\n\t\n\t\n\tdef test_ring(self):\n\t\t\"\"\"\n\t\tTests if DPadAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'ring' : {\n\t\t\t\t'radius' : 0.3,\n\t\t\t\t'outer' : {\n\t\t\t\t\t'dpad' : [{\n\t\t\t\t\t\t'action' : 'button(KEY_A)'\n\t\t\t\t\t\t} , {\n\t\t\t\t\t\t'action' : 'button(KEY_B)'\n\t\t\t\t\t\t} , {\n\t\t\t\t\t\t'action' : 'button(KEY_C)'\n\t\t\t\t\t\t} , {\n\t\t\t\t\t\t'action' : 'button(KEY_D)'\n\t\t\t\t\t}]\n\t\t\t\t},\n\t\t\t\t'inner' : {\n\t\t\t\t\t'X' : { 'action' : 'axis(ABS_X)' },\n\t\t\t\t\t'Y' : { 'action' : 'axis(ABS_Y)' },\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t\n\t\tassert isinstance(a.outer, DPadAction)\n\t\tfor sub in a.outer.actions:\n\t\t\tassert isinstance(sub, ButtonAction)\n\t\tassert isinstance(a.inner, XYAction)\n\t\tfor sub in a.inner.actions:\n\t\t\tassert isinstance(sub, AxisAction)\n\t\n\t\n\tdef test_dpad8(self):\n\t\t\"\"\"\n\t\tTests if DPad8Action is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'dpad' : [{\n\t\t\t\t'action' : 'button(KEY_A)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_B)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_C)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_D)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_E)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_F)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_G)'\n\t\t\t\t} , {\n\t\t\t\t'action' : 'button(KEY_H)'\n\t\t\t}]\n\t\t})\n\t\t\n\t\tprint(a)\n\t\tprint(a.actions)\n\t\tassert isinstance(a, DPadAction)\n\t\tfor sub in a.actions:\n\t\t\tassert isinstance(sub, ButtonAction)\n\t\n\t\n\tdef test_XY(self):\n\t\t\"\"\"\n\t\tTests if XYAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'X' : { 'action' : 'axis(ABS_X)' },\n\t\t\t'Y' : { 'action' : 'axis(ABS_Y)' },\n\t\t})\n\t\t\n\t\tassert isinstance(a, XYAction)\n\t\tassert isinstance(a.x, AxisAction)\n\t\tassert isinstance(a.y, AxisAction)\n\t\n\t\n\tdef test_trigger(self):\n\t\t\"\"\"\n\t\tTests if TriggerAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : 'button(KEY_X)',\n\t\t\t'levels' : [ 10, 80 ]\n\t\t})\n\t\t\n\t\tassert isinstance(a, TriggerAction)\n\t\tassert isinstance(a.action, ButtonAction)\n\t\tassert a.press_level == 10\n\t\tassert a.release_level == 80\n"
  },
  {
    "path": "tests/test_profile/test_modeshift.py",
    "content": "from scc.uinput import Keys, Axes, Rels\nfrom scc.actions import ButtonAction, AxisAction, GyroAction\nfrom scc.constants import SCButtons, HapticPos\nfrom scc.modifiers import *\nfrom . import parser\nimport inspect\n\nclass TestModeshift(object):\n\t\"\"\"\n\tTests various combinations of modeshift and modifiers.\n\tMost are based on stuff that was failing in past.\n\t\"\"\"\n\t\n\tdef test_146_1(self):\n\t\t\"\"\"\n\t\thttps://github.com/kozec/sc-controller/issues/146\n\t\t\"\"\"\n\t\tSTR = \"mode(LB, dpad(button(Keys.KEY_UP)), rotate(3.8, sens(2.0, 2.0, ball(0.552, mouse()))))\"\n\t\ta = parser.from_json_data({\n\t\t\t\"action\": \"mouse()\",\n\t\t\t\"ball\": [ 0.552 ],\n\t\t\t\"rotate\": 3.8,\n\t\t\t\"sensitivity\": [2.0, 2.0, 1.0],\n\t\t\t\"modes\": {\n\t\t\t\t\"LB\": {\n\t\t\t\t\t\"dpad\": [{\n\t\t\t\t\t\t\"action\": \"button(Keys.KEY_UP)\"\n\t\t\t\t\t}]\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t\n\t\tassert a.to_string() == STR\n\t\tassert isinstance(a, ModeModifier)\n\t\tassert isinstance(a.default, RotateInputModifier)\n\t\tsens = a.default.action\n\t\tassert isinstance(sens, SensitivityModifier)\n\t\tassert tuple(sens.speeds) == (2.0, 2.0, 1.0)\n\t\tball = sens.action\n\t\tassert isinstance(ball, BallModifier)\n\t\tassert ball.friction == 0.552\n\t\n\t\n\tdef test_146_2(self):\n\t\t\"\"\"\n\t\thttps://github.com/kozec/sc-controller/issues/146\n\t\t\"\"\"\n\t\tSTR = \"mode(LGRIP, ball(XY(mouse(Rels.REL_HWHEEL), mouse(Rels.REL_WHEEL))), rotate(3.8, sens(2.0, 2.0, mouse())))\"\n\t\ta = parser.from_json_data({\t\n\t\t\t\"action\": \"mouse()\", \n\t\t\t\"rotate\": 3.8, \n\t\t\t\"sensitivity\": [2.0, 2.0, 1.0],\n\t\t\t\"modes\": {\n\t\t\t\t\"LGRIP\": {\n\t\t\t\t\t\"X\": { \"action\": \"mouse(Rels.REL_HWHEEL)\" }, \n\t\t\t\t\t\"Y\": { \"action\": \"mouse(Rels.REL_WHEEL)\" }, \n\t\t\t\t\t\"ball\": []\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t\t\n\t\tassert a.to_string() == STR\n\t\tassert isinstance(a, ModeModifier)\n\t\tassert isinstance(a.default, RotateInputModifier)\n\t\tsens = a.default.action\n\t\tassert isinstance(sens, SensitivityModifier)\n\t\tassert tuple(sens.speeds) == (2.0, 2.0, 1.0)\n\t\tlgrip = a.mods[SCButtons.LGRIP]\n\t\tassert isinstance(lgrip, BallModifier)\n\t\tassert isinstance(lgrip.action, XYAction)\n"
  },
  {
    "path": "tests/test_profile/test_modifiers.py",
    "content": "from scc.uinput import Keys, Axes, Rels\nfrom scc.actions import ButtonAction, AxisAction, GyroAction\nfrom scc.constants import SCButtons, HapticPos\nfrom scc.modifiers import *\nfrom . import parser\nimport inspect\n\ndef _is_axis_with_value(a, value=Axes.ABS_X):\n\t\"\"\"\n\tCommon part of all tests; Check if parsed action\n\tis AxisAction with given value as parameter.\n\t\"\"\"\n\tassert isinstance(a, AxisAction)\n\tassert a.id == value\n\treturn True\n\n\nclass TestModifiers(object):\n\t\n\tdef test_tests(self):\n\t\t\"\"\"\n\t\tTests if this class has test for each known modifier defined.\n\t\t\"\"\"\n\t\tfor cls in Action.ALL.values():\n\t\t\tif \"/modifiers.py\" in inspect.getfile(cls):\n\t\t\t\tmethod_name = \"test_%s\" % (cls.COMMAND,)\n\t\t\t\tassert hasattr(self, method_name), \\\n\t\t\t\t\t\"There is no test for %s modifier\" % (cls.COMMAND)\n\t\n\t\n\tdef test_name(self):\n\t\t\"\"\"\n\t\tTests if NameModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'name' : 'hithere'\n\t\t})\n\t\t\n\t\t# NameModifier is lost in parsing\n\t\tassert not isinstance(a, NameModifier)\n\t\tassert a.name == 'hithere'\n\t\tassert _is_axis_with_value(a)\n\t\n\t\n\tdef test_click(self):\n\t\t\"\"\"\n\t\tTests if ClickModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'click' : True\n\t\t})\n\t\t\n\t\tassert isinstance(a, ClickModifier)\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_pressed(self):\n\t\t\"\"\"\n\t\tTests if PressedModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"pressed(axis(ABS_X))\" })\n\t\tassert isinstance(a, PressedModifier)\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_released(self):\n\t\t\"\"\"\n\t\tTests if ReleasedModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"released(axis(ABS_X))\" })\n\t\tassert isinstance(a, ReleasedModifier)\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_touched(self):\n\t\t\"\"\"\n\t\tTests if TouchedModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"touched(button(KEY_A))\" })\n\t\tassert isinstance(a, TouchedModifier)\n\t\n\t\n\tdef test_untouched(self):\n\t\t\"\"\"\n\t\tTests if UntouchedModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"untouched(button(KEY_A))\" })\n\t\tassert isinstance(a, UntouchedModifier)\n\t\n\t\n\tdef test_circular(self):\n\t\t\"\"\"\n\t\tTests if CircularModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'circular' : True\n\t\t})\n\t\tassert isinstance(a, CircularModifier)\n\t\n\t\n\tdef test_circularabs(self):\n\t\t\"\"\"\n\t\tTests if CircularModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'circularabs' : True\n\t\t})\n\t\tassert isinstance(a, CircularAbsModifier)\n\t\n\t\n\tdef test_ball(self):\n\t\t\"\"\"\n\t\tTests if BallModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'ball' : True\n\t\t})\n\t\t\n\t\tassert isinstance(a, BallModifier)\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_smooth(self):\n\t\t\"\"\"\n\t\tTests if SmoothModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'smooth' : [ 5, 0.3 ]\n\t\t})\n\t\t\n\t\tassert isinstance(a, SmoothModifier)\n\t\tassert a.level == 5\n\t\tassert a.multiplier == 0.3\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_deadzone(self):\n\t\t\"\"\"\n\t\tTests if DeadzoneModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\t# One parameter\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'deadzone' : { 'upper' : 300 }\n\t\t})\n\t\t\n\t\tassert isinstance(a, DeadzoneModifier)\n\t\tassert a.upper == 300\n\t\tassert _is_axis_with_value(a.action)\n\t\t\n\t\t# Two parameters\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'deadzone' : { 'upper' : 300, 'lower' : 50 }\n\t\t})\n\t\t\n\t\tassert isinstance(a, DeadzoneModifier)\n\t\tassert a.lower == 50\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_sens(self):\n\t\t\"\"\"\n\t\tTests if SensitivityModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\t# Simple\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'sensitivity' : [ 2.0, 3.0, 4.0 ]\n\t\t})\n\t\tassert isinstance(a, SensitivityModifier)\n\t\tassert a.speeds == [ 2.0, 3.0, 4.0 ]\n\t\tassert _is_axis_with_value(a.action)\n\t\t\n\t\t# Hold and doubleclick\n\t\ta = parser.from_json_data({\n\t\t\t'hold' : {\n\t\t\t\t\"action\" : \"mouse(ROLL)\",\n\t\t\t\t'sensitivity' : [ 3.0, 4.0 ]\n\t\t\t},\n\t\t\t\"doubleclick\" : {\n\t\t\t\t\"action\" : \"gyro(ABS_RZ, ABS_RX, ABS_Z)\",\n\t\t\t\t'sensitivity' : [ 7.0, 8.0, 9.0 ]\n\t\t\t},\n\t\t\t\"action\" : \"axis(ABS_Z)\",\n\t\t\t'sensitivity' : [ 10.0, ]\n\t\t}).compress()\n\t\tassert isinstance(a.holdaction, MouseAction) and a.holdaction.get_speed() == ( 3.0, 4.0 )\n\t\tassert isinstance(a.action, GyroAction) and a.action.get_speed() == ( 7.0, 8.0, 9.0 )\n\t\tassert isinstance(a.normalaction, AxisAction) and a.normalaction.get_speed() == ( 10.0, )\n\t\t\n\t\t# Modeshift\n\t\ta = parser.from_json_data({\n\t\t\t'modes' : {\n\t\t\t\t\"A\" : {\n\t\t\t\t\t\"action\" : \"mouse(ROLL)\",\n\t\t\t\t\t'sensitivity' : [ 3.0, 4.0 ]\n\t\t\t\t},\n\t\t\t\t\"B\" : {\n\t\t\t\t\t\"action\" : \"axis(ABS_X)\",\n\t\t\t\t\t'sensitivity' : [ 7.0, ]\n\t\t\t\t},\n\t\t\t\t\"X\" : {\n\t\t\t\t\t\"action\" : \"gyro(ABS_RZ, ABS_RX, ABS_Z)\",\n\t\t\t\t\t'sensitivity' : [ 8.0, 9.0, 10.0 ]\n\t\t\t\t},\n\t\t\t},\n\t\t\t\"action\" : \"axis(ABS_Z)\",\n\t\t\t'sensitivity' : [ 12.0, ]\n\t\t}).compress()\n\t\tassert isinstance(a.mods[SCButtons.A], MouseAction) and a.mods[SCButtons.A].get_speed() == ( 3.0, 4.0 )\n\t\tassert isinstance(a.mods[SCButtons.B], AxisAction) and a.mods[SCButtons.B].get_speed() == ( 7.0, )\n\t\tassert isinstance(a.mods[SCButtons.X], GyroAction) and a.mods[SCButtons.X].get_speed() == ( 8.0, 9.0, 10.0 )\n\t\tassert isinstance(a.default, AxisAction) and a.default.get_speed() == ( 12.0, )\n\t\n\t\n\tdef test_feedback(self):\n\t\t\"\"\"\n\t\tTests if FeedbackModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\t# One parameter\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'feedback' : [ \"BOTH\" ]\n\t\t})\n\t\t\n\t\tassert isinstance(a, FeedbackModifier)\n\t\tassert a.haptic.get_position() == HapticPos.BOTH\n\t\tassert _is_axis_with_value(a.action)\n\t\t\n\t\t# All parameters\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'feedback' : [ \"RIGHT\", 1024, 8, 2048 ]\n\t\t})\n\t\t\n\t\tassert isinstance(a, FeedbackModifier)\n\t\tassert a.haptic.get_position() == HapticPos.RIGHT\n\t\tassert a.haptic.get_amplitude() == 1024\n\t\tassert a.haptic.get_frequency() == 8\n\t\tassert a.haptic.get_period() == 2048\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_rotate(self):\n\t\t\"\"\"\n\t\tTests if RotateInputModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"axis(ABS_X)\",\n\t\t\t'rotate' : 33.14\n\t\t})\n\t\t\n\t\tassert isinstance(a, RotateInputModifier)\n\t\tassert a.angle == 33.14\n\t\tassert _is_axis_with_value(a.action)\n\t\n\t\n\tdef test_mode(self):\n\t\t\"\"\"\n\t\tTests if ModeModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\t# Without default\n\t\ta = parser.from_json_data({\n\t\t\t'modes' : {\n\t\t\t\t'A'  : { 'action' : \"axis(ABS_X)\" },\n\t\t\t\t'B'  : { 'action' : \"axis(ABS_Y)\" },\n\t\t\t\t'LT' : { 'action' : \"axis(ABS_Z)\" },\n\t\t\t}\n\t\t})\n\t\t\n\t\tassert isinstance(a, ModeModifier)\n\t\tassert _is_axis_with_value(a.mods[SCButtons.A],  Axes.ABS_X)\n\t\tassert _is_axis_with_value(a.mods[SCButtons.B],  Axes.ABS_Y)\n\t\tassert _is_axis_with_value(a.mods[SCButtons.LT], Axes.ABS_Z)\n\t\t\n\t\t# With default\n\t\ta = parser.from_json_data({\n\t\t\t'action' : 'axis(ABS_RX)',\n\t\t\t'modes' : {\n\t\t\t\t'X'  : { 'action' : \"axis(ABS_X)\" },\n\t\t\t\t'RT' : { 'action' : \"axis(ABS_Z)\" },\n\t\t\t}\n\t\t})\n\t\t\n\t\tassert isinstance(a, ModeModifier)\n\t\tassert _is_axis_with_value(a.default,  Axes.ABS_RX)\n\t\tassert _is_axis_with_value(a.mods[SCButtons.X], Axes.ABS_X)\n\t\tassert _is_axis_with_value(a.mods[SCButtons.RT], Axes.ABS_Z)\n\t\n\t\n\tdef test_doubleclick(self):\n\t\t\"\"\"\n\t\tTests if DoubleclickModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : 'axis(ABS_RX)',\n\t\t\t'doubleclick' : {\n\t\t\t\t'action' : \"axis(ABS_X)\"\n\t\t\t}\n\t\t})\n\t\t\n\t\tassert isinstance(a, DoubleclickModifier)\n\t\tassert _is_axis_with_value(a.normalaction,  Axes.ABS_RX)\n\t\tassert _is_axis_with_value(a.action, Axes.ABS_X)\n\t\tassert not a.holdaction\n\t\n\t\n\tdef test_hold(self):\n\t\t\"\"\"\n\t\tTests if HoldModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : 'axis(ABS_RX)',\n\t\t\t'hold' : {\n\t\t\t\t'action' : \"axis(ABS_X)\"\n\t\t\t}\n\t\t})\n\t\t\n\t\tassert isinstance(a, HoldModifier)\n\t\tassert _is_axis_with_value(a.normalaction,  Axes.ABS_RX)\n\t\tassert _is_axis_with_value(a.holdaction, Axes.ABS_X)\n\t\tassert not a.action\n"
  },
  {
    "path": "tests/test_profile/test_special_actions.py",
    "content": "from scc.uinput import Keys, Axes, Rels\nfrom scc.constants import SCButtons, HapticPos\nfrom scc.special_actions import *\nfrom scc.actions import ButtonAction\nfrom . import parser\nimport inspect\n\nMENU_CLASSES = (MenuAction, HorizontalMenuAction, GridMenuAction,\n\tRadialMenuAction, QuickMenuAction)\n\nclass TestSpecialActions(object):\n\t\n\t# def test_tests(self):\n\t#\tTests if this class has test for each known SpecialAction defined.\n\t#\tRemoved: profile is not parsed this way anymore, so newly added actions\n\t#\t\t\tdon't have to support what's tested.\n\t\n\t\n\tdef test_profile(self):\n\t\t\"\"\"\n\t\tTests if ChangeProfileAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"profile('xyz')\" })\n\t\tassert isinstance(a, ChangeProfileAction)\n\t\tassert a.profile == \"xyz\"\n\t\n\t\n\tdef test_shell(self):\n\t\t\"\"\"\n\t\tTests if ShellCommandAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"shell('ls -la')\" })\n\t\tassert isinstance(a, ShellCommandAction)\n\t\tassert a.command == \"ls -la\"\n\t\n\t\n\tdef test_turnoff(self):\n\t\t\"\"\"\n\t\tTests if TurnOffAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"turnoff\" })\n\t\tassert isinstance(a, TurnOffAction)\n\t\n\t\n\tdef test_restart(self):\n\t\t\"\"\"\n\t\tTests if RestartDaemonAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"restart\" })\n\t\tassert isinstance(a, RestartDaemonAction)\n\t\n\t\n\tdef test_led(self):\n\t\t\"\"\"\n\t\tTests if LockedAction is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({ 'action' : \"led(66)\" })\n\t\tassert isinstance(a, LedAction)\n\t\tassert a.brightness == 66\n\t\n\t\n\tdef test_osd(self):\n\t\t\"\"\"\n\t\tTests if OSDAction is parsed correctly from json.\n\t\t\"\"\"\n\t\t# With text\n\t\ta = parser.from_json_data({ 'action' : \"osd('something')\" })\n\t\tassert isinstance(a, OSDAction)\n\t\tassert a.text == \"something\"\n\t\t# As modifier\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"button(KEY_X)\",\n\t\t\t'osd' : True\n\t\t})\n\t\tassert isinstance(a, OSDAction)\n\t\tassert isinstance(a.action, ButtonAction)\n\t\n\t\n\tdef test_dialog(self):\n\t\t\"\"\"\n\t\tTests if all Menu*Actions are parsed correctly from json.\n\t\t\"\"\"\n\t\t# Simple\n\t\ta = parser.from_json_data({ 'action' : \"dialog('title', osd('something'))\" })\n\t\tassert isinstance(a, DialogAction)\n\t\tassert a.text == \"title\"\n\t\tassert len(a.options) == 1\n\t\tassert isinstance(a.options[0], OSDAction)\n\t\tassert a.options[0].text == \"something\"\n\n\t\t# Complete\n\t\ta = parser.from_json_data({ 'action' : \"dialog(X, Y, 'title', \"\n\t\t\t\"name('button', osd('something')), name('item', osd('something else')))\" })\n\t\tassert a.confirm_with == SCButtons.X\n\t\tassert a.cancel_with == SCButtons.Y\n\t\tassert isinstance(a, DialogAction)\n\t\tassert a.text == \"title\"\n\t\tassert len(a.options) == 2\n\t\tassert a.options[0].describe(Action.AC_MENU) == \"button\"\n\t\tassert a.options[0].strip().text == \"something\"\n\t\n\t\n\tdef test_menus(self):\n\t\t\"\"\"\n\t\tTests if all Menu*Actions are parsed correctly from json.\n\t\t\"\"\"\n\t\tfor cls in MENU_CLASSES:\n\t\t\ta_str = \"%s('some.menu', LEFT, X, Y, True)\" % (cls.COMMAND,)\n\t\t\ta = parser.from_json_data({ 'action' : a_str })\n\t\t\tassert isinstance(a, cls)\n\t\t\tassert a.control_with == HapticPos.LEFT\n\t\t\tassert a.confirm_with == SCButtons.X\n\t\t\tassert a.cancel_with == SCButtons.Y\n\t\t\tassert a.show_with_release == True\n\t\n\t\n\tdef test_position(self):\n\t\t\"\"\"\n\t\tTests if PositionModifier is parsed correctly from json.\n\t\t\"\"\"\n\t\ta = parser.from_json_data({\n\t\t\t'action' : \"menu('some.menu', LEFT, X, Y, True)\",\n\t\t\t'position' : [ -10, 10 ]\n\t\t}).compress()\n\t\t\n\t\tassert isinstance(a, MenuAction)\n\t\tassert a.x == -10\n\t\tassert a.y ==  10\n\t\n\t\n\tdef test_keyboard(self):\n\t\t\"\"\"\n\t\tTests if KeyboardAction is parsed correctly from json.\n\t\t\"\"\"\n\t\t# With text\n\t\ta = parser.from_json_data({ 'action' : \"keyboard\" })\n\t\tassert isinstance(a, KeyboardAction)\n\t\n\t\n\tdef test_gestures(self):\n\t\t\"\"\"\n\t\tTests if GesturesAction is parsed correctly from json.\n\t\t\"\"\"\n\t\t# Simple\n\t\ta = parser.from_json_data({\n\t\t\t'gestures' : {\n\t\t\t\t'UD' : { 'action' : 'turnoff' },\n\t\t\t\t'LR' : { 'action' : 'keyboard' }\n\t\t\t}\n\t\t})\n\t\tassert isinstance(a, GesturesAction)\n\t\tassert isinstance(a.gestures['UD'], TurnOffAction)\n\t\tassert isinstance(a.gestures['LR'], KeyboardAction)\n\t\t# With OSD\n\t\ta = parser.from_json_data({\n\t\t\t'gestures' : {\n\t\t\t\t'UD' : { 'action' : 'turnoff' },\n\t\t\t},\n\t\t\t'osd' : True\n\t\t})\n\t\tassert isinstance(a, OSDAction)\n\t\tassert isinstance(a.action, GesturesAction)\n\t\tassert isinstance(a.action.gestures['UD'], TurnOffAction)\n\t\n\t\n\tdef test_cemuhook(self):\n\t\t\"\"\"\n\t\tNothing to test here\n\t\t\"\"\"\n\t\tpass\n\n"
  },
  {
    "path": "tests/test_setup.py",
    "content": "import scc\nimport pkgutil\n\nclass TestSetup(object):\n\t\"\"\"\n\tTests if SCC should be installable.\n\t\"\"\"\n\t\n\tdef test_packages(self):\n\t\t\"\"\"\n\t\tTests if every known Action is documentated in docs/actions.md\n\t\t\"\"\"\n\t\ttry:\n\t\t\timport gi\n\t\t\tgi.require_version('Gtk', '3.0') \n\t\t\tgi.require_version('GdkX11', '3.0') \n\t\t\tgi.require_version('Rsvg', '2.0') \n\t\texcept ImportError:\n\t\t\tpass\n\t\t\n\t\tfrom setup import packages\n\t\tfor importer, modname, ispkg in pkgutil.walk_packages(path=scc.__path__, prefix=\"scc.\", onerror=lambda x: None):\n\t\t\tif ispkg:\n\t\t\t\tassert modname in packages, \"Package '%s' is not being installed by setup.py\" % (modname,)\n"
  },
  {
    "path": "tests/test_strings/__init__.py",
    "content": "from scc.parser import ActionParser\nfrom scc.actions import Action\nimport sys\r\n\r\nparser = ActionParser()\r\n\r\ndef _parses_as(a_str, action):\r\n\t\"\"\"\r\n\tTests if action parsed from string equals specified action.\r\n\t\r\n\tDone by parsing string to Action and comparing it using _same_action()\r\n\t\"\"\"\r\n\tparsed = parser.restart(a_str).parse()\r\n\tassert _same_action(parsed, action)\r\n\treturn True\n\n\ndef _same_action(a1, a2):\n\t\"\"\"\n\tTests if two actions are the same.\n\tDone by comparing .parameters list and .to_string() output.\n\t\"\"\"\n\tassert len(a1.parameters) == len(a2.parameters)\n\tfor i in range(0, len(a1.parameters)):\n\t\tif isinstance(a1.parameters[i], Action):\n\t\t\tassert isinstance(a2.parameters[i], Action), \"Parameter missmatch\"\n\t\t\tassert _same_action(a1.parameters[i], a2.parameters[i])\n\t\telse:\n\t\t\tassert a1.parameters[i] == a2.parameters[i], \"Parameter missmatch\"\n\tassert a1.to_string() == a2.to_string()\n\treturn True\n"
  },
  {
    "path": "tests/test_strings/test_keys.py",
    "content": "from scc.uinput import Keys\nfrom scc.lib import IntEnum\n\nclass TestKeys(object):\n    def test_up_str(self):\n        assert isinstance(Keys.KEY_UP, IntEnum)\n        assert Keys.KEY_UP.name == \"KEY_UP\"\n        assert str(Keys.KEY_UP) == \"Keys.KEY_UP\""
  },
  {
    "path": "tests/test_strings/test_modifiers.py",
    "content": "from scc.actions import Action, ButtonAction, AxisAction, MouseAction\nfrom scc.constants import SCButtons, STICK, HapticPos\nfrom scc.uinput import Keys, Axes, Rels\nfrom scc.modifiers import *\nfrom . import _parses_as, parser\nimport inspect\n\nclass TestModifiers(object):\n\t\n\t# TODO: Much more tests\n\t# TODO: test_tests\n\t\n\tdef test_ball(self):\n\t\t\"\"\"\n\t\tTests if BallModifier can be converted from string\n\t\t\"\"\"\n\t\t# All options\n\t\tassert _parses_as(\n\t\t\t\"ball(15, 40, 15, 0.1, 3265, 4, axis(ABS_X))\",\n\t\t\tBallModifier(15, 40, 15, 0.1, 3265, 4, AxisAction(Axes.ABS_X))\n\t\t)\n"
  },
  {
    "path": "tests/test_vdf.py",
    "content": "from scc.lib.vdf import parse_vdf\nfrom scc.foreign.vdf import VDFProfile\nfrom io import StringIO\nimport os\nimport pytest\nimport vdf\n\nclass TestVDF(object):\n\t\"\"\" Tests VDF parser \"\"\"\n\t\n\tdef test_parsing(self):\n\t\t\"\"\" Tests if VDF parser parses VDF \"\"\"\n\t\tsio = StringIO(\"\"\"\n\t\t\"data\"\n\t\t{\n\t\t\t\"version\" \"3\"\n\t\t\t\"more data\"\n\t\t\t{\n\t\t\t\t\"version\" \"7\"\n\t\t\t}\n\t\t}\n\t\t\"\"\")\n\t\tparsed = parse_vdf(sio)\n\t\tassert type(parsed[\"data\"]) == vdf.vdict.VDFDict\n\t\tassert parsed[\"data\"][\"version\"] == \"3\"\n\t\tassert parsed[\"data\"][\"more data\"][\"version\"] == \"7\"\n\t\n\t\n\tdef test_dict_without_key(self):\n\t\t\"\"\"\n\t\tTests if VDF parser throws exception when there is dict with key missing\n\t\t\"\"\"\n\t\tsio = StringIO(\"\"\"\n\t\t\"data\"\n\t\t{\n\t\t\t\"version\" \"3\"\n\t\t\t{\n\t\t\t\t\"version\" \"7\"\n\t\t\t}\n\t\t}\n\t\t\"\"\")\n\t\twith pytest.raises(SyntaxError) as excinfo:\n\t\t\tparsed = parse_vdf(sio)\n\t\n\t\n\tdef test_unclosed_bracket(self):\n\t\t\"\"\"\n\t\tTests if VDF parser throws exception when there is unclosed {\n\t\t\"\"\"\n\t\tsio = StringIO(\"\"\"\n\t\t\"data\"\n\t\t{\n\t\t\t\"version\" \"3\"\n\t\t\t\"more data\" {\n\t\t\t\t\"version\" \"7\"\n\t\t\t}\n\t\t\"\"\")\n\t\twith pytest.raises(SyntaxError) as excinfo:\n\t\t\tparsed = parse_vdf(sio)\n\t\n\t\n\tdef test_too_many_brackets(self):\n\t\t\"\"\"\n\t\tTests if VDF parser throws exception when there is } wihtout matching {\n\t\t\"\"\"\n\t\tsio = StringIO(\"\"\"\n\t\t\"data\"\n\t\t{\n\t\t\t\"version\" \"3\"\n\t\t\t\"more data\" {\n\t\t\t\t\"version\" \"7\"\n\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\"\"\")\n\t\twith pytest.raises(SyntaxError) as excinfo:\n\t\t\tparsed = parse_vdf(sio)\n\t\n\t\n\tdef test_import(self):\n\t\t\"\"\"\n\t\tTests if every *.vdf file in tests/vdfs can be imported.\n\t\t\"\"\"\n\t\tpath = \"tests/vdfs\"\n\t\tfor f in os.listdir(path):\n\t\t\tfilename = os.path.join(path, f)\n\t\t\tprint(\"Testing import of '%s'\" % (filename,))\n\t\t\tVDFProfile().load(filename)\n"
  },
  {
    "path": "update-wiki.py",
    "content": "#!/usr/bin/env python2\nimport sys, os, subprocess\n\ndef try_run(cmd):\n\tif os.system(cmd) != 0:\n\t\tsys.exit(1)\n\n\ndef merge(f1, f2, from_, to):\n\t\"\"\"\n\tMerges lines from line containting 'from_' to line containing 'to'\n\tfrom f1 to f2\n\t\"\"\"\n\tlines1, inside = [], False\n\tfor line in open(f1, \"r\").readlines():\n\t\tif from_ in line.strip(\"\\r\\n\\t \"):\n\t\t\tinside = True\n\t\telif to in line.strip(\"\\r\\n\\t \"):\n\t\t\tinside = False\n\t\tif inside:\n\t\t\tlines1.append(line)\n\t\n\tlines2, inside = [], False\n\tfor line in open(f2, \"r\").readlines():\n\t\tif from_ in line.strip(\"\\r\\n\\t \"):\n\t\t\tinside = True\n\t\t\tlines2 += lines1\n\t\telif to in line.strip(\"\\r\\n\\t \"):\n\t\t\tinside = False\n\t\telif not inside:\n\t\t\tlines2.append(line)\n\t\n\topen(f2, \"w\").write(\"\".join(lines2))\n\n\ndef main():\n\t\n\tif not os.path.exists(\"sc-controller.wiki/.git\"):\n\t\ttry_run(\"git clone 'https://github.com/kozec/sc-controller.wiki.git'\")\n\t\n\tos.chdir(\"sc-controller.wiki\")\n\ttry_run(\"git pull\")\n\ttry_run(\"git reset master\")\n\t\n\tmerge(\n\t\t'../docs/actions.md',\n\t\t'Custom-Action-Examples-and-Explanations.md',\n\t\t'# <a name=\"actions\">',\n\t\t'# <a name=\"examples2\">'\n\t)\n\t\n\ttry_run(\"git commit -a -m \\\"Updated wiki from docs\\\"\")\n\n\nif __name__ == \"__main__\":\n\tmain()"
  }
]