PKGGjIvirtualtouchpad/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from ._info import * from . import platform PKGG`ccvirtualtouchpad/announce.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import zeroconf from ._info import __version__ # The name of the Virtual Touchpad service SERVICE_NAME = '_virtualtouchpad._http._tcp.local.' def announce(ip_address, port): """Announces that *Virtual Touchpad* is available on the local network. :param str ip_address: The IP address on which *Virtual Touchpad* is reachable. :param int port: The port on which to connect to *Virtual Touchpad*. :return: an object with the method ``unregister()``, which must be called to remove the service from the system """ import getpass import socket import types result = zeroconf.Zeroconf() info = zeroconf.ServiceInfo( SERVICE_NAME, '%s@%s.%s' % (getpass.getuser(), socket.gethostname(), SERVICE_NAME), socket.inet_aton(ip_address), port, 0, 0, { 'version': '.'.join(str(v) for v in __version__)}) result.register_service(info) def unregister(self): """Unregisters the Virtual Touchpad service. """ self.unregister_service(info) self.close() result.unregister = types.MethodType(unregister, result) return result PKHHPCCvirtualtouchpad/_info.py# coding=utf8 __version__ = (0, 13) __author__ = 'Moses Palmér' PKiHH _virtualtouchpad/__main__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import logging import socket from argparse import ArgumentParser try: from virtualtouchpad import systray except ImportError: systray = None from .server import main log = logging.getLogger('virtualtouchpad') def _get_local_address(default=socket.gethostname()): """Returns the address for the local network. The one returned is the one most likely on a *LAN*. If no probable match is found, or an error occurs, ``default`` is returned. :param default: The default return value if none can be calculated. :type default: str :return: the address """ try: import netifaces except ImportError: return default # Get all interfaces interfaces = { interface: netifaces.ifaddresses(interface) for interface in netifaces.interfaces()} # Get all IPv4 interfaces interfaces4 = { key: value[netifaces.AF_INET] for key, value in interfaces.items() if netifaces.AF_INET in value} # Get the IP address with the longest net mask best_length = 0 result = None for interface, descriptions in interfaces4.items(): for description in descriptions: if not 'broadcast' in description: continue # Count the number of non-0 in the net mask current_length = len([ p for p in description['netmask'].split('.') if int(p)]) if current_length < best_length: continue best_length = current_length result = description if result: return result['addr'] else: return default def start(): try: from . import announce except ImportError: announce = None parser = ArgumentParser( description='Turns your mobile or tablet into a touchpad for your ' 'computer.') parser.add_argument( '--port', type=int, help='The port on which to listen', default=16080) parser.add_argument( '--log-level', type=str, help='The log level to use.', choices=['debug', 'info', 'warning', 'error', 'critical'], default='error') args = parser.parse_args() logging.basicConfig( level=getattr(logging, args.log_level.upper())) address = _get_local_address() if systray: icon = systray.SystemTrayIcon('Virtual Touchpad - http://%s:%d' % ( address, args.port), lambda: None) else: icon = None try: if announce: announcer = announce.announce(address, args.port) try: main(address=address, **vars(args)).serve_forever() except KeyboardInterrupt: log.info('Interrupted, terminating') except: log.exception('An unhandler exception occurred in main') finally: if announce: announcer.unregister() finally: if icon: icon.destroy() if __name__ == '__main__': try: start() except Exception as e: log.exception('An unhandled exception occurred') PKGGJC]] virtualtouchpad/html/favicon.ico (F@@ (Bn  H h>Y( &,::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::7+%#NzyG]SbO KE/#K9 iOp  ia FC  /& +++ www&&&  }? ) ```lll  U"""{{{ 777bbbxxxzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz{{{{{{{{{|||||||||||||||||||||||||||||||||||||||}}}}}}sssWWW*** Ccccggg !!!iii::: DMMM 333aaa,pddd &&&[[[ XPPP 111 4bbb ---''' qqqxxx "d TTT fffwwwD ___ ggg000444i  \\\ GGGBBB  LLL  TTT+++AAA ZZZ WWWBBB <<<zzzGGGEEE mmmyyyEEE 222'''EEE ~~~}}}EEE ,,,444GGG  hhhGGG $$$JJJ=== &&&GGG!!! LLL555GGG !!!""""""""" YYY555III!!!"""""""""""""""444$$$555III!!!"""""""""""""""nnnuuu555III!!!"""""""""""""""333555III!!!"""###%%%%%%%%% OOO###555KKK$$$%%%%%%%%%%%%%%% GGG###555LLL$$$%%%%%%%%%%%%%%% <<<)))###555LLL$$$%%%%%%%%%%%%%%% rrrkkk###555KKK$$$%%%%%%&&&(((((("""$$$<<<$$$555LLL&&&(((((((((((((((###MMM%%%555NNN'''(((((((((((((((###zzz\\\%%%555NNN'''(((((((((((((((###+++'''%%%555NNN'''((((((((()))+++%%%KKK '''555OOO'''***++++++++++++%%%FFF """""""""(((555QQQ***+++++++++++++++%%%,,,### !!!!!!!!!""""""""""""""""""(((555QQQ***+++++++++++++++%%%___uuu!!!!!!!!!""""""""""""""""""""""""""""""(((555QQQ***++++++++++++---'''=== !!!"""""""""""""""""""""""""""""""""""""""######(((555QQQ***,,,---.........(((,,,+++ """"""""""""""""""""""""""""""""""""""""""#########%%%%%%***555SSS---...............(((XXXnnn !!!!!!!!!"""""""""""""""""""""""""""""""""""""""###$$$$$$$$$%%%%%%%%%%%%%%%+++555SSS---...............(((GGG!!!!!!!!!""""""""""""""""""""""""""""""""""""""""""$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%+++555SSS---............///)))...---!!!"""""""""""""""""""""""""""""""""""""""""""""###%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%+++555SSS---...///111111111***QQQ""""""""""""""""""""""""""""""""""""######$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&'''(((---555UUU000111111111111111***vvvUUU""""""""""""""""""""""""#########$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&'''((((((((((((---555VVV000111111111111111***,,,+++!!!"""""""""""""""$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%'''''''''((((((((((((((((((((((((---555VVV000111111111111222,,, !!!!!!===""""""###$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&((('''((((((((((((((((((((((((((((((((((((...555VVV000111222444444555... !!!""""""""""""""" VVV\\\###%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&'''((((((((((((((((((((((((((((((((((((((()))))))))***000555WWW444555555555555555..."""""""""""""""""""""&&&666%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&(((((((((((((((((((((((((((((((((((((((((()))))))))++++++++++++000555YYY444555555555555555...""""""""""""""""""""",,,)))%%%%%%%%%%%%%%%%%%%%%%%%'''''''''((((((((((((((((((((((((((((((((((((((((((*********+++++++++++++++++++++000555YYY444555555555555555..."""""""""""""""###$$$777'''%%%%%%%%%%%%&&&''''''(((((((((((((((((((((((((((((((((((((((((()))******+++++++++++++++++++++++++++++++++111555YYY444555555666777777///######$$$%%%%%%%%%%%%]]]TTT%%%&&&'''((((((((((((((((((((((((((((((((((((((()))))))))***+++++++++++++++++++++++++++++++++++++++,,,,,,,,,222555YYY555777777777777777000!!!%%%%%%%%%%%%%%%%%%&&&;;;'''(((((((((((((((((((((((((((((((((((()))))))))++++++++++++++++++++++++++++++++++++++++++,,,,,,,,,.........333555ZZZ666777777777777777000"""%%%%%%%%%%%%%%%%%%'''111'''(((((((((((((((((((((((()))*********+++++++++++++++++++++++++++++++++++++++,,,---------..................333555ZZZ666777777777777777000"""%%%%%%%%%%%%%%%%%%111vvv'''(((((((((((((((((()))******++++++++++++++++++++++++++++++++++++++++++---------..............................333555ZZZ666777777777999:::222"""&&&&&&&&&'''(((&&&OOO^^^$$$((())))))***++++++++++++++++++++++++++++++++++++++++++,,,,,,---..........................................///444555[[[777:::;;;;;;;;;<<<333###(((((((((((((((&&&mmmOOO&&&++++++++++++++++++++++++++++++++++++++++++,,,,,,,,,........................................../////////111111666555]]]:::;;;;;;;;;;;;;;;333 $$$(((((((((((((((%%%AAA)))++++++++++++++++++++++++++++++,,,---------.......................................///000000000111111111111111666555^^^:::;;;;;;;;;;;;;;;333 $$$(((((((((((((((%%%111***+++++++++++++++++++++---------..........................................000000000111111111111111111111111111666555^^^:::;;;;;;;;;;;;<<<444 $$$((((((((())))))>>>&&&++++++++++++,,,---.............................................///000111111111111111111111111111111111111222222777555^^^:::;;;============555 %%%***+++++++++(((QQQlll(((,,,,,,..........................................//////000111111111111111111111111111111111111111222222333555555:::555___<<<===============555 '''++++++++++++(((ZZZ___+++..................................../////////000111111111111111111111111111111111111222333333444555555555555555:::555```<<<===============555 '''++++++++++++'''gggNNN,,,...........................000000000111111111111111111111111111111111111111333444444555555555555555555555555555:::555```<<<============>>>666 '''++++++++++++'''wwwAAA---...............///000000111111111111111111111111111111111111111222444444444555555555555555555555555555555555555:::555```<<<===>>>??????@@@777 (((,,,---......'''+++...//////000111111111111111111111111111111111111111222222333555555555555555555555555555555555555555555666555666777<<<555aaa???@@@@@@@@@@@@@@@777 ***............///,,,111111111111111111111111111111111111111222333333444555555555555555555555555555555555555555666666666666777777777777<<<555bbb???@@@@@@@@@@@@@@@777 ***.........---999...111111111111111111111111111111333333333555555555555555555555555555555555555555555666666666777777777777777777777777<<<666bbb???@@@@@@@@@@@@@@@777 ***.........---BBB///111111111111111111222444444444555555555555555555555555555555555555555666777777777777777777777777777777777777777777;;;aaa???@@@@@@AAABBBCCC999 ***//////000///KKKzzz///222222222333555555555555555555555555555555555555555555555555666777777777777777777777777777777777777777888888888999>>>QQQAAACCCCCCCCCCCCDDD::: ,,,111111111///KKK{{{000444555555555555555555555555555555555555555666666666777777777777777777777777777777777777777777888888888:::;;;;;;;;;<<<~~~EEECCCCCCCCCCCCCCCCCC::: ,,,111111111///KKK}}}333555555555555555555555555555555555666666666777777777777777777777777777777777777777888999999999;;;;;;;;;;;;;;;;;;;;;:::RRRDDDCCCCCCCCCCCCCCCCCC::: ,,,111111111///KKK|||333555555555555555555555666777666777777777777777777777777777777777777777777888:::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;===RRRCCCCCCCCCCCCDDDEEEFFF<<< ---222222444333NNN|||333555555555555666777777777777777777777777777777777777777888888888:::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<;;;JJJmmmCCCCCCEEEFFFFFFFFFFFFGGG=== 000555555555333NNN}}}444666777777777777777777777777777777777777777777888888888;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<===============<<>>===JJJ```kkkxxx|||{{{{{{{{{{{{{{{{{{{{{{{{zzz{{{}}}}}}}}}}}}}}}}}}~~~}}}sssiiiZZZFFFDDDFFFFFFFFFFFFFFFFFFFFFFFFGGGIII>>> 000666555555666>>>333777777777888888:::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<================================================???@@@@@@@@@@@@???======<<<<<<<<<<<<<<<<<<<<<<<<<<<>>><<>>>>>>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAACCCCCCCCCCCC???gggAAAFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGGIIIIIIIIIIIIIIIIIIIIIJJJ??? 222777777777777222III:::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<=======================================>>>>>>>>>???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAABBBCCCCCCCCCCCCCCCCCCCCCEEEXXXEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGGHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII??? 222777777777777333rrrYYY999;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<==========================================?????????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAA]]]GGGFFFFFFFFFFFFFFFFFFFFFFFFGGGHHHHHHHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIKKKAAA 222777777777888777iiittt777;;;;;;;;;;;;;;;================================================>>>?????????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDCCCFFFFFFFFFFFFFFFHHHHHHHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIKKKKKKKKKLLLBBB 333999;;;;;;;;;999ZZZ>>>;;;<<<==========================================>>>>>>>>>@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAACCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDFFFFFFEEEUUUeeeEEEGGGGGGIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJJJJJJJLLLLLLLLLLLLLLLLLLMMMBBB 666;;;;;;;;;;;;;;;>>>UUU;;;=================================>>>>>>>>>???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDEEEFFFFFFFFFFFFFFFFFFIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJJJJJJJKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMBBB 666;;;;;;;;;;;;;;;888qqq;;;========================?????????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@BBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFFFF```zzzIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIKKKKKKKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMCCC 666;;;;;;;;;;;;;;;<<<\\\CCC<<<=========>>>?????????@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCEEEEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEMMMTTTIIIIIIIIIIIIIIIIIIIIIJJJKKKKKKKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMNNNNNNOOODDD 666<<<<<<============AAAhhh===???@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGFFFHHH~~~IIIIIIIIIJJJJJJJJJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMOOOOOOOOOOOOOOOPPPDDD 777==================>>>NNN@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@AAAAAAAAABBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGGHHHIIIJJJWWWIIIJJJKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMMMMMMMNNNOOOOOOOOOOOOOOOOOOOOOOOOPPPCCC555=====================XXX>>>@@@@@@@@@@@@@@@@@@@@@BBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCEEEEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFHHHHHHHHHIIIIIIIIIIIIIIIsssJJJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO:::222===============>>>???DDDnnn???@@@@@@AAABBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDEEEEEEEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGHHHHHHHHHIIIIIIIIIIIIIIIHHHKKKQQQKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMNNNNNNNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPRRRRRR666+++======???@@@@@@@@@@@@???]]]GGGBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIGGGUUU]]]JJJLLLLLLLLLLLLLLLLLLLLLNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPQQQSSSRRRSSSSSSRRR---(((???@@@@@@@@@@@@@@@@@@@@@@@@KKKBBBCCCCCCCCCCCCCCCCCCDDDDDDDDDEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGGGGHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHGGGiii}}}LLLLLLLLLMMMMMMMMMNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPPPPPPPRRRSSSSSSSSSSSSSSSSSSSSSRRR'''???@@@@@@@@@@@@@@@@@@@@@???NNNUUUAAACCCCCCDDDDDDEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGGGGHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHXXXMMMMMMMMMNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPQQQQQQQQQSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSQQQ <<<@@@@@@@@@@@@@@@AAABBBBBB@@@lll|||MMMFFFEEEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFGGGHHHHHHHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJJJJKKKZZZPPPNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOPPPRRRRRRRRRSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSUUUGGG dY...AAA@@@BBBCCCCCCCCCCCCCCCCCCEEETTTFFFDDDEEEFFFFFFFFFFFFFFFFFFHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJIIIIIIPPP]]]VVVNNNOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOORRRRRRRRRSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSTTTUUUUUUUUUVVV4447*!!!CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCJJJ```RRRFFFCCCEEEHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJJJJIIIJJJHHHJJJXXXgggyyyUUUNNNOOOOOOOOOOOOOOOOOOPPPPPPPPPSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSTTTTTTUUUUUUUUUUUUUUUUUUUUUSSS 888DDDCCCCCCCCCCCCCCCCCCCCCDDDDDDJJJjjjQQQFFFFFFGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHIIIHHHRRRjjjPPPOOOOOOOOOOOOPPPQQQQQQQQQSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUWWW>>> |c!!!BBBCCCCCCCCCDDDEEEEEEEEEFFFFFFEEEHHH{{{}}}lllkkkjjjxxxnnnNNNOOOOOOQQQRRRQQQRRRSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVVVPPP D4 444FFFEEEEEEFFFFFFFFFFFFFFFFFFFFFEEEGGGjjj[[[NNNRRRRRRRRRSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUXXXZZZ???#@@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFcccVVVQQQSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVVVVVVVVVXXXYYYYYYYYYMMMv4"""DDDFFFFFFFFFFFFFFFGGGGGGGGGHHHIIIIIIHHH[[[eeeSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVVVWWWWWWWWWYYYYYYYYYYYYYYYYYYSSS!!!$***DDDFFFGGGHHHHHHIIIIIIIIIIIIIIIIIIIIIHHHEEE[[[bbbPPPRRRSSSSSSSSSSSSSSSSSSSSSTTTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUWWWWWWWWWXXXYYYYYYYYYYYYYYYYYYYYYYYYSSS+++d*)))EEEIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHGGG^^^___PPPRRRSSSSSSSSSSSSSSSSSSTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUXXXXXXXXXYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYZZZSSS+++p###BBBJJJIIIIIIIIIIIIIIIIIIJJJIIIJJJLLLLLLLLLJJJMMMWWW}}}pppYYYRRRRRRSSSSSSSSSSSSTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVVVVVVVVVYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYZZZ[[[MMM"""P777GGGJJJJJJJJJJJJKKKLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNXXXyyy]]]UUUTTTSSSSSSSSSTTTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUVVVVVVVVVWWWYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYZZZZZZ\\\UUUAAA # :::KKKMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNMMMLLLLLLNNN___xxxkkkTTTPPPRRRSSSSSSTTTTTTTTTUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUWWWWWWWWWXXXYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYZZZZZZZZZ[[[\\\YYYCCC"""5 ///BBBJJJKKKKKKLLLLLLMMMMMMMMMOOOOOOOOOPPPPPPPPPPPPPPPNNNMMMLLLRRRYYYYYY___gggiiikkkrrrwwwwwwrrrkkkhhh^^^[[[RRRNNNPPPQQQRRRSSSTTTUUUUUUUUUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVXXXYYYYYYZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ[[[ZZZZZZZZZYYYYYYMMM777 #R $$$,,,222;;;DDDCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCDDDEEEEEEEEEEEEEEEDDDDDDEEEEEEEEEFFFFFFGGGGGGGGGHHHIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMMMNNNNNNNNNMMMCCC<<<111*** 6Q65!fP zj5w%%O}D$ <dd9??~~>>~(@ @Brq@'y!vn'.& %%%}}}www"""  F  9 qqq """nnnkkk###>!!! ...,,, 0pppEEE kkkrrrq   ,,,===SSS'''  kkkFFF <<<///222 ???mmm ~~~444 ZZZQQQ???555 AAA666 HHHbbbBBB___VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVnnn777  %%%CCC !!!999 """222{{{DDD$$$:::!!!###sss888FFF$$$;;;"""%%%!!!GGG$$$<<<$$$&&&OOOTTTHHH$$$===%%%(((!!!&&&III$$$???''')))"""---JJJ$$$@@@(((***$$$```DDD !!!!!!KKK$$$BBB***+++$$$$$$ !!!!!!"""""""""""" LLL$$$BBB+++---&&&222uuu !!!!!!"""""""""""""""######$$$"""MMM$$$DDD---...'''\\\BBB!!!!!!"""""""""""""""######$$$%%%%%%%%%%%%###NNN$$$EEE---000((((((""""""""""""######$$$%%%%%%%%%%%%%%%&&&&&&'''%%%PPP$$$GGG///111))) ---######$$$%%%%%%%%%%%%%%%%%%&&&''''''(((((((((&&&PPP$$$HHH000333+++"""""" JJJQQQ"""%%%%%%%%%%%%&&&&&&''''''((((((((((((((())))))(((RRR$$$JJJ333666---""""""ttt555$$$&&&&&&''''''((((((((((((((())))))******++++++)))SSS$$$KKK444666--- $$$$$$&&&((('''((((((((((((((())))))***+++++++++++++++,,,,,,***TTT$$$LLL555777...!!!%%%%%%///vvv%%%(((((())))))***++++++++++++++++++,,,------......,,,UUU$$$LLL666888000"""'''&&&EEEWWW'''***+++++++++++++++,,,,,,------...............///---VVV$$$OOO999;;;222###(((%%%^^^AAA***++++++,,,,,,------...............//////000000111///WWW$$$PPP:::<<<222$$$***'''111,,,------...............//////000111111111111111222111YYY$$$QQQ;;;===333&&&+++)))///............//////000111111111111111222333444444555333[[[$$$RRR<<<>>>444&&&---,,,,,,//////000111111111111111222333444444555555555555555333\\\ ###SSS>>>@@@666(((...444}}}---111111111111222333444444555555555555555666666666777555\\\222UUU???AAA666)))...>>>nnn...222333444444555555555555555666666777777777777777777666WWWQQQAAACCC888+++000BBBmmm222555555555555555666666666777777777777777888999:::::::::FFFFFFCCCDDD999,,,111CCCnnn222555666666777777777777777777888999::::::;;;;;;;;;;;;;;;:::yyypppBBBDDDFFF;;;///444EEEooo444777777777777777888999::::::;;;;;;;;;;;;;;;<<<<<<<<<======???zzzwwwFFFFFFFFFGGG;;;///555>>>}}}444777888999::::::;;;;;;;;;;;;;;;<<<<<<==================>>>>>>===KKK^^^gggggghhhhhhfffyyyiiikkkkkklllkkkaaaOOODDDFFFGGGGGGHHH===000777777777:::;;;;;;;;;;;;;;;<<<<<<<<<===============>>>>>>???@@@@@@@@@@@@??????>>>???@@@@@@>>>MMMBBBCCCDDDDDDDDDEEEGGGHHHHHHIIIIIIJJJ>>>111888666===;;;;;;<<<<<<==================>>>>>>??????@@@@@@@@@@@@@@@AAABBBBBBCCCCCCCCCCCCCCCJJJFFFFFFGGGGGGHHHHHHIIIIIIIIIIIIIIIJJJKKK??? 333;;;888QQQ;;;===============>>>>>>???@@@@@@@@@@@@@@@AAAAAABBBBBBCCCCCCCCCCCCCCCDDDEEEEEECCCwwwEEEHHHIIIIIIIIIIIIIIIJJJKKKKKKLLLLLLMMM@@@ 444<<<:::XXX}}}:::===>>>>>>??????@@@@@@@@@@@@@@@AAABBBBBBCCCCCCCCCCCCCCCDDDDDDEEEFFFFFFFFFEEEUUUVVVHHHIIIIIIJJJJJJKKKLLLLLLLLLLLLLLLLLLNNNAAA555===<<>>@@@@@@@@@@@@AAAAAABBBBBBCCCCCCCCCCCCCCCDDDEEEEEEFFFFFFFFFFFFFFFGGGFFFMMMHHHKKKKKKLLLLLLLLLLLLLLLMMMMMMNNNNNNOOOPPPBBB333>>>===;;;CCC@@@AAABBBBBBCCCCCCCCCCCCCCCDDDEEEEEEFFFFFFFFFFFFFFFGGGGGGHHHHHHGGGQQQWWWKKKLLLLLLLLLMMMMMMNNNNNNOOOOOOOOOOOOOOOQQQ>>>///@@@@@@???KKKLLLAAACCCCCCCCCDDDEEEEEEFFFFFFFFFFFFFFFGGGGGGHHHHHHIIIIIIHHHGGGcccpppJJJMMMMMMNNNNNNOOOOOOOOOOOOPPPPPPQQQRRRRRRTTT999&&&AAA@@@AAA???kkkdddFFFCCCEEEFFFFFFFFFFFFGGGGGGHHHHHHIIIIIIIIIHHHGGGHHH]]]MMMNNNOOOOOOOOOOOOOOOPPPQQQRRRRRRSSSSSSSSSSSSTTT,,,@@@CCCCCCCCCDDDnnnPPPGGGEEEEEEFFFGGGGGGGGGGGGFFFGGGKKKWWWnnnPPPOOOOOOPPPPPPQQQRRRRRRSSSSSSSSSSSSSSSTTTTTTUUUQQQU333DDDCCCDDDDDDGGGxxxpppjjjcccjjjttt{{{OOOPPPQQQRRRRRRSSSSSSSSSSSSTTTTTTTTTUUUUUUUUUUUUWWW;;;CAAAFFFFFFFFFFFFEEEwwweeePPPRRRSSSSSSSSSSSSSSSTTTTTTUUUUUUUUUUUUUUUVVVVVVXXXQQQg"""EEEGGGGGGHHHHHHGGGcccXXXRRRSSSSSSTTTTTTTTTUUUUUUUUUUUUUUUVVVVVVWWWXXXYYYZZZUUU%%%W"""DDDJJJIIIIIIIIIHHHJJJcccuuuVVVQQQSSSTTTTTTUUUUUUUUUUUUUUUVVVWWWWWWXXXYYYYYYYYYYYY[[[RRR%%% ;666HHHLLLMMMMMMMMMKKKLLLXXXppprrr]]]TTTSSSUUUVVVVVVVVVVVVVVVWWWWWWXXXYYYZZZZZZZZZZZZZZZ[[[[[[VVV???0X***666<<>>ppp&&&AAA!!!AAA"""  !!!CCC!!!CCC$$$???JJJ!!!""""""###$$$"""FFF!!!FFF'''ppp+++"""$$$$$$%%%&&&&&&%%%HHH!!!III***######&&&&&&'''((())))))(((JJJ!!!LLL,,,222ddd%%%))))))***++++++,,,+++MMM!!!NNN...!!!IIIGGG)))+++,,,---......///...OOO!!!QQQ111"""ccc888---...///000000111222111RRRSSS333$$$zzz222000111222333444555555444TTT,,,555UUU666'''333444555555666666777888777NNNQQQ888***666666777888999::::::;;;<<<===EEE<<<,,,;;;999:::;;;<<<<<<======>>>>>>@@@TTT```^^^pppcccdddYYYGGGHHH>>>///mmmKKK;;;======>>>??????@@@AAAAAABBBBBBAAAAAAIIIFFFGGGHHHJJJKKKAAA444OOO}}};;;???@@@AAAAAABBBCCCDDDDDDEEEFFFCCCoooHHHKKKKKKLLLMMMNNNCCC444???ggg???AAACCCDDDEEEFFFFFFFFFFFFIIIwwwTTTLLLMMMNNNOOOPPPRRRBBB,,,AAAUUU]]]KKKFFFFFFGGGJJJUUUrrreeeMMMPPPQQQRRRSSSSSSUUU777BBBDDD```dddPPPSSSSSSTTTTTTUUUVVVRRR3"""EEEHHHVVVYYYSSSUUUVVVVVVWWWXXXZZZTTT))),a222===@@@LLLeeevvv\\\KKKGGGIIIKKKLLLLLLMMMMMMIII;;;Z` Y/+(  ZW`///,,, !!! ]aaaYYY###222222|||999BBB,,,,,,FFF888BBB:::>>>===!!! CCC!!!AAA"""ttt!!!&&&&&&HHH!!!EEE:::NNN(((,,,+++MMM JJJTTT>>>///222222RRR555:::NNNccc===666888888HHHEEE\\\JJJ;;;===>>>???NNNaaa```UUU??? CCCAAA???AAABBBIIIeeeIIINNNCCC ,,,|||QQQSSSVVV888l 000^^^^^^HHHKKKKKK;;;hk   hPKGGDH\:: virtualtouchpad/html/index.xhtml Virtual Touchpad
Sensitivity
Acceleration
PKHHd 6Ikk.virtualtouchpad/html/virtual-touchpad.appcacheCACHE MANIFEST # Version 0.13 CACHE: help translations/index translations/help NETWORK: * PKiHHz8$virtualtouchpad/html/index.min.xhtmlVirtual Touchpad
Sensitivity
Acceleration
CapsCtrlAltAltGr🌐PKNG#I5&5&)virtualtouchpad/html/keyboard/buttons.svg Caps Ctrl Alt AltGr 🌐 PKiHHdv0virtualtouchpad/html/keyboard/geometry/large.svg PKiHHQq*DD8virtualtouchpad/html/keyboard/geometry/function-keys.svg Esc F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 Home End PgUp PgDn PKҬEG겴@M@M.virtualtouchpad/html/keyboard/layout/en.layout{ "meta": { "name": "English (qwerty)" }, "layout": { "AB01": [ [ "<", 60, "less" ], [ ">", 62, "greater" ], [ "|", 124, "bar" ], [ "\u00a6", 166, "brokenbar" ] ], "AC09": [ [ "l", 108, "l" ], [ "L", 76, "L" ], [ "l", 108, "l" ], [ "L", 76, "L" ] ], "AE02": [ [ "2", 50, "2" ], [ "@", 64, "at" ], [ "2", 50, "2" ], [ "@", 64, "at" ] ], "AE03": [ [ "3", 51, "3" ], [ "#", 35, "numbersign" ], [ "3", 51, "3" ], [ "#", 35, "numbersign" ] ], "AD09": [ [ "o", 111, "o" ], [ "O", 79, "O" ], [ "o", 111, "o" ], [ "O", 79, "O" ] ], "AD08": [ [ "i", 105, "i" ], [ "I", 73, "I" ], [ "i", 105, "i" ], [ "I", 73, "I" ] ], "AE06": [ [ "6", 54, "6" ], [ "^", 94, "asciicircum" ], [ "6", 54, "6" ], [ "^", 94, "asciicircum" ] ], "AE07": [ [ "7", 55, "7" ], [ "&", 38, "ampersand" ], [ "7", 55, "7" ], [ "&", 38, "ampersand" ] ], "AE04": [ [ "4", 52, "4" ], [ "$", 36, "dollar" ], [ "4", 52, "4" ], [ "$", 36, "dollar" ] ], "AE05": [ [ "5", 53, "5" ], [ "%", 37, "percent" ], [ "5", 53, "5" ], [ "%", 37, "percent" ] ], "AD03": [ [ "e", 101, "e" ], [ "E", 69, "E" ], [ "e", 101, "e" ], [ "E", 69, "E" ] ], "AD02": [ [ "w", 119, "w" ], [ "W", 87, "W" ], [ "w", 119, "w" ], [ "W", 87, "W" ] ], "AD01": [ [ "q", 113, "q" ], [ "Q", 81, "Q" ], [ "q", 113, "q" ], [ "Q", 81, "Q" ] ], "AE09": [ [ "9", 57, "9" ], [ "(", 40, "parenleft" ], [ "9", 57, "9" ], [ "(", 40, "parenleft" ] ], "AD07": [ [ "u", 117, "u" ], [ "U", 85, "U" ], [ "u", 117, "u" ], [ "U", 85, "U" ] ], "AD06": [ [ "y", 121, "y" ], [ "Y", 89, "Y" ], [ "y", 121, "y" ], [ "Y", 89, "Y" ] ], "AD05": [ [ "t", 116, "t" ], [ "T", 84, "T" ], [ "t", 116, "t" ], [ "T", 84, "T" ] ], "AD04": [ [ "r", 114, "r" ], [ "R", 82, "R" ], [ "r", 114, "r" ], [ "R", 82, "R" ] ], "AB10": [ [ ".", 46, "period" ], [ ">", 62, "greater" ], [ ".", 46, "period" ], [ ">", 62, "greater" ] ], "AB11": [ [ "/", 47, "slash" ], [ "?", 63, "question" ], [ "/", 47, "slash" ], [ "?", 63, "question" ] ], "AC12": [ [ "\\", 92, "backslash" ], [ "|", 124, "bar" ], [ "\\", 92, "backslash" ], [ "|", 124, "bar" ] ], "AC11": [ [ "'", 39, "apostrophe" ], [ "\"", 34, "quotedbl" ], [ "'", 39, "apostrophe" ], [ "\"", 34, "quotedbl" ] ], "AC10": [ [ ";", 59, "semicolon" ], [ ":", 58, "colon" ], [ ";", 59, "semicolon" ], [ ":", 58, "colon" ] ], "AD10": [ [ "p", 112, "p" ], [ "P", 80, "P" ], [ "p", 112, "p" ], [ "P", 80, "P" ] ], "AD11": [ [ "[", 91, "bracketleft" ], [ "{", 123, "braceleft" ], [ "[", 91, "bracketleft" ], [ "{", 123, "braceleft" ] ], "AD12": [ [ "]", 93, "bracketright" ], [ "}", 125, "braceright" ], [ "]", 93, "bracketright" ], [ "}", 125, "braceright" ] ], "AB08": [ [ "m", 109, "m" ], [ "M", 77, "M" ], [ "m", 109, "m" ], [ "M", 77, "M" ] ], "AE11": [ [ "-", 45, "minus" ], [ "_", 95, "underscore" ], [ "-", 45, "minus" ], [ "_", 95, "underscore" ] ], "AE10": [ [ "0", 48, "0" ], [ ")", 41, "parenright" ], [ "0", 48, "0" ], [ ")", 41, "parenright" ] ], "AE12": [ [ "=", 61, "equal" ], [ "+", 43, "plus" ], [ "=", 61, "equal" ], [ "+", 43, "plus" ] ], "AC04": [ [ "f", 102, "f" ], [ "F", 70, "F" ], [ "f", 102, "f" ], [ "F", 70, "F" ] ], "AC05": [ [ "g", 103, "g" ], [ "G", 71, "G" ], [ "g", 103, "g" ], [ "G", 71, "G" ] ], "AC06": [ [ "h", 104, "h" ], [ "H", 72, "H" ], [ "h", 104, "h" ], [ "H", 72, "H" ] ], "AC07": [ [ "j", 106, "j" ], [ "J", 74, "J" ], [ "j", 106, "j" ], [ "J", 74, "J" ] ], "AB09": [ [ ",", 44, "comma" ], [ "<", 60, "less" ], [ ",", 44, "comma" ], [ "<", 60, "less" ] ], "AC01": [ [ "a", 97, "a" ], [ "A", 65, "A" ], [ "a", 97, "a" ], [ "A", 65, "A" ] ], "AC02": [ [ "s", 115, "s" ], [ "S", 83, "S" ], [ "s", 115, "s" ], [ "S", 83, "S" ] ], "AC03": [ [ "d", 100, "d" ], [ "D", 68, "D" ], [ "d", 100, "d" ], [ "D", 68, "D" ] ], "AB05": [ [ "v", 118, "v" ], [ "V", 86, "V" ], [ "v", 118, "v" ], [ "V", 86, "V" ] ], "AB04": [ [ "c", 99, "c" ], [ "C", 67, "C" ], [ "c", 99, "c" ], [ "C", 67, "C" ] ], "AE08": [ [ "8", 56, "8" ], [ "*", 42, "asterisk" ], [ "8", 56, "8" ], [ "*", 42, "asterisk" ] ], "AB06": [ [ "b", 98, "b" ], [ "B", 66, "B" ], [ "b", 98, "b" ], [ "B", 66, "B" ] ], "AC08": [ [ "k", 107, "k" ], [ "K", 75, "K" ], [ "k", 107, "k" ], [ "K", 75, "K" ] ], "AE00": [ [ "`", 96, "grave" ], [ "~", 126, "asciitilde" ], [ "`", 96, "grave" ], [ "~", 126, "asciitilde" ] ], "AB03": [ [ "x", 120, "x" ], [ "X", 88, "X" ], [ "x", 120, "x" ], [ "X", 88, "X" ] ], "AB02": [ [ "z", 122, "z" ], [ "Z", 90, "Z" ], [ "z", 122, "z" ], [ "Z", 90, "Z" ] ], "AE01": [ [ "1", 49, "1" ], [ "!", 33, "exclam" ], [ "1", 49, "1" ], [ "!", 33, "exclam" ] ], "AB07": [ [ "n", 110, "n" ], [ "N", 78, "N" ], [ "n", 110, "n" ], [ "N", 78, "N" ] ] } }PKiHH)>=>=.virtualtouchpad/html/keyboard/layout/se.layout{ "meta": { "name": "Swedish (qwerty)" }, "layout": { "AE00": [ [ "\u00a7", false ], [ "\u00bd", false ], [ "\u00b6", false ], [ "\u00be", false ] ], "AE01": [ [ "1", false ], [ "!", false ], [ "\u00a1", false ], [ "\u00b9", false ] ], "AE02": [ [ "2", false ], [ "\"", false ], [ "@", false ], [ "\u00b2", false ] ], "AE03": [ [ "3", false ], [ "#", false ], [ "\u00a3", false ], [ "\u00b3", false ] ], "AE04": [ [ "4", false ], [ "\u00a4", false ], [ "$", false ], [ "\u00bc", false ] ], "AE05": [ [ "5", false ], [ "%", false ], [ "\u20ac", false ], [ "\u00a2", false ] ], "AE06": [ [ "6", false ], [ "&", false ], [ "\u00a5", false ], [ "\u215d", false ] ], "AE07": [ [ "7", false ], [ "/", false ], [ "{", false ], [ "\u00f7", false ] ], "AE08": [ [ "8", false ], [ "(", false ], [ "[", false ], [ "\u00ab", false ] ], "AE09": [ [ "9", false ], [ ")", false ], [ "]", false ], [ "\u00bb", false ] ], "AE10": [ [ "0", false ], [ "=", false ], [ "}", false ], [ "\u00b0", false ] ], "AE11": [ [ "+", false ], [ "?", false ], [ "\\", false ], [ "\u00bf", false ] ], "AE12": [ [ "\u00b4", true ], [ "`", true ], [ "\u00b1", false ], [ "\u00ac", false ] ], "AD01": [ [ "q", false ], [ "Q", false ], [ "@", false ], [ "\u03a9", false ] ], "AD02": [ [ "w", false ], [ "W", false ], [ "\u0142", false ], [ "\u0141", false ] ], "AD03": [ [ "e", false ], [ "E", false ], [ "\u20ac", false ], [ "\u00a2", false ] ], "AD04": [ [ "r", false ], [ "R", false ], [ "\u00ae", false ], [ "\u00ae", false ] ], "AD05": [ [ "t", false ], [ "T", false ], [ "\u00fe", false ], [ "\u00de", false ] ], "AD06": [ [ "y", false ], [ "Y", false ], [ "\u2190", false ], [ "\u00a5", false ] ], "AD07": [ [ "u", false ], [ "U", false ], [ "\u2193", false ], [ "\u2191", false ] ], "AD08": [ [ "i", false ], [ "I", false ], [ "\u2192", false ], [ "\u0131", false ] ], "AD09": [ [ "o", false ], [ "O", false ], [ "\u0153", false ], [ "\u0152", false ] ], "AD10": [ [ "p", false ], [ "P", false ], [ "\u00fe", false ], [ "\u00de", false ] ], "AD11": [ [ "\u00e5", false ], [ "\u00c5", false ], [ "\u00a8", true ], [ "\u00b0", true ] ], "AD12": [ [ "\u00a8", true ], [ "^", true ], [ "~", true ], [ "\u02c7", true ] ], "AC01": [ [ "a", false ], [ "A", false ], [ "\u00aa", false ], [ "\u00ba", false ] ], "AC02": [ [ "s", false ], [ "S", false ], [ "\u00df", false ], [ "\u00a7", false ] ], "AC03": [ [ "d", false ], [ "D", false ], [ "\u00f0", false ], [ "\u00d0", false ] ], "AC04": [ [ "f", false ], [ "F", false ], [ "\u0111", false ], [ "\u00aa", false ] ], "AC05": [ [ "g", false ], [ "G", false ], [ "\u014b", false ], [ "\u014a", false ] ], "AC06": [ [ "h", false ], [ "H", false ], [ "\u0127", false ], [ "\u0126", false ] ], "AC07": [ [ "j", false ], [ "J", false ], [ "", true ], [ "", true ] ], "AC08": [ [ "k", false ], [ "K", false ], [ "\u0138", false ], [ "&", false ] ], "AC09": [ [ "l", false ], [ "L", false ], [ "\u0142", false ], [ "\u0141", false ] ], "AC10": [ [ "\u00f6", false ], [ "\u00d6", false ], [ "\u00f8", false ], [ "\u00d8", false ] ], "AC11": [ [ "\u00e4", false ], [ "\u00c4", false ], [ "\u00e6", false ], [ "\u00c6", false ] ], "AC12": [ [ "'", false ], [ "*", false ], [ "\u00b4", false ], [ "\u00d7", false ] ], "AB01": [ [ "<", false ], [ ">", false ], [ "|", false ], [ "\u00a6", false ] ], "AB02": [ [ "z", false ], [ "Z", false ], [ "\u00ab", false ], [ "<", false ] ], "AB03": [ [ "x", false ], [ "X", false ], [ "\u00bb", false ], [ ">", false ] ], "AB04": [ [ "c", false ], [ "C", false ], [ "\u00a9", false ], [ "\u00a9", false ] ], "AB05": [ [ "v", false ], [ "V", false ], [ "\u201c", false ], [ "\u2018", false ] ], "AB06": [ [ "b", false ], [ "B", false ], [ "\u201d", false ], [ "\u2019", false ] ], "AB07": [ [ "n", false ], [ "N", false ], [ "n", false ], [ "N", false ] ], "AB08": [ [ "m", false ], [ "M", false ], [ "\u00b5", false ], [ "\u00ba", false ] ], "AB09": [ [ ",", false ], [ ";", false ], [ "\u00b8", true ], [ "\u02db", true ] ], "AB10": [ [ ".", false ], [ ":", false ], [ "\u00b7", false ], [ "\u02d9", true ] ], "AB11": [ [ "-", false ], [ "_", false ], [ "", true ], [ "\u02d9", true ] ] } } PKGGM>__2virtualtouchpad/html/translations/index/default.jsexports.translation.catalog = { code: "en", plural: "n == 1 ? 0 : 1", texts: {} }; PKHHs-virtualtouchpad/html/translations/index/sv.jsexports.translation.catalog={"texts": {"Acceleration": "Acceleration", "Please upgrade your browser to a newer version.": "Uppgradera din webbl\u00e4sare till en nyare version.", "Your browser does not support Virtual Touchpad. These features are missing:": "Din webbl\u00e4sare st\u00f6der inte Virtual Touchpad. F\u00f6ljande funktioner saknas:", "Sensitivity": "K\u00e4nslighet", "Failed to connect. Please verify that is running.": "Anslutningen misslyckades. Kontrollera att k\u00f6r.", "One apple.": ["Ett \u00e4pple.", "M\u00e5nga applen."], "Connection closed. Please click here to reconnect.": "Anslutningen st\u00e4ngdes. Klicka h\u00e4r f\u00f6r att ansluta igen.", "WebSockets are not supported": "WebSockets st\u00f6ds inte", "Touch events are not supported": "Touch events st\u00f6ds inte"}, "code": "sv", "plural": "(n != 1)"}PKGGM>__1virtualtouchpad/html/translations/help/default.jsexports.translation.catalog = { code: "en", plural: "n == 1 ? 0 : 1", texts: {} }; PKHHDπ,virtualtouchpad/html/translations/help/sv.jsexports.translation.catalog={"texts": {"How do I click?": "Hur klickar jag?", "The address bar is hidden when in fullscreen mode. Tap the fullscreen button to activate it.": "Adressf\u00e4ltet d\u00f6ljs i fullsk\u00e4rmsl\u00e4ge. Knacka p\u00e5 fullsk\u00e4rmsknappen f\u00f6r att aktivera det.", "Start the drag-and-drop operation by tapping once, and then immediately tapping again, quickly followed by dragging.": "B\u00f6rja att dra-och-sl\u00e4ppa genom att knacka en g\u00e5ng och sedan direkt knacka och dra igen.", "On iOS, the address bar will also be hidden if you install Virtual Touchpad to your home screen.": "P\u00e5 IOS f\u00f6rsvinner adressf\u00e4ltet dessutom om du installerar Virtual Touchpad p\u00e5 hemsk\u00e4rmen.", "Stop the drag-and-drop operation by tapping once.": "Sluta dra-och-sl\u00e4ppa genom att knacka en g\u00e5ng till.", "How do I hide the address bar?": "Hur g\u00f6mmer jag webbl\u00e4sarens adressf\u00e4lt?", "Simply tap on the virtual touchpad on your device.": "Knacka p\u00e5 sk\u00e4rmen p\u00e5 din enhet.", "How do I drag-and-drop?": "Hur drar-och-sl\u00e4pper jag?"}, "code": "sv", "plural": "n != 1"}PK6YAGˡW"%virtualtouchpad/html/css/touchpad.csstouchpad { display: block; position: absolute; top: 0; left: 0; right: 0; min-height: 100%; background: radial-gradient(circle at bottom center, #282828 0%, #181818 50%, black 100%); } PKGGԻJJ virtualtouchpad/html/css/app.csshtml, body { background-color: black; height: 100%; margin: 0; } table.stack caption { font-size: 12pt; margin-top: 5pt; text-align: left; } table.stack td { font-family: courier; font-size: 10pt; } table.stack td:nth-child(2) { padding-right: 50pt; } #keyboard { position: absolute; left: 0; right: 0; bottom: 0; overflow: hidden; max-height: 0; } @media screen and (min-width: 448px) { #keyboard { transition: max-height 1s ease; } .connected #keyboard.ready { max-height: 100%; } .message-box #keyboard.ready { max-height: 0; } } #keyboard-func { position: absolute; left: 0; right: 0; top: 0; margin-top: -100%; overflow: hidden; } @media screen and (min-width: 448px) and (max-aspect-ratio: 448 / 206) { #keyboard-func { transition: margin-top 1s ease; } *:full-screen .connected #keyboard-func.ready { margin-top: 0; } *:-moz-full-screen .connected #keyboard-func.ready { margin-top: 0; } *:-webkit-full-screen .connected #keyboard-func.ready { margin-top: 0; } .message-box #keyboard-func.ready { margin-top: -100%; } } @media screen and (min-width: 448px) and (max-aspect-ratio: 448 / 244) { #keyboard-func { transition: margin-top 1s ease; } .connected #keyboard-func.ready { margin-top: 52px; } .message-box #keyboard-func.ready { margin-top:; -100%; } } #settings-overlay { display: none; } .connected #settings-overlay.toggled { display: block; position: absolute; z-index: 50; min-height: 100%; top: 0; left: 0; right: 0; } #settings-view { position: absolute; z-index: 51; top: 48px; left: 0; bottom: 0; width: 67%; margin-left: -67%; transition: margin-left 0.5s ease; background: linear-gradient(white 0%, #E0E0E0 50%, #D0D0D0 100%); background-color: white; } #settings-view.toggled { margin-left: 0; } #settings-view.sliding { transition: none; } #settings-view > div { color: black; font-family: sans-serif; font-size: 18pt; margin: 32px; } #toolbar { z-index: 50; position: fixed; display: table-cell; left: 0; right: 0; top: 0; margin: 0; padding: 4px; vertical-align: middle; text-align: right; background-color: white; transition: margin-top 1s ease; } @media (orientation: landscape) { *:fullscreen #toolbar { margin-top: -52px; } *:-moz-full-screen #toolbar { margin-top: -52px; } *:-webkit-full-screen #toolbar { margin-top: -52px; } } #toolbar > button { display: table-cell; height: 44px; width: 44px; float: right; border: 0; margin-left: 4px; margin-right: 4px; padding: 0; outline: 0; vertical-align: middle; text-align: center; background-color: transparent; } #toolbar > button svg { fill: #606060; stroke: #606060; stroke-linecap: round; stroke-linejoin: round; stroke-width: 4; } #toolbar > button text { font-family: sans; font-size: 32px; font-weight: bolder; stroke: none; } #toolbar > button .frame { fill: none; } #toolbar > button .shape { stroke: none; } .connected #toolbar > button.settings { float: left; } *:fullscreen #toolbar > button.fullscreen { display: none; } *:-moz-full-screen #toolbar > button.fullscreen { display: none; } *:-webkit-full-screen #toolbar > button.fullscreen { display: none; } PKGG9/-!virtualtouchpad/html/css/help.cssarticle > h1 { font-family: sans-serif; font-size: 20pt; } article > h2 { font-family: sans-serif; font-size: 18pt; } article > p { font-family: sans-serif; font-size: 16pt; } svg { fill: #606060; stroke: #606060; stroke-linecap: round; stroke-linejoin: round; stroke-width: 4; } svg text { font-family: sans; font-size: 32px; font-weight: bolder; stroke: none; } svg .frame { fill: none; } svg .shape { stroke: none; } PK7NG F{{%virtualtouchpad/html/css/keyboard.csskeyboard svg { width: 100%; } /* * Definitions for the generic groups defined in the shared file; they are not * children of a keyboard element, so we must use the .keyboard class to * distinguish them. */ circle.keyboard, line.keyboard, path.keyboard, rect.keyboard { stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; } path.keyboard.shift { fill: none; } text.keyboard, keyboard text { font-family: arial; font-size: 16px; } text.keyboard.small, keyboard text.small { font-size: 10px; } .keyboard.background { fill: #e0e0e0; } .keyboard.background.special { fill: #a0a0e0; } .keyboard.background.layout { fill: #a0e0a0; } .keyboard.overlay { fill: url(#keyboard-shade); fill-opacity: 0.2; stroke: #c0c0c0; } .keyboard.overlay.special { stroke: #a0a0c0; } .keyboard.overlay.layout { stroke: #a0c0a0; } .keyboard.drawing { stroke: black; } /* * Definitions to show and hide the appropriate key labels depending on the * keyboard modifier state */ keyboard .key text { visibility: hidden; } keyboard .key text.static { visibility: visible; font-size: 9px; } keyboard.mod-none .key text.mod-none { visibility: visible; } keyboard.mod-shift .key text.mod-shift { visibility: visible; } keyboard.mod-altgr .key text.mod-altgr { visibility: visible; } keyboard.mod-both .key text.mod-both { visibility: visible; } /* * Definitions to scale and animate keys when pressed. */ keyboard .key { transform-origin: 50% 50%; transform: scale(1.0); transition: transform 0.05s ease; } keyboard .key.pressed { transform: scale(0.85); } PKGGlL(virtualtouchpad/html/css/message-box.cssmessage-box { z-index: 100; position: fixed; left: 0; right: 0; max-height: 0; transition: max-height 1s ease; margin: 4pt; margin-top: 60px; padding: 0; background-color: white; color: black; font-family: sans-serif; font-size: 18pt; } .message-box message-box { max-height: 100%; padding: 4pt; } message-box.error {} PKGG.B%virtualtouchpad/html/css/trackbar.csstrackbar { display: block; height: 48px; width: 100%; background: none; } trackbar .track { display: block; position: relative; width: 100%; height: 4px; left: 0; top: 0; margin-top: 16px; background-color: #A0A0A0; } trackbar .groove { display: block; position: relative; height: 4px; margin-right: 10px; background-color: #0000FF; } trackbar .mark { position: relative; top: -10px; float: right; margin-right: -10px; width: 16px; height: 16px; background-color: white; transition: background-color 0.2s ease; border: 4px solid #0000FF; border-radius: 16px; } trackbar.active .mark { background-color: #0000FF; } PKGG VS7S7&virtualtouchpad/html/img/icon96x96.pngPNG  IHDR_`E )iCCPiccxڕgP} mқT) HYz^EY"bCDi Q)+X X,"*(GL{'?zw93" ~.06|G$/3 Q@%:&K \ JJ  @n3/f_>L~ {T|ABlL.?-Vɏagس>؄W@+pHKOowF,{ӿ3]xY2( LEP-c0+'poP<C( ZB7\kp}g )x!"t J:#qB<?$@T$ Gv"%H9R4 -OrF ,'PDP \ @ףqh{*=vл(*Dߠ `T)czs0,c[bkڱ^l{ 93c8=2p[qI\7}x]% f|'~?H XM9JH$l&:W ÄIH!HXD&"^!HTɘL # HVeiD#-h&rKG"/Q)kJ%REiܠSST՗@NޢNP?$h:4Z8-vvNkat}/~I!/&-MVKdD(YT]+A4OR=91XVZ bcb q#qoRV3D 'hBc%&C1v27SLSLd0O3&A$,rc%XgYX_R1R{ڥFc;GȰeddt<!;'ǔ˝{*o?&?(࢐Pp]aNhXxYqVdTtE5[e'yeyeW,!%M@Ujjj꼚ZZSu:G=^FnnMiM7<6q-VVCm6G;I}TT'^V.k{Xwx~ŪUhz\l6 }~~[50 M Iic35~y=ߙĘ1yl02mg܌on6kfa^g>ar|8[x {m->[Y ,Zagdj5FsM̚5*֑ BMQmmK;Uhfi67{Оoi`#X8$TY9ιyeUW~177[ۼ~GKOOguk|Ե}OϾ_ZW~F~~ jf-; C B M #5-sZwpTixQs !yå#7GGF,GzG6F.DEExxo+gccccccgW%8$$KtMO\LN:ܑBJH*ڟ6^.̰817g"3{LA`0K+kWDMvms⹩t64w|3n3os_r-- [Q[n+6eI;~)0,(/3xgoB].ڊDEcv!ᇡ=TV]|İdWzG~\w̬>¾}?Y.^W>y@WoWT:$VW\_3Zk_Q'_np#vGKM8QXWMAM9[eKH=!rۖ/kvkt_:̆oqwxۑk|G G?yK϶Nj=|!W_;fK/_>M-WWJ-33gg^zM{v ·O[L>-,qi̧9^Y&.W}J.P cHRMz&u0`:pQ<bKGD#2 pHYsHHFk>tIME1)U!,IDATxw_E7ޖvIBIh{G4i JGTHWzQA @H^g?fo~$u^7w93SVTLXme0:@H.TĤь\QaЁ7\ԋ1bԜb|| % H_lxO>靨^ k/ +LW .ufҍb4]L1bi5(z`2&\lqqm;b&.a DLZwRIS^L[xO=a ϧ |n[n&:VPC94L=&O\IZ r>HC1b34^[;#=Kq*)©ٵy4e9$?cW)E9(^D' gL-z3ZŌ|t6.c/wf ,~.OP7RC!ORG> mO}ڜA ؖhߎͩ}WP35ƬÄOSwfLL̼J `!؋/6Γ053_ş=;ї$|OpЅbR?4˧m:ioP>7}nQ\G!,/I(ʍ|&iCҵnN5fYzŽؚʊ_PlK"#Py$Nf+ z2խ\I/ZR<MQ܁Fg2Fbt(^_ot˂&1 _淜)ֺ,5%Q?>w%@6:aʽh-mzQs9U V㋾|'=ԇ}ձ$!8-3Rf&CqiLLSq!L5]%v؏)i| @>^Euyܲjc|J]:b1#S}֧]l@f/Ow3^cܮR| Q4`N0?`bU)ŊTn{cGF uv#3xOuzz7_ຎpy{w{SthS~O~]6ާEEL*Pubu؜w KnSsH鿜<_̰CM(.k gR5K7yn$ODc~OOl7O7͕s|$mWd0t$k, =NJbbv 8Ga8&́Tވ??3StxS?@Vӡd ko}6O@Zw xwYb} )bܦIUM+&j)~&L5~'gedH}8pyi3VLvyCG,˔:'*_/`3>܆gjd'G1bXbT:ߠ36#lBX'Sl Kz dXw KF qG7*EsHy;jVP釡ىGx} y,(A-z2ϯɨ9GdMPoc_ʝZ+6 r޼{3Sf'n8;KPI)'feT6nۍfakJ)ue6ރc |>k[v*Y¿c1_)~1k7V¶lV摬|霿 {+BݔԵ Usc DsfDIL@qRHiY,Yn̰Cc 9?K̤ jڲh CطaƞKq űUβc;j#}f"{+FY>ى׏#_0b :>ke4K&$X68"6l<s <2 lRژ LeB*yɓSlK}Bմ3]S#EF} 5̜/#2~q+ul_>a.T)gx'wnx~q&H%i7L O_Ū?L0̯ UHnf]/,Sqat*䥺dZP'K0,#\ǿ}%G suS{G 1h19=NTNϖN[2cxV/Ƙ-O9;HasRw վb%KD=o0fy7HS/κk945+ũ3TY8>UGy pJ"7$ҝ!8khќԡ[(0u;^W/ml"S~Hy?}v3o:ao&bhf(D:ɥX!3=HSb5M? KTl7/+,iz .As) {ƸϫsJp_#3#I"ּVUS))>Ca>ؒtZiS`%B < )xR}8$TMv: F09Յ13"YF S*bv6LW&Iϥy|.JA vȪ}6LƼ*n\6߳OPv)KirbT!xP$]a=L/~6%KE6Ba6aqY.W +dN¶Mj@b<)ڀr70xz{,>9ǧ{Im)f]-Dž`MѰyHLofvHT*5=o3L($=M:c!gl'"F:&>|!/Sy3t;Ny.%P323z[o*S 5-(ĔL|W2@*?&g!o=gS9oQJ,]XF:?e0-CZ='S;Ȏɓx_l >CAtA)wΖe7A KL6;j&اW7F5ڂm> ttt&z:L('P4x4'=wb'ə!6DUyS.)y{&ɝ7^;)2[?!6G1 Hы%*)㛿yɟ"TȲ8te뜓>"[kn~Τ2pIL$P\{hOlBPS3)۲l1o!-[ӣ ЃԖݔ"#jJ~g+4O7v1v-*Glov"B1^}_?MWOQǖ1j`tq0c<^llĥ)"hf ǡ%c.pj󻞣w=MoDOt[/bbOj^%#-c q Rwj=^7ħ~،+Qv[0&Fh)f|(M'I mȾ@sAL/iYރiC(;s|)EG(X-yh:dʡ+2^<Ӗi(LJ*Iˇ"\ő嗔Ffqueߏ60Do&`HO˞{Y3_!I=J{ 3v/肏3p%3bViætO5Jz/RtáؖJq//b7Tě"g@ 荕E4\4nѡzH-Io<-KH0hf1Lߎ g6]3=1Ed{uWzؖmYޘqDYBI5KTG2- pk)`-j-`F|!¨Q͛) MOߴe{xS$(<ѩj6g㔸/erTv4[drk!{&`TvIKS,Z%v7E,THE 5OيTVa%TH x:mahs3aw3+gr XX=ԕ7zy 3pY֚X5-|*[>3k%!>Y&aƹxB @i9 s,i"?=N'Cˤ濥Lu凁Z( :nNߑ-Ch$@bmYׅlLVelWqF zk@ٞt|Ƶ}fkj^$ gPRy/fp3naŋ;2f?"6^T?zrlu(7 szIlLc ĚXr\/}&{b㵘؛->("0u^ֽ|>mk\MŘ\@?3,|8Q@)1q_QdV֚fc eA`&o~\{OQʏ"I,c0H̯3Zȍ1P^_I>D2G_) NJiBM%o)W#`^ $jjJ` ʁNAbE+~B3DH|$@D:. ǰڏ澭I]0`)<$+E/O/' ̧șE|+c+ĤnR|'uQ?"BwYiR:?)}òWH=W+۲4Τ9ӬNv:`t&应FDptH;{Q͑%QN /-'NhP`:eT_'}ve~Qdҍ~M/pԁRmI9rBG:N!%F>D4IE }I'Lo?7__Z޽=r- l^Hٗ5i0kTz0k\s{ͼjRʫ)GS@y>Nqqn"F)4HD%;) t(iy| |WQv4WOrDi |}3l$-GG0;;I{Cgd)pHYWa gᦃy0Wm]s|z*‡IUB[9`b {0'ϿϮQܷ| \G]|fX[Hn>dSfPHhόt":v,QpiQq±L?V0ͅ\X槻0!"1:)bKT{k\|wIr'G” bOVOEI\Uc1;#T6ˡc3#$="x8:N%t_OY?ϖah+KQ]𧤝IQAV%P򙜹tuN-[3Qf×fzbB6tq45AZ4w.Ź_7<8)ƈd;XV:Y~ǟN:-9:X~Ogq6e3Mx;`LO ,usP |(r sQJ^Z4H^Ώwwc> #.J5`IoE1"e*Pk:ڙ: &]7bJOSCj&"l*̯vv{@U˓卤Ƚ*~?v[M~˲wHC)8יUFkQZO,~#6KsX\Q+?I#moc)g>)&.Džfs)bVű#/1}xIgytUR[Ϸ"D;?${ =?N"{,䠦oe,ji*U/'7C LJݳpEDoޞc-"V.= ?d͂rQr ҅0f+hq*>XcW pT-ZY?\`YE}D(8 t#R< bsRsO^:)м\l\T$)*d{ g1?\͚Ѱ|̫3iz0qW2usizӕb|.@笉3#F",<#O|^}7?r*AԬjvY:Q*lchxf+t/}xglxiM~}G C)AGn:s@vw։٬\V L)DRDž%6oǧ YfS>]vϜ;_'4?`1܏֯eJŝs)HuUjN!η IuZ<[D8^5/ҖnaQk0=_NSen]ɺȉypNEoLm̓3p;<̡dE7w~ˆeh(1̸ZԎ&IlK|JRR)ޗRC)սR>)_RC|I!)Hv;?_i[J=SjyNJ-˔ZVSjuzJK)76fbk\,6)5N)ңҷؔ$/JCRzn56 KiSZRƔRk)ٵo'=1}4ŢkJ5we7T3%ږ)նIMnOLŔJaTwCJ]H6A)R%Rj%VEJ-gԪ.VRj'Sj]J'ڼRcהZoV;ߎi)>N)oUJNIiR;u:_R p埗)Q)UzTR)֥T[Rm|)LHfrJۦTGJu)ՍH6OS =4;>;8Sj1"[Ԫ>_SjXJdRSju`J.N )~663Rģ :6ISzJO)-vGJ]R)9ϸeuw{}叝K5=BKU8cglO5\9ռ/ cxr81'՜bIkf3PRL|8`֭ȟMJxw | 5k\Φ, OUȆ}ylwa溧*>Vs?3h^;%⽀WR 8V34W<11i|0}3C+^ QUP# V:Gsd6SOv\ctTN|sZ2/aVTFl&j5zN`6f+ OGF[-g54B8PӅ%>͸*R'Ēj3u*Gɵ|]Ff{'sxiB @LX-KY.=ؕS}U& 7@I1%X963h\X#E{ (g`n86Oxur?v_@T19]| ELH_(=⢘6|0ܿEea\ǷaՓe9_MQx;Qd?FTjxRηf#)ywc:muyFBeDiAWvSd Vl TV#&4# FP/]u9)k@m̡b"ͪ&3}WD4371~ɇC9e1N\YRW"̸xhAYr ӣG\xH2jŅǸFzuU9'?9\Wd!@3t'dD^I-M|5Uޚ!E72㏑wTojsFy$H2[9ڊj~=+:V*~DM@>3-OpLzߵ]EØB;*)]f{Sr!oۯu263{KXoʊ;a|\z+r?E6dW}8_w~6CsSw6> .,Gm8d?l+7gmޥ/b4}e&1o«1 &M-?'xvQZ7a/Q ܁1>A71co@c7KZ%tEXtdate:create2016-01-25T21:49:41+01:00%tEXtdate:modify2016-01-25T21:49:41+01:00W1tEXtps:HiResBoundingBox157x158+0-1 tEXtps:LevelAdobe-3.0 EPSF-3.0 pIENDB`PKGGȨ~~&virtualtouchpad/html/img/icon57x57.pngPNG  IHDR99vM )iCCPiccxڕgP} mқT) HYz^EY"bCDi Q)+X X,"*(GL{'?zw93" ~.06|G$/3 Q@%:&K \ JJ  @n3/f_>L~ {T|ABlL.?-Vɏagس>؄W@+pHKOowF,{ӿ3]xY2( LEP-c0+'poP<C( ZB7\kp}g )x!"t J:#qB<?$@T$ Gv"%H9R4 -OrF ,'PDP \ @ףqh{*=vл(*Dߠ `T)czs0,c[bkڱ^l{ 93c8=2p[qI\7}x]% f|'~?H XM9JH$l&:W ÄIH!HXD&"^!HTɘL # HVeiD#-h&rKG"/Q)kJ%REiܠSST՗@NޢNP?$h:4Z8-vvNkat}/~I!/&-MVKdD(YT]+A4OR=91XVZ bcb q#qoRV3D 'hBc%&C1v27SLSLd0O3&A$,rc%XgYX_R1R{ڥFc;GȰeddt<!;'ǔ˝{*o?&?(࢐Pp]aNhXxYqVdTtE5[e'yeyeW,!%M@Ujjj꼚ZZSu:G=^FnnMiM7<6q-VVCm6G;I}TT'^V.k{Xwx~ŪUhz\l6 }~~[50 M Iic35~y=ߙĘ1yl02mg܌on6kfa^g>ar|8[x {m->[Y ,Zagdj5FsM̚5*֑ BMQmmK;Uhfi67{Оoi`#X8$TY9ιyeUW~177[ۼ~GKOOguk|Ե}OϾ_ZW~F~~ jf-; C B M #5-sZwpTixQs !yå#7GGF,GzG6F.DEExxo+gccccccgW%8$$KtMO\LN:ܑBJH*ڟ6^.̰817g"3{LA`0K+kWDMvms⹩t64w|3n3os_r-- [Q[n+6eI;~)0,(/3xgoB].ڊDEcv!ᇡ=TV]|İdWzG~\w̬>¾}?Y.^W>y@WoWT:$VW\_3Zk_Q'_np#vGKM8QXWMAM9[eKH=!rۖ/kvkt_:̆oqwxۑk|G G?yK϶Nj=|!W_;fK/_>M-WWJ-33gg^zM{v ·O[L>-,qi̧9^Y&.W}J.P cHRMz&u0`:pQ<bKGD#2 pHYsHHFk>tIME1*(EOIDAThy_ӹ>s"@1f1ԍTBњjhJT[3AkT! AH"ro#yYunǻ554ͤGSu$A=."3(jxoQN$VAB[ymA1Ga#fC,W+Ae/>w3$oM/%wj'Wr"5Teb~OC{4M= |:3tޭ睵X8u؍bb KvQGQl7PbcMx%]G;]sxo`LkȠyUC8V f8/d m#/t'ted]ad.P0hmn{5v*}5i`Y3ngk *7ҶUGS 9SПČpbxX,ZnOQ<ԌNxK,q(Gn)}Syx.ì-Y2uؚb&)}+t"[a(iElrj9N1&TQ*X9J34V*D1M=hڈ8k3K ‚k(S# f=ܟb9K_kKQ,0Nî'/iL;ˬA2CqeN6ؖͨXq. y$vtL 1Y̹6O)W!+/ό黏vߩr XOT&n*55riTLxbi<[c}zpM[zWE}9f޾.j%}hÈ3s4XIHă9*~KCfrҝʆe9as黄WQ?-72FB,ܑ4.pue@)/c4q5;3p18/G ϝ92Kj^;yk-5?fq_[. do7$f%Uh^I'QA񋜓ͱ (ƕu>Ćf]ե,-xoY41 ۔7ns._BN*xge* =t KN9x%FBe;* T!TD2w1 |.cHQy"gm:B[N^~)W%l?ҴLӊw~XٜJ?*o3 +Ý5kfb]\9Kqz5ҳy㉉%览`uvf LǢKy+1;7%|:99ә9|JCr"5*iYM,IbwlFgK)ekeD+Ŕзʶ6enN&%5dMW>̅r5O4yi7*597TF/4ݻ0GThviw搝(AoJSy:ˤݗhF/7sbAŞ|ZN:C$ZZXVӉ6Xbr5:%h%ÿxBJ|"v9s!#;dKS3Hՙu"2%G/\Bq_etMYj=Elxnëted*V*5U(r]'qټ]%h-Q94}YiqI$]xx ZހtAuJڹ)jFZFQoɹe? (,[r5[-lMZͫFn JNu `OND{.pyJF|aY ?)De؎gZ:gYk>9rq7l_9oetdY.u'.i{;Oi)[ju⯹f25^?KN:3GO&/Dn(]g~;k%]$zcx^ϧGo'X_on .*WѧAe'K1+I_<d,o!Lҩ5R/3$}KRFQi'Za_<Ƹ,>"՝;krṱŸ4k!d˹4:[A*Mi"/_|~uE}^yǮƒvkZɍ_g9U>:Q^!Wc~2t~Ƙ6,.)傒vO0g߁8ԋlEdVF/ƽ J@{2x!3P\Z!!$versFKVx B%Y><+ӹ|N_f.Zytl9mFR}s.Oz3?] hϛ,YsHcڷ8:*ƿ(y%sY{<{`QَoڛQi9JaN515w*t yb/cſr6{'oGR#> ơ,+Rg ͋(6Q gD>5DQ[;%=#,>GF}D݅"E4tuD:ͯE4񽈳zF̛˔t_Dېeio!D4בu;~99g ;Qøm9[y]r(~}rmm5@RmkPf.j/f^ܞbtP>h.ۻK>,ٞRʢhulrG.hX9BtXy Ʉ«UpF;™n0)/WuHD*տ)5GEԼQ{MDuǗ்Q|DCSDkE4x@Dy3#ѼZDu"o{$k'%>p^CFܱIvLv]k@3v-xWڃb# (ǖ| PرQT-qLyp1WuǿkŜ:nIlYt<1?un06ڈ+4Ų+;z俹S_\ȜiX:~%tEXtdate:create2016-01-25T21:49:42+01:00R%tEXtdate:modify2016-01-25T21:49:42+01:00MtEXtps:HiResBoundingBox157x158+0-1 tEXtps:LevelAdobe-3.0 EPSF-3.0 pIENDB`PKGG@L~ {T|ABlL.?-Vɏagس>؄W@+pHKOowF,{ӿ3]xY2( LEP-c0+'poP<C( ZB7\kp}g )x!"t J:#qB<?$@T$ Gv"%H9R4 -OrF ,'PDP \ @ףqh{*=vл(*Dߠ `T)czs0,c[bkڱ^l{ 93c8=2p[qI\7}x]% f|'~?H XM9JH$l&:W ÄIH!HXD&"^!HTɘL # HVeiD#-h&rKG"/Q)kJ%REiܠSST՗@NޢNP?$h:4Z8-vvNkat}/~I!/&-MVKdD(YT]+A4OR=91XVZ bcb q#qoRV3D 'hBc%&C1v27SLSLd0O3&A$,rc%XgYX_R1R{ڥFc;GȰeddt<!;'ǔ˝{*o?&?(࢐Pp]aNhXxYqVdTtE5[e'yeyeW,!%M@Ujjj꼚ZZSu:G=^FnnMiM7<6q-VVCm6G;I}TT'^V.k{Xwx~ŪUhz\l6 }~~[50 M Iic35~y=ߙĘ1yl02mg܌on6kfa^g>ar|8[x {m->[Y ,Zagdj5FsM̚5*֑ BMQmmK;Uhfi67{Оoi`#X8$TY9ιyeUW~177[ۼ~GKOOguk|Ե}OϾ_ZW~F~~ jf-; C B M #5-sZwpTixQs !yå#7GGF,GzG6F.DEExxo+gccccccgW%8$$KtMO\LN:ܑBJH*ڟ6^.̰817g"3{LA`0K+kWDMvms⹩t64w|3n3os_r-- [Q[n+6eI;~)0,(/3xgoB].ڊDEcv!ᇡ=TV]|İdWzG~\w̬>¾}?Y.^W>y@WoWT:$VW\_3Zk_Q'_np#vGKM8QXWMAM9[eKH=!rۖ/kvkt_:̆oqwxۑk|G G?yK϶Nj=|!W_;fK/_>M-WWJ-33gg^zM{v ·O[L>-,qi̧9^Y&.W}J.P cHRMz&u0`:pQ<bKGD#2 pHYsHHFk>tIME1("&$cNIDATxweE7v! 9 9JNAQD$1!$AD "d̐g`rΩtt@<>UU+Z{B X~b;wgo oFOc>gZ፯!a LP_25o?_T'Bfs)ܲ3gJ~]7^B3IہV+i`}Q9Wp/x/ͬydž瓣BF.ӣ_xO\Eȓh:mdg\?>C(S\?7 ~T}3l3^ǰίP]7 W7'{{xeVO0y1N>x'г߳j`v!iHÄ~ՃcڎvgMFdlu b)>K#x)tI}P; ~]XEK4͗=ǷCMܽ*_OFyck(ggsYa+z fXA) e7J|&Y􀹘7d 17K 菥 #1(>:GX 1XOaT-6VcV93 uI-0?1gSZcFLC돩9GwDR~i9Ů ZC.zHLA ygp&n %EX8@ LcxD-ys!NCAp4h85u؟i#$&JeqxZ/ nF?uv&oƄ#%b|n[` aS-G=< k98X6 YgH#3wS&ċ nHJ_í+yy W+_eboAN`^cyT^@h#n`ނ~ѷcZq0wfP`+|)?[ۂI_|{h}#i "͔X̓Dw<#Gr.Y'"#Od2p+i`4O,nKܚ^e2[UXn<'^$1[6ZZ4'1mf䃘*/.˫s^z3aM$#[F&/5ΨAD F*- /3:-AMCxKcۘ8fK4}'3 7+9uqqBNۓ=сײ]> w3s)샅(KJ;ɧՎ(y"ͅy`6|0zqTW/>]59e.IRqh1es]p1c~ E/Jʂ|=p.a\\ J޹l4՞4~͹L\gvL94/yKav>~MI:.XΨ* U鈛HN}Jg~nu]3$Q4_y3~8|g?g4~> k3MVA+P CbjW"^Cw0Mĺ+1f9z-{C3< O-k7aGub mAxnK;vRTgpK$"vV+ÅLZ/fZT=5=76GlO?kLf3čPo}(~"̫[TG7̸Yf{N/A( <B'Y6UkV;N'P6R׹_GdFǾ\Ӵ*8O`أ'}ZGt:;Ϩ]^f~U}&4K҈x+I+k)Ŷ|Vۘe'tkG6dͩ&Ӈ ?WcWx|ݟb]SU(2%&~2??)q9Ik/eMIBw{%/̜!hy0𲮚mV;1?^Yrp>/d XFFL[Go3j(9@&5&k0 ̄v. ^N#TB3ϜBuM9`!?KMܮ~ZҁFR$!ڙx ܂a Tc]=E"q'&^MIGTO((>-×sX#XB9fEyCK5T{^҂wauĵ+aȌ2@RP+$%&Ґ]T?hX}q39]urLo@]vew ,;+q,fSM/]6/#|13m?hOt$J7)DDu.na)`'<ˊheF">bHRU}>j!<ӹ4ȂpNna I(렦=u*qtm4%cU:Vm%3QIs%6Rݜ,­gư? l'14 |~JdK+eFҿ֛V< +;eCʙqg~ 8IKgu9d^3?lOgOَ#]̛dU_K.WԮт/P_%@1e+as>#d{f=0kď`  Eهp.(m;Ak,5@GjPcQ~t}ު;yYx\=:S/V}d1jp+ec{l˖a3 B|6 bwI5b<2w#rp1ie[p5;K)8.굘ٛ|:(2sTƽ*g1 hw&2qWū gNVW4'j:\Wr;diƯMPG|K?|| U޴oGì~^G9MM,W*||[⍕y+<6M-SۊW". q %U:M׆uK%aE66f2v:#~L10Xެ'yġbfI8 $&Mq駽1#eUM]1mV{⫼OLYeX#'Ж{AgYGRmgɉ%Y1δqw-ZM$ڈЅN TմbEGs Y5cÕ׌~pA:"Ƨx4ҵ1Um7mt^&n$ ҮTgCX /i~+hؙjd@| 5hX*XYߑ'Mؘ߭=a4I^>ws us`GOtS 9.3φgJ[x9^u߄NgOYc9MG[ O\ҟ%Ak6|OW;[UR+I.+QE׼u.fAܷ#W;qK_?+kB0~һ-й>w+7U\oAђL}PZ0Ip6/ wlG%-l2ֳy[tN$`52Xi/Sl@K)ys&A$ܔC%WT}g⼣xq*08B i5&vl6z+[HL5B. Ih5~?Y/|fvG.\tHm1Z^!ц$ Pǡ*-ehqmXšXx;oyBw!.bUh3JodĮj|WnEk? ^RLjߣaYF)sŴS#RοmNǽi(3UZVel%Gf$j.V~&YTX f|.-8"|Fl][%&at"mU,jm.{̚ȝ7iOsߛ݄7֑0 mjaTڶ;CT MM\ ?$tr0×%A҄y9ampwk<|/>Iq >58E8:21;>,KhfSvxttC2MWR=DfDyqߡ-]77L.bn&6KLWZtTsYݱ>bt4aa)MՄߨ!NA~1pYhƒҊ-ֳWUc5gj.=MЋ?%\IZG8S:>%p 0^Xi(mGSscͩOFI;kH3O7o%y|0cӅ] -d}0[r꽨i[/]HBtG`IyxV u˜X5x Gq$.;Vtne[#t-Ln"-z(#%W N>Xouc&l&qq}Nܡyf' -,Vap:gHrWKKPSƧ3g-Tw =Wg95mMޒ}4Y ouh:@ n Op=$ ЛKR@a)tqaH~=Ez=|19Y(cwb?[x7,Y-KF[֏/pBQ2?da)կRZ{۔T~=7•jR+p뚞*%&A-\wvaql {v>N+Ofy5o 7gE )CigSjT|u_[HtR8V$~"/11}'4o<~RJN˓3GN]\m+1.̲2vv &G`3qda֌M(&h/ַW'{R?ײzazsYlY.+ٴխ7nt5¥~y(Yvq:yɔ$Wg{[_gSƠ%]2+oF3a+?1qUyaYW:>Xrp:L'S,dSCa WSE-G \)ag"ũum U366h/]R$y{ek2~Ǫ8O4%1IJ֬DҔJ'B96t/]jq4~Z0:[w<_h ݄OߛY,lj2NIo ^nY78 %Ƨ|VV NpPzZw;|]I;fB;_bµ՘8i[?Ʋ'@5.d8W0C kX~WTwaYMzApn X$iF*#ˈԇƍY$I;,>(YG.tMJO |VxS%s,G;,a~/%~/JYsy\|EJ^Acy.sKq=iٷ+3r$NJk Wƿ!ΤZ46atpKI%ja.;iTnݼz |r2{a?*0)H+h0Rvr;bt-㉥womjw8O-SWEmԽ/S] %oci'#v`[^!hP]hq 6B*.a;0*=7IYfTd߅9f,Xި & / Qi[KХQ:@9(0R' pjf ^'?:ЖnSDK{ygOT7Z uo9yߖҗmDܥv?*_K;DVS^%-Me nd;-mmY~'*Cx ܫ&!1\'mf|=fKkls<9[Vc>1l1 W uE/at}%0~É$FYr>qj*mTlAx,O-mpi'( ~-;@[=_MOaHGylI~V$\Uez9)_x"gVOGSBu4Gwi҆Iݡax?ߙMPF6T& #2N2hN',arz߅jiOg%Gދ_M{>ěsG'Sz ll[ۦ]J$oGHu$F.#qdiNM) sݨ]nf_x2cXYq5Ǵ1¿J!cl2K!i:nf@{:ZN-N,b$zSWIe~UxvNJl9?~Ɉ^`9h~dh݌<2FhK^rGed܊9S}vu5g#QE!A#4f0A:l8&٦g.e҅%by9$?YRO)QOHUPHG<Ɲat%ϰ8$dV8PrJ@7Skk<4h;<-ΈQ uRw.{扒%IEߕj_1{ }_eo˰ZOR(&^X*ߣ5{KK/-~a5*ӵïT?G fcNT°I i_ʾ9 )YI^42zna.)x9/H:3;E?J^ ޏwS)y&@aMI*}8Us W ierzGҰ33$Vތ]}it\5ic~*f]3hI\J[Cx9̟f *78/%.ƺOuɖ4VobeP? sHwfLy$&b)uF,ǫgA^`)S))oQ쁆L=Y 2\2I<]VB$]AI5 q:Ő,&\Oc2>jtTtmyi5ޑ1/t-l%iCrN# s{C0XI#IxYK0բ#OBm+R]Cb9Im}R$EA I#\U]b{ wIwtk\rVL+Ğ5ck9]Fqj :2jj|AKiB %ou ?d0ZIhԷ>W$Wƺ/A^.!%Ⱦ82U(֤8DM}_Vm(~X :J!7 _VKŲ&SGyuC3L&l1I[ư"1f‹ TR_`țʂ, }GIS㤢 >kS6h0T6@{jQ+zb>-UY\V-=k{JAx0u0h#~)FQ]]BIpya_5_)J/KFW19 jciWk͖QSZ?KF*p%JN9T y9HĒSQ+L~kn|IL|3iږvSH Ix<5f&c$GP-y*l`z~ӪiZ$DMj)gF{_Je4䀎(!/OΛY~CrB=Ey)BMׄsTy\*um5W.1%ɉڄ2@LlT|.@|7R֝ibT2Kyq>Z9YT`µ#lϯ\wR9jL3E.+wlke%BZ~rFwzI qb1CHr^re gf|Ba5]WZ ٛm9uցYpcTjp0;Yl@,K|v6R]oN&x %n26V۩vڞ g%w )cŖ4y꼘 gI~=D$PXI[0,{"$ӵϽW˷@U@Sj>ή>SՃ AR]0)+q&a:~/հF5lJ~T6%wC=3v|W͒淚t2}Fuj{3x濖vΔ;IRM yVWR\6ӟuL:uVԛ%(S&/.Z7kXdCX 'D3',6|MU:04כ_5)Hjx2ڪΒuTfݎr~941Y-m=KyZBFBڊ% #4Xkﮭ۫A.lkrVc_1P\*RkO4~9,] oT -kOԯ郶d]qnVD!3J' *CI=0)+2R(GUY>+J6mW[Li9CT氬91S0ڹ⇉eQHvV*R^O;8뾩䢏I{Y!EǷ?e';on襌#/R=BۖY*fj/[g/)L TPmR\Y;HyRZY0gК))oIc2E&l4ޓ7%ʞU jɢoP 'qێ,8GISviKe;)  -‘ 29>C{gj$3e 'x~gϢz%JGRjjz@=;FJ܎sCe0lX vνy+$MGx&s%nwjN>A}HxL%n΃l6y :lEzT3}E#Pٖb'_dl)gg7;;1e ʄ.>b$V KCN[5B$kjJJF :UFQiK[Uru ד=wO+vv__Y8~5Vyh͓r[s+V@d*'3Adb\S%[Y*F.oav;A(-rodT/IN3k[N1g(*E;_gd~i/4zFKeϴDRѦlL֘UIf6ŕ{Ae rwf(]LX!zk)*b0wu׷H&3UAD 088FU㶞XV?LʺfP=x*fR\YR{Tϒ;V31<[s\CPĹp><|!E|FҼf$U:OzvLR]MkAZHUmBJU)F@ 5u%I)QV-~]rtW]NOV:ۢ()W{8 P7*[ Yn-!nN.S pZjK=-T .68t$&թsi R}&g}NѣZնCMI3Rn(e'X%3Ttbgnn3viY{ߕ]Ʃ'!5KyJpUy'by>N?E[Z60>Ym)TC'X7=4}李L8@W#HZ*9H^(R2<-*p BtNveL#}EHk$\AZ `)n07oJDpTF5%1U]f{0f])UnƯG9wh۠f뗄Xf:]>GV 2#jvPoՅ( ɺ\}/'>#-A_L^B%ͤ_\cƣ<5 dgnJ4MRĪZ2ҡz Ä3&a9|Fmz8Jcd)iKӨ&甔ϰJD*l`^ˊ/vȏIVV~ut? nTh 3CZRR.nEOq$4G\Guc<"| / *+V:ߥ-b'YV~78ۢPq\ qgҵ_;J ZvdO8bU\i82M9_nґgMuLj2ƝLe8ǘYl},'- [RKW%:-RjVR}Y~kM-%Ah tߢӨ~"/{(S+{5=&hJ\)Ud5Y9N8؆<t$iII\+ip(T+q0k}LJp,~aβB*Jt=浂kuUmA~Tֱ9%ԧ.ڷ/2~ m4pX;gפqZ=y~YqmaDuR0+#ґ0 ^%ӵ>}׍yÎ4o6cu-/5OlŜi2J3@Ǥ?T'u.S{.ˀW_">=Ń]NJ( ABm 3KhWөqjۢ?l'$kwHזu"e%ҮD0~ШQ{oNcڕL0YLt=B:Ym@;A/|D<HJn7]>$[Oh~%N$,mI뾔w:^B )Ҷ]ghI.h6}C%Ԥ4%ح~&놺eŴkc}U)rQ38Z;/vr*aKtz~'D>S^L$_Ǩ MBT\Kk'R[p\WC?UT|Ag.U1$i'qd0ϕLSI*y{oQ\@jʐwr)qjN_,d^<;[8o5>gq P_zX=>F]m;K.ߴY'kYh5 j5v.(]+ +*#M7) l##jeS )6 GPQ 9Wkj =b Rnܻ9sˣ# Ws)YTOiKݰ{ݔX!0kvUfJD7_/P2:2fX9(CO1ߠ7[÷$YBx |NfWA_ dXOHYþ"7ڎʲ&h}QJR? ®9h.~JT,S,L킊9d)Xs;a}dh5)|Z k uPծgRl& 9ba͇aN=AWxhs J"jގ.R%UYH8xM4Z-5;8&oSn#-!Y9OPfI6R kLm@<b ܵ?JmuFve37G2 -B ֺ''bj1f9m^Z@ڰ;bµ/%-O%ށiem&gC:[)nU#ڿ2eW5R_cSx|e_(a%ldKr;\e4-eF݄WK~$!B縭%m[w6oH3>'d> NX `0/jOw }#;~+._lgR&9$UP5jEWw U2WS`xXD֘opNsk*lxT5Va.)6Jp͜/)we=(Zv{i=$iG^nc'<} AmMȢeKzFJR>c9nIpe;gP^׮f齠2m8Mۏb(`@ӎ]֡TꊔyG IcgU)ECLRj5r y^\ԟ]Qy]x]#3sg}oe]n)qڐgnˋzݵxCuQ77yGgu;=<3 _W}|L)Z4-RBHpyv=I> >qω?MZTEI@n4!%chٝRUTeL+·bd'צGqϢ-܃c%ůPD4J?> 3?oܼ<~Ƶ'1E{٫O{26XsOdC(ubCV4XGGxNbӋ&/a.cb,%%e] Y,UyT;74+)%sfҤ?SNՖ_5SsnOqXj7枇K״3^7|Bʯ|s57|ѤR8Џ=*i#-s9]E |+|/awpwt˄}Vx\ө~JgB +4S+NhBDPpF ]f6cغZ&!:B!>+pӊl/Fvy}M]/PBai Ewt*>[wMKyLJEF/\R Lkn̩;᬴$ۇCS=%FϵgMy#;FblcCazcblc{ 1c1c>]5{Vc-c;=;߈҂b<όE1c1c13M1cb-1A] 0ucnSjb<1~vRxm16oc/n8Gqt;߉q16N ]wc<7ž?썋R3H?K2&#F4&ܗżC?k 0} ۟]}gpy`b,si_7c;yjTuh2&su/[82/3J1ŠOfg^<덃!Y/Oem}_dY"w.wlγ_fڌam X<ИCa \1MIѺG.WbdSʆ0H3.1?9n+$(% _g8V>LJ~?0./f2Xuc4Stt^Y>3Ϙs.'$cjOZ6am1Q,+ 6Nk8s u]\NHQF%_۳ivK VZf(&a3QxB.!'/eo>S˲og)aFnkaI KWJ9sp?Wj]F\ >i,藚3ys^BUB!T˂/в!z<ֿ>.}GI{,2lAʇ\c4;LޏSls luLT1a6?ކKZMVfŧV+d1Uo%?:A^mo!ل(#>ZJr!f4᪼e,w=ja*~TĂiPW:ziـIda[=!iMCB/)UD8P6C)k3_Km#iي7FpK8رۥAw[ؑyQ @>Jʶf鼼@`JD@Ry83[2a.2P%(CQmuNB` UzP)wlneVu;ġB3:G<ةM\9ENRzTH OH9v3U;պ{%XPYl*R(xU(E[%lk)"ShU+[)fV+cv' 66%nEek|] 3Wv1W@ξ؃9e|YwLh/G^6p8MrMgmRB`;[k0gwb卆HGےtՙo%#yy ܓ!~k*C?N3*`[YzT&~X"rW hk;\8N]=>W#i+O紧{+ &]x}).}ɯx揸^qcFѧGn%ы?Od;Eα,_-ϭ﮽{OsoV&|05Ks+;p P/9&Sl+\oW"E,0}%RwsREI1SҎޖ̅~53?&l X^SYz PKGG(KK(virtualtouchpad/html/img/icon196x196.pngPNG  IHDR )iCCPiccxڕgP} mқT) HYz^EY"bCDi Q)+X X,"*(GL{'?zw93" ~.06|G$/3 Q@%:&K \ JJ  @n3/f_>L~ {T|ABlL.?-Vɏagس>؄W@+pHKOowF,{ӿ3]xY2( LEP-c0+'poP<C( ZB7\kp}g )x!"t J:#qB<?$@T$ Gv"%H9R4 -OrF ,'PDP \ @ףqh{*=vл(*Dߠ `T)czs0,c[bkڱ^l{ 93c8=2p[qI\7}x]% f|'~?H XM9JH$l&:W ÄIH!HXD&"^!HTɘL # HVeiD#-h&rKG"/Q)kJ%REiܠSST՗@NޢNP?$h:4Z8-vvNkat}/~I!/&-MVKdD(YT]+A4OR=91XVZ bcb q#qoRV3D 'hBc%&C1v27SLSLd0O3&A$,rc%XgYX_R1R{ڥFc;GȰeddt<!;'ǔ˝{*o?&?(࢐Pp]aNhXxYqVdTtE5[e'yeyeW,!%M@Ujjj꼚ZZSu:G=^FnnMiM7<6q-VVCm6G;I}TT'^V.k{Xwx~ŪUhz\l6 }~~[50 M Iic35~y=ߙĘ1yl02mg܌on6kfa^g>ar|8[x {m->[Y ,Zagdj5FsM̚5*֑ BMQmmK;Uhfi67{Оoi`#X8$TY9ιyeUW~177[ۼ~GKOOguk|Ե}OϾ_ZW~F~~ jf-; C B M #5-sZwpTixQs !yå#7GGF,GzG6F.DEExxo+gccccccgW%8$$KtMO\LN:ܑBJH*ڟ6^.̰817g"3{LA`0K+kWDMvms⹩t64w|3n3os_r-- [Q[n+6eI;~)0,(/3xgoB].ڊDEcv!ᇡ=TV]|İdWzG~\w̬>¾}?Y.^W>y@WoWT:$VW\_3Zk_Q'_np#vGKM8QXWMAM9[eKH=!rۖ/kvkt_:̆oqwxۑk|G G?yK϶Nj=|!W_;fK/_>M-WWJ-33gg^zM{v ·O[L>-,qi̧9^Y&.W}J.P cHRMz&u0`:pQ<bKGD#2 pHYsHHFk>tIME1("&$cyIDATxw\U?ם^H#^)"U(E *]DAz %Bzߔ͖ܻsgv΄_<$;{s=Sߟhѿ3.Fg#l6 8 }]%?&1{1?o_Ŭ &oO`X??l^̓!ڌ?b8Fρ啹_Z|745G.;7֠1OaXmv x֙7pȲ1|Q/h%.RT@tݖgh3@>ZkK neg+?/GKQ7NP?tE878B~Ĕ&b.r<H 1к^#z S3L%4f=yS8_p=Ӽ*-hy (ܳQgv|!v#w}̨DVs/Jw[ܗtg1W|1f'D ~D|=Gt/7CHq[Cў6+%L_`;y'j>I Mg'/&}HL;Tl(AL!C&# _!i>ٻ0؅oTznzkF<_^Jo}>72Չ9j+ds]l"_^ZN ɽ;ct!!V&_IIg;Yr]A.DۆhNtz+b9|ôϤBp*Һ=؍kͰODBND Ӗ 40˘3fi~ Q:N#+6&n@W B̳&:8H'aƨ9aKƣPwu"ON^_OpzGXH<{r,3w~9-(`աysϠptGS0 X7`Г,Ӈ^t: cUH]`-<_;}Ccz:/lɍ~DXE)&5$,=)\CZiٗ8uiOh*Q  yA!fWOuwmo ;rѺp!Uo,Pbᄻh[a _;1s_ԟbd$Ǎ}`΢\#̰9Qx\nt\ƐuR3n쩙Y͉N!9&KT'얩 O^H  Y,s?CCblh)2s*6d|2_A)~E[8&c2QKƫ#nBUPTp;BAid쓅_0W\LUohPN9s ILMw</+ʯAtq?6#t;a,w].(/ Ԣ6{_ xŴ)aSXؐ>,w?0_fFx?tS,6J~/VU Ϳ 3]!:Z1NAiNECfl{?c54Ӌ3w9k/}AOd9V!z^[ >f]V_#A!N OQA::Fe;Yu,+Gs 6ctN ~P&n>5sna]9/LDl^: 菷(Y/lVQWH}atW%5w`.~ va-~ľY,< /27+Y81z^ɊS:]cBJxv^[)AYE6o mu-Zz,؃?xO&"arf۬ L _'`$ȾjX8LkJ<8r%YVakY5wflM5<;~+Ѽu bYݎ۱R+RK,y}F#>~e5Wb Zz2}O&]{⍯;Gr`]lA"/IuNtj,juDN&!:ph6mtJƒ~L܏꼵Mu9̺>&\ns@RK]`#u?LLb@9-#:>Q%MZg~+mjr4enjxkcwgRl1ܥUʭN d &e6%[8]RlH\|[M`/r')rg_`[0e4=T˨ͻ?<ۜș匯c];4ƨ| 9Xe<gq7 '3S76:%g*^Ne+q FzkRAg!ZKqbo): V5 @ftOO_Ɍ1u#ZFl\u!omt+\vx >,#~W.T uIx]˲cކ.UEIAnyCya"s%$Nɛ}/= 7):z Q^IXpi,[BSdQGkLܐw~ʤ *Ou~уy0ZRbYF'c:ƀn^{i塞֜ʖwegȳ`V|[F0mN{)3ׄI6Oԛx$7ufQ_xhOug.D7$~}ua:FY3>Gelk|CWfN}vQ{ջ8{g6)BV =n&pK|@@Y )ԭɐ9mnW/;n=O&Q\A&cEf$lnxh;hlsڔH8tNyArɘᬐ{hse0nË?V)Z[xw!^WDvYU f[TNCyn17'\;O*wFRz~NbN#C`&>[g ՎFawcGEh ڒn櫹f.$`uhI:MN'OѴڕ+rhozZ”ky^^1?eV7Jw4?;O$ ц 4rQu)fC =h࿳V܏bݞŊeܓܸ57L_+}zHGyt'D_E/&((ش*h*\\Nia8&x ?:k=`وa6hwbqcչJк&Ã-<6S{0bu^ I6՚Je W딊Ӊ|#m!܆rSFܚbX~!]WE z8wS؆ኧ{+њV)+:zQO䶂eFʹ?a9$V;ϟ9+]CtZ Ku%>LL/,/pG䵟ѰcvTv&%Gi{ Cilͧ!IѝeWzVP,s{]ө1!"w7Ml?BiGYk(508nP< ڧ!ʹ"?SuhI;}l#{CuVm=+~˟r4Je::5)t?7vxIK| љLL< 'iZJ,^/J%DTVQBFZ4H1$l:AhѴAӖsHD\ocޫ4ɸ(ˊ tZDV^JEy*w"E00d}I ɏ| ꁞDY^;N IPQU0]3W33݆%Hԗ ̤] Y}`1wwHﭩ'OU?K$BvgŴQ0_X)adeXR:;))N=Y,P%/2!aQlԕnb}~Ncw#wy+4]$cDO79QzPOyJY g?vW%NѦwg' &θ!L0¸or븈WeK}5_[_.MX徖?DFq_%>aSr3n+9-=YJD֖@].8$Dg% V1k5WF|ѮWvn%:4l껰lȀG,^[|7C ϓƗITK(d]ڍe0ɳas.Ɇӂ2}„}?&'Sm5_"}MPF %~.Zz^pޟS;;D0FY^.TW=uJu,SΕ QV)k+v'ȕAv6qiJVmoH /k2}0U]xqiZI#Im?$!<73dW/}7IbU֓pWi5(?Off0-i:ex9_󊻓ۖ]ESb$$W(Y*-sb.0&ukܶ1ǡd2k=NsLۓJIWg0=8Gsܖ#~<䉋(,]Vm<'>]aF,;ogM ȝNY<_eu~!6H.E' c^$Pg /D8e) >˾d^hb;|Ԯ}m^pi"v4fS'Yf5(<@YM.4v:mAfϣ5 U[t2֊YOx?{7c?k.aP3vӱ)SVh{*Dk=:QR樼whN  @< t9- U#Ɨ{t !1{*Q;454 sz c,H ӹXȈKB1hg ltC (*Rҧoqz,]zX+*fQAT4,^1ƬKg2fPX>f)*oԬ[_ MOꉷ"QiTB_+YzWdA;9 o`}O5JHPKFDgr 7*ش&eJ r!|3k.(vUiڶ>,msSљX4{o} MjdV=^13#~R%Jt8f +̣3ɘHu0f)hQVCS Ɛ?&wW毐D;c9&jНg:&T&nM?: STp㞘DaU[; aJ,cFޫ\.7QM(ӕFMLuI;;h'RJc( NXX0ѹ bW= 2FF-T~u5ggށnRtR Ԙ$n)n*TUP%j GOO]ȧ^$/L\wiu; =IɊ Nv־Tl4!7MC,n$/DtDVM qv>L;H%({(;!!yOmo3Y5V5 WA81{0wctk[ $+2!G~waKyrJ=J-S3epbW(-r$&?-D0l-98XmJΥe @:o&Qt+E1,Ubt,oao#SҘ-H⎲c%TE'Nl$Bw8dy<3fs1+VPڊEWd}:)IpEJ( '1e>F SL\^Hʴinv&PIݹ|ynJ~bA,?iJr !s.%a[EsnIx'l5FΥ ۟ףaS*4fO ^+yyAh7f#1_ԘU2:d5ƵU"D-!d7 Pu_x;3'*$%[hR )u7U!J}lnRa_<2<gaĦԔdQE{Q?0 %ߛ=C$qtbnI;YO|o ?)̽^ t;@,6*̙-pe$M=]aN"?WMj&9¬oNќktA(W7bh>V7^!zo 5>m0Ѫ4,%>Irնʿd" e,TIp߯y DCBx!4ʖJ2$ܚUG7 ?"ڈx'RRi _C Z5S#j:(ćg:@'K,{}kpxL1J4oOsbEjbz`A\un_4/Q҅X):m';v7 92AA+}\[_).ݰ2qZp~'w5 ڂ1> j0 !*֟8јI͹gi",1;u;*QP/N=B8tzvUz̥uAK0F4+f z< b^IN{*yr 1Jӈ=Nk@$ Gg˜vؖ0!8(xR(Y"E fRx*0G~ 36D4di#&t.яB`GaAow$::V"7‡BW1K-co*nSΝ*\sb溇:-Y4mhb Y['Dk{]iMKwr<)ęuG 'Ht+gQ+Qa"ѧ*Ҳ)w#U> KloN/VҘ޸8[|. 'S7<&EK$@MOX+x+7ZXPj1EHȱޏo9P5v揘y>c5;4~HzhT>fBO?!\VԶ:Il2]j`@[zvQH'мȯ']vmQ zYIǙnTn-_*R:Mfh/vlf KtnH`)5^$gk~]c1&9M/11\͔Q]TAgHuiJWW6dҳpHԛkIxxs +O|1BvUv.hrYuy~$#"D*ٙ{Jd?KR\t- s]CD+Kۉ=k?a1,RnjX/0$ХA"s2r c'[OBLcɚSd7 jՊCLt.:,A%2wAa~L?"PI~c°$DV$TɒR W+.D݈6Q ȅS߈6,di9QWG-zFSlЛTCt=]bn!~P q1g͹eI(PUSeGr(fĝ? ^ZB1Rvv:)郳(<3lf]bwغ/_CpyXd.́?+n'?.5`XRY>Y&&QN _GBNJ>k>Bm즻uO۬O3B]6Bӛ )8/!&k[ UejQo+'?]UO>N`TKA`Ak&jH2Wʩr!!z5M'9JfL~B;wght:_&ӿ e<.mHGUh (5&ޤ"BXjNr _RxSJ8QeֶJI5k[N*iOJUҬG [QS KOJa ^b0}@jDv*/<׃+U6qi32_L9a7B:_Li(F`S\ti"U9: H'Z+ZeĩlT!OkgkOqSjBLGsqnf*t5|YiOא D-:,X &&nD 5_w)t&\m3B+$QJ I2&kV,,T dY}b^mIaM?"9];[}AR7 3iG׿C-ib7r([ϚU0#%?3߮}W(l*ƣܥS+GBV i c*;埽;\RmI흉 eo+b?N<5ͣ.΋oz:C '$/+BaRBaɘV(|EBuzo J֤e_nS!(R`unH鵟Z(&dt1?IH?_,,x") 3F'Iɔ,m( qgƫ/#8jDG;M4ޗ -a@`]h5E'QQAF®fN,]*W~IaMQCn]RCm;Ory؜ixs'!ZZrxRN/MLl6Q ]-1Ed% {~ VZWu4 'YpP;:f Ǔ๬s.&] iJ9b'G]O$~C>̼-.^'o7<2E,8)08eu_H/ϨWqq}E;g4Zbۃocq%ӔCG|BG $Yjj EBHKzkXfA!oTbJ8PV^hcCvL>kzK]7g&E*SϜXBu/.ɔˢ4,NMYNn#x| IFs9+ ES+]bO2~ȁA2-|}b&! 36zɽ+~DAaE wDQdO{SjIT F'>B2RTU#j3")(Ft&a`!6e"כ|O&O Irvj+ e"ԆgP$A~-1\K;iSx$o@t ɏ'3ikx/3: bg [$Vdn~O澠**~#MVItgAfP W&|KC.dݎ:&τi-vXǷ6!ɖ4vJV~aoSQm6 A$mDr}9y9rS F?T&I!!R_6?Q< Jkrh O?\7@$H Mg`M!ҍo$ sAudzX2z)Q bߘt>#b1a,\S;) dv(͓rVvSGh'ǚ:V#acY7ԆH{r6B*[9j&Utm͜_ɮ ߪh SHکF O_YքVBg,p"In@f0wҘ<.K`A4h;YXg_ڑR56FsRp)fMfmL_#8AݟY=N>"wr' VT$jTZA΂{μxZ ,Yu(VTEq^N't8Sq6'q4+OiQ  Sӑ7:_@[T>`I5hЌh8s0H}&T{o58hRKR}k]l(} \Qoj B,m7*'rN`oIH^U5s w2(黙3<&&uؙ8SVNJA̾+=+qч8<sT\ uwT*WS%t `fH1 6XD먜_{9yL6RN1h}SU/?bjb{G`\HI&_Qb_]%cM@ GZi*.Dϱ9 =oͱT,rprqtf䢤Q]{^5.ŧ#}7Ǭ5!d3-0 uY$\khQV~-( OJȃ+Օ[g9  o\Mߗzρ΂ﻊ{~.nF*B ʋC䉶`a#ufED'Xv/4PCsiLlEt.k"ì#t~T"E9E@@Ǒ!>EzT[n(Nk\_ -0i)!dB-2{tLn ?S)-NYxfSUIr@EkBM}SkESv*-5(Dq14 ox_%dVbfF% /%9Fɂ)Um/[8Lk9LP(ݯܨh-m(kRWR`R5WN̛) +5yUEiA:el2MħxGS] ԚK t`OEITi:QܠPé:CGW6a蹌Zď ›D( u_WL R{)\Q} 'imֵ]S14 |7DpƜmy]Gؐi'a.iY(xCX6h-yW%k)jM/,!^A K)3eOz3~SCǎcs ng?l?gCbHIJ]g/24X⹙!*>ӴFr/%Z[v/05.#^]`!uD^ Kza% D'*1-/K06 Y]$Du4K~A$7w=c9l aߵY`Ŵ Ψ7B`0H JMqSƿko2VfY-;?S@"$| LҔ $lam{$PD`ݒ(t=fcEg#xZjIY~ֳMUE;Ćw0iL蛕"SL?*NnDG1(/\ %З ;~ďTJ?Xig):OX~\؎ !pԾ )ˋDh%vt!?NB):@>tuT1h)bBcqMǔQ2FpTׅa#wP:[,Ʒ/-J>JKjDm[;DD^f] 7wuLYFkDUv9  ?̸sahYWE~AI $ b"<ԗ~PTޜeg(az bKEEc!>8EK__I\ NIޠ7?eG(4Ozꚬʐ o,˂_EiG%FJ"ylldȐFLfC}M"/(YF}JٟRf})BhhyXqM+oggi5v1bi,ͥ5ՈwQ]?IÔ۝ߢw_NxI>&kx+vP1JIlBUB9[e|f BPs(I'nƀh)v8^YΦ 5(ɟ.b<=r) ؘtEԏQ^)ej_v gP8ERǼ$ě{U%^ C@)VCwk ^ SqL ˜q>IN} + BHYq5#f.x+HM3Vٽ+RԎrQU(3U̢6FB+f;>4I]o0v >TdށNE}# [mYEy tf%"\j>FH#*-vF_KsNƚyse&T7L7I#:Ђ)I]4hn;Dy1H͊I6ʩ_rbHk <%m(<%oDNE}#%K ,gi|#_w4VjJw-Pa|'TJJғ*'xHc gއYڔf:C-SĿ4B/@2QֳqgHLg4\813^~ʸYh:=qwrTVӅ )fva3>jPAc;bQF&&+L6ڢW>aPs'2ug>w{W!|2zVvEi$jvD|-ݕ08~8RZ[*u8N`!X-=f W Dkg'1lC]Hn|H,흮}+Am|᭯g ͮKknWqۙh_a_]VQ̧SX^E$_Z),Evi' Fa ''Y=R> iۼiD-UX!0ץQp*)Mѫԏy*|i/<ʛU_Ʒɬ~\Ӻ+D?Wz ,J)3Rv&`movmf%6SZBPKUH˽4ILZJbcI:Gq-/GaҚf$" %΢y<Ϭ-˯Vw1~ͯ;(g(H*u7!xD!OgJpD+!7zEQZPJ֚t!TX[eBbՅuo+,"ڲ>K'TLIr !|ct[u]Zf l? O<\r*GpFk P5u$ 9q@œb \r)ScOKTkCNM#] z CCƔ*gM$ 'UK=xkU޶; FǽK~$ݙg})(֝xo)8MNcVmP ܋@ֶ H|oa4ѡbA,r*HܖZ&巫+gs=8 M% \z,T84B~Fa'[-8K,VR|6UGAaA( +Bஉ.B Oz{˓ Bm"n Q=LXGa;q8g2' v+M;B۹I_7#> Ξ.V4nVլ ^ D?miާ!e*H}:^\bhnp! u(4a" yJ OS1xG'. 3GJ?:v%'Ѳf *w~&F֌ՈT8!fpY=}Pa!30W5h$>"PMR#H^-_ , ->I_%%ICvl*x.\GWW3xU{ƈyoʖ9˾'uH~KtH`pk_Y1+Xn ė*y~ c8Kؽ~n 7z`$ĿIxA[ m+9OJ4RGv *Pp}*<}.~W{ukk| ~CtO?:AB)df*)gYz5Hu[ T_ûCtaVza|SfI6"w QZF,YkR7&Dߨu3/DJiFZA=": yr+tr1 ۯ_em*nGJS5V̵m87 N{;IIuG&;:B]$:Ma@|.7b^t3ެܪ vM'T DAvN'Fʔ9ߓ]p5`^P֠jFročlteOc瑼¬Ec,P\ M5";)gH}Id>'Q욄ȑs_m|f1iKk ݷjN$F}»tcIk $tT!*&mJcC*%Yy|FL28xWdTu{-ĕE!ІkT?K* s򺒌 -4p$i֬!Z ]g"yo鑦&?+kLH~dHBkt/l 3BPG!b[4w7fLcҔQoRFIS5)htK»&zLKQ?">)x㑂G R;ddѤU[X8VJHtdP*]P{μvn6Ӫ.l,6.6@}N{6TO!QhT4e۔Dw"ek|'NӁIJAߞN(sXB6~Ǘ !VۅD ֤TQ_v|ؔ^֐xO-?@zO_SZyMmw8` 8N?蔣T.ahtb_Jp1WܟDzzN ҷ쳮(ML6vZDSa/mXa5v+M1 `Uٜ<ϐZ"{Vz2Nhgf ^UjC>sNiLU9&=Y?bk!K(3AT'N=D깆zVϋd7Wx ?ib'1"=| lb+ )Z9. /B5VbG\ۗADo zΚD_iݹjev9g2v88P4_o cWaM#0IB Y5Ze֜zM)] OaU$3;4wɹD|. G4vY' 8G/`{sfPxe,0A^sQ4/SfK8{(5m"хY\įP+1lf4wbB1Zg%kJ- =ɿ^ϔi:,ދ+)Nj nL"$Ft,]JvrJ'}<45&m oU6aհDVhz>^I@s$ѣe߭T89}J!}_)+Fv|}<9/mJt@P>,h et%p6ČӍ*3ĘdumoSd;WX=ə6q4|]6@*{oc0i NH< T{e,}>ZWɝ(˶b7O>M/VUqV0oӰPS{akrTy/)+ y\S4G|;-i)ZxNГSYT0J陟8X WR5 )ՔI %,x~A&H~ YaP:W]v~[ބYFJٵhOK{/?l!!b6MΜ$)gFl:ο;@oہzD!On{EQ.k"_ykBx uVbNOb4 s@G9'coE~Lg3&/~LX&:5 4;Y&AFyzڞ)݂?W8Dᆐ+FDF43-U)p;k 4/뫳cEKykKЅpl.Κ.Jɣp]!TOQ姘vx.%~6x/fy2l6~z \$BDbBp H1}o̬}=͌)R&>(GjIh"w gEM63D!QPU3 s\o_ͩz omI$m rgcSE= &qNي*ׁUA!0.畾Siɸz ߾޿˙5O!w=Ne%̢ɚ'0̫ 5n( /ru"VqU8^|-z1:h)@܉8;3[ }@,tQ 3Ʉcyb;.JLU`PT Վ>QZO հE}/+cmq4rJcfBr _`ttr:㩿}C%5{3DyRSxӽJH?~oxd=b||(BUp.xhs M({ 9t!w\bB˺X-3pl[m Kuz?Tgi6%j>ğu!}:E,,Q|xc7G| nĖG/DZMPU5w]7ZYίNja l{T3 'O stUg zC/m^…Dg+2Pt >:73`Z,odHum Vޒvޛi؍5RWR rֱ&zyP!Oј0EXh'ڀ_f*cTM x0n#z[^j/oiZ"x՞Ǒ C#SY/z& $^)sr_O1#Ox&*sF+ΥS%װp>է,w?BK2N{"sB޴y^Ϋ'釙N<:{IIy(&4ղ:t3B6LjO4qu/N6h~7gܛ\{`ŋYqGDڗ=+J vůCpԳo㙴G}ł1)īn=ZrBKxyE`/:VB"m-݊IkDK]U9qZ)'!Bhn'5 \יt:P)ͯ&mۉT*U=}SBZv"A:-G>#OfZ mx5kHBYJD"NAX5)7CG,ؑĄEyЗ[X^ϻWO/*Fof#q^mL |*qFA M'Es0AƃJ[MMCf©SB3f6qr^ۂXdg)ՉSXh ?x8 1]_JL?Ǔ2)J!IiV!cvUW/ Ywڅ"EuW*M_ caD+@1kɂ:lbC4.;WRl12i>^1).G\mm;QcʷNJJFFw|Vjh/Z_cmCǪ?ns6ɋw^Ik$:?N^湙 w&EP1sm'H64.a'8/k?bxJTw!1}#&&"+Ty!fvK" N2iɳw(Zz3q 'o>H)MKj ـZؿ;~z2,}BnPp 3< tWE-Q 9(*?]R^l$X}:#^R?xXR:~*MDv8]We{=π~K1ܲ6vqﴄԗUbط ygR,ŧa<ޕ[F'KP8m&;ކnl,RԄY=<6^&&,;/ ~q٘h q|c̃/9\7igw嗢~Sn$6.C ťD ȼ5c7X؍MҔτzwN{;mǶ]Ya|-ŗ nm4 i ? PKGGc((&virtualtouchpad/html/img/icon72x72.pngPNG  IHDRHHt )iCCPiccxڕgP} mқT) HYz^EY"bCDi Q)+X X,"*(GL{'?zw93" ~.06|G$/3 Q@%:&K \ JJ  @n3/f_>L~ {T|ABlL.?-Vɏagس>؄W@+pHKOowF,{ӿ3]xY2( LEP-c0+'poP<C( ZB7\kp}g )x!"t J:#qB<?$@T$ Gv"%H9R4 -OrF ,'PDP \ @ףqh{*=vл(*Dߠ `T)czs0,c[bkڱ^l{ 93c8=2p[qI\7}x]% f|'~?H XM9JH$l&:W ÄIH!HXD&"^!HTɘL # HVeiD#-h&rKG"/Q)kJ%REiܠSST՗@NޢNP?$h:4Z8-vvNkat}/~I!/&-MVKdD(YT]+A4OR=91XVZ bcb q#qoRV3D 'hBc%&C1v27SLSLd0O3&A$,rc%XgYX_R1R{ڥFc;GȰeddt<!;'ǔ˝{*o?&?(࢐Pp]aNhXxYqVdTtE5[e'yeyeW,!%M@Ujjj꼚ZZSu:G=^FnnMiM7<6q-VVCm6G;I}TT'^V.k{Xwx~ŪUhz\l6 }~~[50 M Iic35~y=ߙĘ1yl02mg܌on6kfa^g>ar|8[x {m->[Y ,Zagdj5FsM̚5*֑ BMQmmK;Uhfi67{Оoi`#X8$TY9ιyeUW~177[ۼ~GKOOguk|Ե}OϾ_ZW~F~~ jf-; C B M #5-sZwpTixQs !yå#7GGF,GzG6F.DEExxo+gccccccgW%8$$KtMO\LN:ܑBJH*ڟ6^.̰817g"3{LA`0K+kWDMvms⹩t64w|3n3os_r-- [Q[n+6eI;~)0,(/3xgoB].ڊDEcv!ᇡ=TV]|İdWzG~\w̬>¾}?Y.^W>y@WoWT:$VW\_3Zk_Q'_np#vGKM8QXWMAM9[eKH=!rۖ/kvkt_:̆oqwxۑk|G G?yK϶Nj=|!W_;fK/_>M-WWJ-33gg^zM{v ·O[L>-,qi̧9^Y&.W}J.P cHRMz&u0`:pQ<bKGD#2 pHYsHHFk>tIME1)U!IDATxwV6ߙ0̠4E#؈EXbAEK4FŨcQADTPAI}1$yC[u9clӞmJD۫V;kҜy}S`ԍtݕG1yE2&nu/(__g%?#..')ct7-¿8x.Ώ4ʗGt]E_R8p 4l1E5ڑh@,7dmAHXṞ^_CNaE,jżX2);9EǍطǗRێNYm"-Q~/H]ZbsڜBT@TW.;=ɺg0L~OQ"HC/M0.]~h|݂Nc@L"4-u).˒+p_xMŹdg i٩*뚎%L5Id -[LkdFS2?N@6֜Y'Ik~mY8R>AzX6?AGٌ δ1] n6dbulW6# @VGv8؇m= "mQlQs*>ʀ׿>?>d=ЂO88~Bnv!IePх׭X.fđ Ǘ(ۃloY̭uI?7kUZɎ"'1c&uo㍃8~]i?S >[=Jv%ў;8=jo~yOLCdq%_b_ZJD~=!~pWq>8;n#fTs6#-3f1=ϖw{={TeݓsGPуn{D1^r͈i7g-^a9 1tf a^,>2߰5>׊l7*Nl2oj撏y$)WOk $C}K$^D&Ϥ {5~9&1p/j#9+%1o; ( C@4~r.{Q4hKކd,t6\)k)Ikb"EcD%qd@ox_"6dx-&b6l,ͶZ69oŸbܗ43:%[(P=A1><@e)Y[&kO*lKm϶-YԗK[R ۣ ]#^adfh'o !\Pu|gY^2ǩ^6c8?C.8ld 'ܼ:{fL3'_GJQjImXC&|3_?I>--B.bBmv$FMlD300һ͒9y蓼nޜQyqiPlMlRLP3-7׼&$;XB__ vw=R_'?>c6N# ksp#sbeSna;isRaSnhO,&߲כ䋉 $O1bal2F+ijUAm)4|򫈎A;b`z| Y))ͻO{vC,VzdL7;F=Ϥ^yw>01Ɠ_G,$I6h zSAx1Tw0Bڞ/2\^XLj"'iҬlCQPS}*qd&¨_W^#;6=` c= ג?uѥH\̗3cOy_]*!ܓ07qU[t[vdyRN90HSSaR~ԫX'K Z.+K;wnQ:l50 -u|ע\ӗ.hM~&zbDPi_hLIͳ!x)tlz_α{/W/rɑ< e]huL66XM)U1Xb\}z` ;w>!{Wdq 7bף?syh&EHpM+E|rK_.ؐ8R^LUa0 ORN ٝޅXx*웂6Q7}>ߖewY;);I T&b$sfnƧnEz4IZ^[Y[]m$߄|>[pq\>'MiJK)=O~WӌYE;Nt}p2ۧE>qt˚ڠKp1k:L-O[;Kadi)s  X@ل#=W̗(ix!~xXS :<''e[Ac)Co"N*[#\ID1Gc51o zH #?Hw܃aďxYJL$yk(p"ʋ76V+H;ѐH,%>¾qޖsxGፇ5_LTi|vS1=fSN*hӉϑCIFK~&7?U\Bb85tW3%^[MY|k|)O|)~K ĪN/ |A.Y' CW%K1_Z6z!E80Њ(qv፮N vC5 ߳(ˌZ5Ehq+2q,OoOi86 .*랒ܨ)~^◔J?KA4h|PR2.ٕb*Y7WTYl8 !s `X׭yM1\Y¹4dwSoRqY0a^>=XdJ?&&RJJe?A⸂CO˿N<#$~(^k:eLf8-x+cmm۟f1~",~ҍ(8֒jY+İ1MjO銭@G=&okP&xV2eĥGSD=On2fPD\cDy1XRLm+V]6?Rſo:g$#ߖض8)r!;eK/Z{y+NRE^TxB;G4fo~u5گT5e}%MحgP?)<7O˷un%#uWi^Җ3ѫBnJfOy44XJm)͡tQjl " OYZzw]b K (S=6BF۞;v;Xy+|8ω?N)ZAg;MΩ?F6ٚ<#<Һ5p7/JulT_BC):jݕ| N⦫l{MDtQĚkQqŭ5&zGT?7-?/݈ڈ#j׈dhVe#ꎊhvDukFQweDDԿQ$m눶5mE0#bOɬc#&nŒKXˈ.Et,&m\Fe 5ߋ}]t$?5_2c9ϓeS)Ӊq YawN!nO6-)X`e]3Y?fHiH˄]#:hbמ^z""FTQXD ͆G4Qղ.W.E|jIL$bnQ=0/-haDu5#jӈh{oF舱 Y+wݼ"?b׍hwWDV\^svbIqlt'eG(?x6f(CÎi<҄69C"[GMDC#Z򍈖FYeE|Oz]"~Y鮈ohEDa-+4t3t._tF;V gdLf+i8{j*ڋ_O1{IQ)؎`b藸軀meSʏR22޺yAT4wHYq<6g)v o0*K@i6rW-U]#aFdw"ae#>(? |BDҤeET?5X2>bfE4+KEDՆU#G4-ŀ# kbpw-6hqD##ꎋ<&sD}Fv父/D b#V_Ѯ"j֊X}6"Zw_;#5DZQ{|jL}.T<51L"Lmm1;m_TϐQ|1v7yå -jb lvRkܖ=4fT)6j5Nғˤ=վ}דQESTKXtKzKeQ5ô߭Z?m/ VF(#\"̎4"Ⱦݕ"ʖFQQQ<6؈G"*/\/ͦETQucDgG8ĈK";FTQ)z@D{EQsqD'EJԱ6j֛F:3-W`>ǩgVqn{cBb֗M]_MCOjwRo[U-/lOu1nbʏ ^ _'~QhdYqd-hKo&:p-Rvk?ޕOΚZ/8جRIGRZ)rܩTPJWcJ͋[m:.7{6K}r1S gd+pݹvޅ ͯWJI(F~- 5 K?/K_ÙKΑB<p¼Syu*Y۞>[ ) ]w΢whֿ*Y1P-fYL{E mT%tEXtdate:create2016-01-25T21:49:41+01:00%tEXtdate:modify2016-01-25T21:49:41+01:00W1tEXtps:HiResBoundingBox157x158+0-1 tEXtps:LevelAdobe-3.0 EPSF-3.0 pIENDB`PKGG@%virtualtouchpad/html/img/settings.svg PKGGj 7qDqD(virtualtouchpad/html/img/icon114x114.pngPNG  IHDRqrs* )iCCPiccxڕgP} mқT) HYz^EY"bCDi Q)+X X,"*(GL{'?zw93" ~.06|G$/3 Q@%:&K \ JJ  @n3/f_>L~ {T|ABlL.?-Vɏagس>؄W@+pHKOowF,{ӿ3]xY2( LEP-c0+'poP<C( ZB7\kp}g )x!"t J:#qB<?$@T$ Gv"%H9R4 -OrF ,'PDP \ @ףqh{*=vл(*Dߠ `T)czs0,c[bkڱ^l{ 93c8=2p[qI\7}x]% f|'~?H XM9JH$l&:W ÄIH!HXD&"^!HTɘL # HVeiD#-h&rKG"/Q)kJ%REiܠSST՗@NޢNP?$h:4Z8-vvNkat}/~I!/&-MVKdD(YT]+A4OR=91XVZ bcb q#qoRV3D 'hBc%&C1v27SLSLd0O3&A$,rc%XgYX_R1R{ڥFc;GȰeddt<!;'ǔ˝{*o?&?(࢐Pp]aNhXxYqVdTtE5[e'yeyeW,!%M@Ujjj꼚ZZSu:G=^FnnMiM7<6q-VVCm6G;I}TT'^V.k{Xwx~ŪUhz\l6 }~~[50 M Iic35~y=ߙĘ1yl02mg܌on6kfa^g>ar|8[x {m->[Y ,Zagdj5FsM̚5*֑ BMQmmK;Uhfi67{Оoi`#X8$TY9ιyeUW~177[ۼ~GKOOguk|Ե}OϾ_ZW~F~~ jf-; C B M #5-sZwpTixQs !yå#7GGF,GzG6F.DEExxo+gccccccgW%8$$KtMO\LN:ܑBJH*ڟ6^.̰817g"3{LA`0K+kWDMvms⹩t64w|3n3os_r-- [Q[n+6eI;~)0,(/3xgoB].ڊDEcv!ᇡ=TV]|İdWzG~\w̬>¾}?Y.^W>y@WoWT:$VW\_3Zk_Q'_np#vGKM8QXWMAM9[eKH=!rۖ/kvkt_:̆oqwxۑk|G G?yK϶Nj=|!W_;fK/_>M-WWJ-33gg^zM{v ·O[L>-,qi̧9^Y&.W}J.P cHRMz&u0`:pQ<bKGD#2 pHYsHHFk>tIME1("&$c9IDATxw]U(߹3ɤ'$$BBGt"JD銂4( HH M TB({tRgXaf@x;ܹskW߅/|᯳0FLj4J"퇙z&n˿xu_E9}Ny#b" xK++XnDX^“=^'vEo_P}9#ĞoPڇo3ͅk ͯ^u#7~+`Rחuމ>KEϊϸ{_!X ֝˞d/똺WO/]fX0)+qc9zo6I V"NBz4_ !RHBP7rBWTf<'q+qW;ri2ƾWYgӴΠ (6RyR'5=)æX`VS܋S\7ۊ2y=q4ze6PLI3fi1s{n =0y7ǁ qʹGѩ?woND)^,c"gb Bz.Gq*t=֦2K*M?2)g|1~CZ RU)&;Vosǫm-b(C:K ВބΓx ip-0~޿&1|  z|❟.6Z{-g ;00ǑlAt!N{и aq ߟbۨELz.iXRKL}cr 3fw‡x}>Iw*S)_]~1Rc)N3 +nrc| _AR\'V'_#xmQ>ZwrQ KpP5ܫ0|EznG2(vmtJQ$cq;O\.*evicZ f3y}O^8eLٖ QlI1(DbfX^wU sQ93>;WjS3c 7Ѫ<76N7qp %7y3φ=iAyF-euQPLJ0.24,w9ȂBE/]3Ly13)+b&50#L%_΁( 0XoqD mڷ E/Nj?y3գ6]8<{\a*4\Kq6a#(m➤=)3 ۖo]ʿb?BڧLŃ[dIsQ@eVdݕJF;:,xDE3JPLDu^`7-"Ѳo3z6QGqaFNYbB`Bшy^wbFh5_3 !͋TR,ax&8<{/DxG;}gimvi4-LN6{k ܷ-gTnBMbtܔ?/FlJDFx K9*((F`W*wa@Cxr#^#Nu͌MO3ZifSQX-Q;R>Jea-6H1sfu-%Sc-k: а]GStJEhZ+?Өl@~."!d˼Kx4bBo7 {= /1U;)};뗱<:y#66rrγ) med&ybwax&"cQ3ix~&}cHc_iZ{Yk~L贕VaT5g$iUtR&gs۩#K8mѝ2q|cśyt݂ac9uf{Yd>N`}>?O!NbX^k͕CcrAڙ!l'c<~-h} |!?dX M߉t>BǿMsL?8UKh ؋#9uh]A{bmZr("5SYsGZRe<2 Ove 6K^g!Rfp'7+Yu>Nu?>ؔ$dCDe AK*$7/.'m}n`XcNFE(7׸\ƎeFi ʫYeir2+ /.?ϰu{=|zjoatW_458:QB is?/ȧtXy@?ؖbLBhLK1AXu#u 1ϡ)ޣk,k~R3[m.Z WQ)4# BBq7:ч!,L>@ eo hn7c =EK!<DnO"M LDGɘ ^͋i}ƳwT?2‘tnxq`_2[jʓR[}?iO#Sr<} ukؕ2o\z3X( bb~owҺ s_SL1}4Iw=VC]f]2|58KV>F.W3`'b\;k`ʂa )~#`+L?xL] niyQS9P(+qvfB+IUWcqhX](vτ4#uńʥ?/I)]@e8 G3Td,.L Q,?KRn'6՚cb~oSyb"B> ,}w $$\I29F`rh|.l7$SiCNmiZ-Y?+TA_M@C rPfNf)A~y.]Wz[Fp/< ?}2|J(m۶e b8wBJ8LuĦMXb z~?S܂U'>0' a%O[7j,Յb&. jN)I\EfGq6ϒb6Þe=+"Vz70''Bb-n !k 5M8)PK% ŹM퉟xfpHޕźf?ϓ:ce{ANˉy{4 (D^F.bWm(}jie+(T^zk)8rsOgq:[ՠчbBs"Sԅf){cqUVyAqiPh1'E 2j{Mm5O:MUq4Ų5 a=e7␝t]c~+S95ɲoYKREH]2tmc6lD1a0Ol7g 81^Vv=1w, nYs#O\”,!:y14ea;IkԄmRfӏpf|#bBcoF<09HJOP(VBxk>Tdz}W/8k¨o[y`ra PHuMK5=¼;w wf+$Cy>|i`Da&#i%('R^wM! Ea(B9[p#"Vkp)M%\2ehngܱ=[YlEWFRA9=R=8$_MBS۾Ez#?ׁUiC 6 ju+|dASiyL^VAYi(iX e"tpjWXW]ٍ^ͪM?R=.T<qX6zQ#(-{k$-ODW^}vX$Sl0e$]JqfZj  jb/Ná}ut1kf֎sh܁9ey45SPvcC fEuC8b[n[!='}w`zM}^5XWEHO{f{İO= Gq L?'y[ _gzf׺A䕍On(A 1}4E'9עvgZlqXx@NLNj ^>`!T`+㦺n{*?L 5mb'=-|De%K=!-ƺm'd9i81#ylHUGp7@y-y fT3B;G.vM0Ut{mߌkߣacᡨ`'*odݞ83++梘kA)ڶa)C0RHs4*#r9YGe/ݖ^;7xrD{qÜq@S>epx^-f/˪s:|Qlqq d!L(~bO_RNWEխ3j Og1fToAvFxo!ʄADmEG1̹'=#m4@&WzmEss~6,xb9}1Cxa}_=+LD{Sc,H\!:<}H>}A^i]7Y`XאgS)kr6+{%#"u-̄'\J?x$=A/6F6v?9TZty͚ tMԋr+A-lJPIbO3DZG|?dDUH#H^ms'W3K&!#اSAd-.=R7e7RC_be j6y%kx9)(ٟz ev2$;"摟.,.2_H[F%Z>U6{Tk?MXrM8FU0p+fS4#J'<"*A+M']G؈;"4p'ɨzwVnR7+cbb;I5?;Yo&;Vf &LKG)am܄_ҁo]/P? $c$Vߌ]("/Q g^geKp(gdy!lܚwI)~8M"[ƅf+<7J]9Ev/{Eo1s(JfR?}O,40| ߿P!TԂrQy-6܆]DLҘ=nj`J\K:3>fWRIV;d;p\ѩ~Ƌ1qqa,A'+S"q4Cz>XJoKks[\Gy*y=?,` OP TfճSzmm)TaM~&Rvޯ*> k!!N{[g1ϥ\鈼J'`f&kϵ<XsQl*r4aڪ H׉s.ePFR&VT\:_?GH k'B/!{gwN`@1s1sJ8~۽}-J{w$M:T >L:1gbkW! ;|!)mG0ͣNԍ=I)9)_W{Ny&j:z4ƇsaO^QUI 6g + O_mEɔZd]}>\NXj&R-ףO7do"ƴJZrn#qfXkU/fu_7)3T6?^16_"t=ۇsZ̪,^%]"\NqQ9I7BE+YõO)1D-#g\[gw71.rՓ=+K,3!{'l 84U怨Z̴hěB59(olOw&&4i!X#*4(3i^wYr?l"_+uދi8Սi|f-87D;߳CuDqϜ.Z,uv_{xF Y}] uTlrzt-s'>' \x~ZL>/Q`bÓT/Gg)D͹o4` Ҳ Y?/;n1G hLU/p n5wsJ-U&Lm8+~/ +ͬLmQYIE8CX哆;.s‰Iiǂ͵" /HwF]e^8~ӹXp*.V_0zRDJ"AKe/WJC:K68i2*_Ip-aC~Fz aΛvjI^Ĵ<*7y}#uܴs=q3^噳Xyk\/HqY uSvqkwjX=Z+"ݔ $.۬(CF@WHT}&Ͱ]h4; ?_˺IuO:Cb_fZAL0k]TK2S, dN_!O^z\Toو j JொNtOKYA0[ټ*Fq,˾ϳXhoQOcҶSXgnZj-1̂9B :=toƬ'oAaLvE`Iʮ95"-sT 2UcȥY`iw;~-X:;BhY4,,evz6G *>{[-ߑ &=1P9SFUڌ4+Ttq +Ρ^{NJUN>iK>_OFZ!W$u [̒=p~4. KjRIŻㅁvH|:30Q JL;Gw.}}IBޘNTx8 Dyǚn@any@ ԭZ~}]IcX "Vj=ݐlup1tPkc.b`1w{׈D1e}EMr]6l}>l:~T@Z[ѧRei^qOP9=WØ7Z7?ajYS}bxbAucba49G}GQxU7co,ҚlH.-N- 3q/5I pnGvb"DK& Q}꣔dD"?ʌTeA}ۄ@S7w|ތp*tWRŬ z xN!£9㦯=reχgܻ|$"OÐEQǘN1:Χ{<ҎX21L{ac)mh.d*E9S›+'l\#(ϙДZTun~.$Vճ~=JiP˽?١.=[gT)!j"d&v +| *jDjx'ՊtO̥xy%omxbfGHTx2ݡ}$šؑ'k4+gz)lKϱH;H[.ĭ-|p: aj}T7 ?gFxIcsRoE҅d{CѬv >#2"eU /g1W%R*+0CX*X,:\̓ߟFk!qFD1(jȽq#ٚ.X.|ߢ۹aتzDgʇI`σĄIͦ-/eM<_eaaϹH>ک%ӯƝ3qZ9椅⧔y,Ydb$P?O%ǐnG|'0s|*Z;rx R=m&ËkMYlݹn@|xxmRdQe*ӓTUnt6he_۟?VI=;iqFqYℛt{` lGŬRTf6:=PҬs{+K E=6)!'q'ao[xf|QKa| HO2F>,V.8 z=V=icSIQ3yA1o=%-BU9՜QnD{qru[(^aXF A5~ {nZ\d}Yx[:H@3omtQn6, AcB.m"B+!BQ,3ϿµspV_m\qBJEz|HJnM8ee=5o(6ȟ6#TRT?/kҲXey23́NHqєR?宔MKJM]Sjꕯ)55TgJMRj3n씺vMiƔ^:^MiRj"S9OJ=/N)=R#sԻzԻ1 )R!)RSڸGJ;ygVIiER*62'tmARxXJ4xxJKi)9 %)nkȳqlp-4|†FQ?nWoցc%hA<.ݾ MS#f-5SC^I/dxyBVK?ŅqH^܇ǰG,/ _Jܳ+!&`,0s qpZYܳtJO26ƦXIaZJ Ss*)5.R)uR"4w¦>)55KJs^xcY)-={T#~DuJ_rA\M J\dxipҵHcSq|*˦V!t4uC .q8+~y[Q.춧PL Or~$KK[e03xn2*tY?鐟id_|]ԾM7y~~Z_@F"@QflOsskօ9'uہa0#%G[+JP̫ 6'qsٖfo״KG[ӒoSs~B(ss׾Cz ߦ8Ҹ8]mxd-_v0YjӶm?tħc~E;NNoв S͑”\^iCG="qZ1AV`GfK=Oܗp5,6^^_S7peYz;.ۗW](]h($tPrdXmsmҚ;:B+Sn)/ȇZ6Y{@TL_~=)v>ΔS(7W0|8;w~\YNe@qITv[$F7pN,O<~>\<#Oe2ynB m놧3{{cY*'02r? 4M5Ⱦb :…Vl9c?j)E7~[@\O\ו#R~qPiКG0gi!q XSg(6iNTrxeJε8-,+&Q<YU1n̺<1ӋQ)rh>1!eIl~zx'b6V*e>0g5hmS̖AEkFdegq46pqG$"VV4aƝZ9k#~ Ԫ\:i3SRUvp츭jNp]ELlQy_25Bi~ϤL 2뮱57P",Je bUo#jI,:efsΤ-Ȭ DLmڜ" XOe ^WGhg(av9Xidp 9EeZ̓\̛~ʑRdҋToS[[aUҒ$N$3Wst\lm]14J ۗ`oa #<Ť|zj"#`Eԝsh:+J9'>aj]`),+swtR7 g"RӾXAW*j5cz5IfϫlX+W#%'.~=Hogxk甾ʒ}oMIDi͞}8|oY?KT=}u[Yq\T͊ A!ͳ$;Z A)wI7Drͤ6ve2C5&Z~I k"@eVli~ by_ "ovgQ~ѿu_vUO.xWYWz9m-A0^#b%^ҡ:Σ1f8+oղoQop̺,=.~l|zK37<ҋ˳-K~В~ xL| `Xƌm&>Δ?<ދſOfj7R?ƈw őY.ceuݧ24\W a3d.Ǵ2/$xUo%tEXtdate:create2016-01-25T21:49:40+01:00=9%tEXtdate:modify2016-01-25T21:49:40+01:00L\tEXtps:HiResBoundingBox157x158+0-1 tEXtps:LevelAdobe-3.0 EPSF-3.0 pIENDB`PKGG6sII&virtualtouchpad/html/img/icon48x48.pngPNG  IHDR00O )iCCPiccxڕgP} mқT) HYz^EY"bCDi Q)+X X,"*(GL{'?zw93" ~.06|G$/3 Q@%:&K \ JJ  @n3/f_>L~ {T|ABlL.?-Vɏagس>؄W@+pHKOowF,{ӿ3]xY2( LEP-c0+'poP<C( ZB7\kp}g )x!"t J:#qB<?$@T$ Gv"%H9R4 -OrF ,'PDP \ @ףqh{*=vл(*Dߠ `T)czs0,c[bkڱ^l{ 93c8=2p[qI\7}x]% f|'~?H XM9JH$l&:W ÄIH!HXD&"^!HTɘL # HVeiD#-h&rKG"/Q)kJ%REiܠSST՗@NޢNP?$h:4Z8-vvNkat}/~I!/&-MVKdD(YT]+A4OR=91XVZ bcb q#qoRV3D 'hBc%&C1v27SLSLd0O3&A$,rc%XgYX_R1R{ڥFc;GȰeddt<!;'ǔ˝{*o?&?(࢐Pp]aNhXxYqVdTtE5[e'yeyeW,!%M@Ujjj꼚ZZSu:G=^FnnMiM7<6q-VVCm6G;I}TT'^V.k{Xwx~ŪUhz\l6 }~~[50 M Iic35~y=ߙĘ1yl02mg܌on6kfa^g>ar|8[x {m->[Y ,Zagdj5FsM̚5*֑ BMQmmK;Uhfi67{Оoi`#X8$TY9ιyeUW~177[ۼ~GKOOguk|Ե}OϾ_ZW~F~~ jf-; C B M #5-sZwpTixQs !yå#7GGF,GzG6F.DEExxo+gccccccgW%8$$KtMO\LN:ܑBJH*ڟ6^.̰817g"3{LA`0K+kWDMvms⹩t64w|3n3os_r-- [Q[n+6eI;~)0,(/3xgoB].ڊDEcv!ᇡ=TV]|İdWzG~\w̬>¾}?Y.^W>y@WoWT:$VW\_3Zk_Q'_np#vGKM8QXWMAM9[eKH=!rۖ/kvkt_:̆oqwxۑk|G G?yK϶Nj=|!W_;fK/_>M-WWJ-33gg^zM{v ·O[L>-,qi̧9^Y&.W}J.P cHRMz&u0`:pQ<bKGD#2 pHYsHHFk>tIME1*(EOIDAThyUՕ/UEQ E128aD1<C'"118Dhu;h8- >92x'<#>t?vnTNx }gR7{)=O* ,;Y^$';M"{M&."VKXny:!jbT-tWQK'.W ao0t0}iˋMڞAvٓ8!#q.9,#;UG0iou{Og5IGR[W39D}LvXy6H3רijd,ì=Ikp:Mv2з*,'@?oGh>]&PމEaƷ&RU(y+ \̪Wh@'qUS]:Ð5#{ ”߳GdgJvn ;$t$CI1AG8c$`uAn\N'Syu+n̢t;d/c GcTa՜oDv 4MV-]9 RȢ˘ÜN%z v ?bFn̕O3>D(b,ї4hƗz~ʲ[\w/T͑8,3(NAڝHR5ק(*nqKԎTDL{y^lNܱ=C&sRoPR҇ ҷIIy2榏wP }ĀG(O"Ij_(~h0t;T-jc(=.|!_j=$s8i Rii%iiR?R}14twpO7ﱀᇑ]F[;}t&i$i=RNL:~| 7QOysHWvX^@#M+6 }@[c+7Ip{xMw|e5n\Ql^ͤw|3?AUA k=ӤA @g/mȕ(_M{4&CXPal MW|Mwм\R'b?ǤWx%4Wn4x-/=+0<~6b[yȋ_[G<ĵd_"?u%P}?dWĜΨҟ)/Νy^(ioJ'l>lx6S9>#XTGd~H?~G40Oj/n"ڝY2"YǠ$)Dz]yrHI. l8 c6稅JL#.0ȎbGT!KBNI:&UyѤ .|caYH,$ݓ[dn}IjSӥ=S ^_v&N' )P/]-S-g`^MV=wo f*w cz, H:/[\ޭY`IqnMj/˧+y $Kt#-&-)Q+KlVP 1qo:y!?F( }BK>bl[f'tmUfWFmt%'7{1d~t`hդ!hbKH{~įэ勘| ɋUC] /I(jI{ymz.t^EX$b34V4]|QG=y}[',tQA P|Ik8H;REy\3h/sEanrjK]^t&,><C4#`%l#s9./-]Il [ݿw%3X6_ݖeEd?qdu]<}~?)+FoqCԑ y~̳98n3-EȎ&{G$`W ZvFmރ(EKC0.438V턿7G?-ˋ9w?x`ZCdE ;ݍ6us"#U.aEDڈEFv"4Ώ先;FQCDDt>.FuUg"ο Xqg{gS>#[ɈR-g'Ԉ#vVD5h7 węC"6?tgD#jFt>nDDݼ'"*bVlܜ˼e{-}`DiY-[IJ` ->mWdEy͹uz}z }kvc/|y;cm )}*}aP~9q1@ד'˯~Fcc늍375i5_کyo^czS%bN]J/=3lV{>Ę1y ۏ3j$." p>wU㰯P'y#g*aZ}y7Sf*ƛѱXT̕~Әy; Mz⒧# Cdž0ΟAM6l }wea| HpeD=MgYxVx%PJ%tEXtdate:create2016-01-25T21:49:42+01:00R%tEXtdate:modify2016-01-25T21:49:42+01:00MtEXtps:HiResBoundingBox157x158+0-1 tEXtps:LevelAdobe-3.0 EPSF-3.0 pIENDB`PKGm6m6virtualtouchpad/html/js/app.js/******************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the Free */ /* Software Foundation, either version 3 of the License, or (at your option) */ /* any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY */ /* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License */ /* for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /******************************************************************************/ exports.app = (function() { var module = {}; // If any checks failed, abort if (checks.failures.length > 0) { console.log(checks.failures); addEventListener("load", features.listMissing); return; } // Automatically refresh when the AppCache is changed if (window.applicationCache) { applicationCache.addEventListener("updateready", function(e) { if (applicationCache.status == applicationCache.UPDATEREADY) { document.location.reload(); } }, false); } var ws, touchpad, keyboard; module.ws = { onOpen: function() { touchpad = new controller.Touchpad(ws); keyboard = new controller.Keyboard(ws); // We are now connected document.body.classList.add("connected"); }, onClose: function() { touchpad = undefined; // This happens when the server closes the connection messagebox.show(_( // and must not be translated "Connection closed. Please click " + "here to " + "reconnect."), ["error"]); // We are now disconnected document.body.classList.remove("connected"); }, onError: function(error) { messagebox.show(_( // Do not translate "Failed to connect. Please verify that is running.") .replace(//g, document.location.host), ["error"]); // We are now disconnected document.body.classList.remove("connected"); }, onMessage: function(message) { var reason, data, tb, content, header, stack; try { var json = JSON.parse(message.data); reason = json.reason; data = " (), " .replace(//g, json.exception) .replace(//g, json.data.trim()) .replace(//g, reason); tb = json.tb; } catch (e) { reason = "unknown"; data = message.data; } content = document.createElement("div"); switch (reason) { case "invalid_command": case "invalid_data": header = _( // Do not translate or "Failed to send command: .") .replace(//g, data.xmlEscape()); break; case "internal_error": header = _( // Do not translate or "An error occurred on : .") .replace(//g, document.location.host) .replace(//g, data.xmlEscape()); break; default: header = _( // Do not translate or "An unknown error occurred: .") .replace(//g, data.xmlEscape()); break; } content.innerHTML = header; if (tb) { var row; function start(targetEl) { row = document.createElement("tr"); targetEl.appendChild(row); } function add(text) { var td = document.createElement("td"); td.appendChild(document.createTextNode(text)); row.appendChild(td); } stack = document.createElement("table"); stack.classList.add("stack"); stack.classList.add("collapsed"); // Add a caption to the table var caption = document.createElement("caption"); caption.appendChild( document.createTextNode(_("Stack trace"))); caption.addEventListener("click", function() { stack.classList.toggle("collapsed"); }); stack.appendChild(caption); // Add the stack trace for (var i = 0; i < tb.length; i++) { start(stack); add(tb[i][0] .substring(tb[i][0].indexOf("virtualtouchpad"))); add(tb[i][1]); add(tb[i][2]); add(tb[i][3]); } content.appendChild(stack); } messagebox.show(content, ["error"]); } }; /** * Open the WebSocket connection. */ addEventListener("load", function() { // Connect to the WebSocket ws = new WebSocket("ws://" + document.location.host + "/controller"); ws.onopen = app.ws.onOpen; ws.onclose = app.ws.onClose; ws.onerror = app.ws.onError; ws.onmessage = app.ws.onMessage; }); module.touchpad = { onButtonDown: function(button) { if (touchpad) { touchpad.buttonDown(button); } }, onButtonUp: function(button) { if (touchpad) { touchpad.buttonUp(button); } }, onMove: function(dx, dy) { if (touchpad) { touchpad.move(dx, dy); } }, onScroll: function(dx, dy) { if (touchpad) { touchpad.scroll(dx, dy); } } }; module.keyboard = { onPress: function(name, keysym, symbol) { if (keyboard) { keyboard.press(name, keysym, symbol); } }, onRelease: function(name, keysym, symbol) { if (keyboard) { keyboard.release(name, keysym, symbol); } }, onAction: function(action) { // Get the layouts var ajax = new XMLHttpRequest(); ajax.open("GET", "/keyboard/layout/", true); ajax.send(); ajax.onload = (function(e) { var result = JSON.parse(ajax.responseText); var layouts = result.layouts; // Create the select element with all its options var select = document.createElement("select"); for (var i = 0; i < layouts.length; i++) { var item = layouts[i]; var option = document.createElement("option"); option.setAttribute("value", item.url); option.appendChild(document.createTextNode(item.name)); select.appendChild(option); // If the layout has the same name as the current layout, // make it selected if (item.name === this.keyboard.layoutName) { select.value = item.url; } } document.body.appendChild(select); // Remove the select once it loses focus select.addEventListener("focusout", (function(select) { select.parentElement.removeChild(select); }).bind(this, select)); // Update the keyboard layout when an option is selected select.addEventListener("change", (function(select) { if (this.keyboard.layout != select.value) { this.keyboard.layout = select.value; } // Remove the select once it has been clicked; this may // trigger the focusout handler, in which case this removal // will fail try { select.parentElement.removeChild(select); } catch (e) {} }).bind(this, select)); // Fake a click on the select to activate it var event = document.createEvent("MouseEvents"); event.initMouseEvent("mousedown", true, true, window); select.focus(); select.dispatchEvent(event); }).bind(this); } }; module.toolbar = { onHelp: function() { window.open("help", "_blank"); }, onSettings: function() { if (!app.settings.visible()) { app.settings.show(); } else { app.settings.hide(); } }, onFullscreenOn: function() { document.documentElement.requestFullscreen(); } }; /** * Remove the toolbar if SVGs are not supported. */ addEventListener("load", function() { if (checks.failed("SVG")) { var toolbar = document.querySelector("#toolbar"); toolbar.parentElement.removeChild(toolbar); return; } }); var settingsOverlay, settingsView; module.settings = { /** * The name of the class added to the settings view and overlay when * it should be shown. */ TOGGLED_CLASS: "toggled", /** * The name of the class added to the settings view and overlay when * it is sliding. */ SLIDING_CLASS: "sliding", show: function() { settingsOverlay.classList.add(app.settings.TOGGLED_CLASS); settingsView.classList.add(app.settings.TOGGLED_CLASS); }, visible: function() { return settingsView.classList.contains( app.settings.TOGGLED_CLASS); }, slideBegin: function() { settingsView.classList.add( app.settings.SLIDING_CLASS); }, slideEnd: function() { settingsView.classList.remove( app.settings.SLIDING_CLASS); }, hide: function() { settingsOverlay.classList.remove( app.settings.TOGGLED_CLASS); settingsView.classList.remove( app.settings.TOGGLED_CLASS); } }; /** * Initialise the settings view and overlay. */ addEventListener("load", function() { var firstTouch, currentTouch; var i = 0; function dx(touch) { return touch.screenX - firstTouch.screenX; } settingsOverlay = document.getElementById("settings-overlay"); settingsOverlay.addEventListener("touchstart", function(event) { app.settings.slideBegin(); // Make sure we have a description of the first touch firstTouch = util.cloneTouches(event.touches)[0]; }); settingsOverlay.addEventListener("touchmove", function(event) { // Find the new incarnation of the original touch var c = event.changedTouches.identifiedTouch( firstTouch.identifier); if (!c) { return; } currentTouch = c; // Slide the view, and make sure to snap it to the edge var d = dx(currentTouch).toFixed(0); if (d > 0) d = 0; settingsView.style.marginLeft = d + "px"; }); settingsOverlay.addEventListener("touchend", function(e) { var d = dx(currentTouch) / settingsView.clientWidth; if (d < -0.15) { app.settings.hide(); } else if (d > 0.5) { app.settings.show(); } else { if (app.settings.visible()) { app.settings.show(); } else { app.settings.hide(); } } // Make sure to remove the explicitly set style to allow the CSS to // decide settingsView.removeAttribute("style"); app.settings.slideEnd(); }); settingsView = document.getElementById("settings-view"); settingsView.addEventListener("touchstart", function(e) { e.preventDefault(); }); }); return module; })(); PKGGE5~~#virtualtouchpad/html/js/lib/init.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ // Make sure exports work exports = window; PKiHH2FF'virtualtouchpad/html/js/lib/keyboard.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.keyboard = (function() { var module = { EL: "keyboard", /** * The class used to mark the root element that contains all keys. */ KEYS_ROOT_CLASS: "keys", /** * The class used to mark each key element. */ KEY_CLASS: "key", /** * The attribute of a key element that contains its key name. */ NAME_ATTR: "x-name", /** * The attribute of a key element that contains its layout ID. * * This attribute is used to look up keys without SYMBOL_ATTR set. */ ID_ATTR: "x-id", /** * The attribute of a key element that contains a general action. * * Keys with this attribute will generate. */ ACTION_ATTR: "x-action", /** * Gets the value of a modifier by reading the class list of an * element. * * @param el * The keyboard element. * @param name * The modifier name. This should be one of the constants in * module.MOD_CLASSES. * @return whether the modifier is set on the element */ getModifier: function(el, name) { return el.classList.contains(name) || el.classList.contains(module.MOD_CLASSES.BOTH); }, /** * Sets a modifier for an element by modifying the class list. * * @param el * The keyboard element. * @param name * The modifier name. This should be one of the constants in * module.MOD_CLASSES. * @param value * Whether to set or clear the modifier. */ setModifier: function(el, name, value) { var other; switch(name) { case module.MOD_CLASSES.SHIFT: other = module.MOD_CLASSES.ALTGR; break; case module.MOD_CLASSES.ALTGR: other = module.MOD_CLASSES.SHIFT; break; default: return; } if (value) { if (module.getModifier(el, other)) { el.classList.remove(other); el.classList.add(module.MOD_CLASSES.BOTH); } else { el.classList.add(name); } el.classList.remove(module.MOD_CLASSES.NONE); } else { if (el.classList.contains(module.MOD_CLASSES.BOTH)) { el.classList.remove(module.MOD_CLASSES.BOTH); el.classList.add(other); } else { el.classList.remove(name); el.classList.add(module.MOD_CLASSES.NONE); } } }, /** * The classes used to set the state of the modifiers on the parent * element. */ MOD_CLASSES: { /** No modifier is active */ NONE: "mod-none", /** Both shift and altgr is active */ BOTH: "mod-both", /** Only shift is active */ SHIFT: "mod-shift", /** Only altgr is active */ ALTGR: "mod-altgr" }, READY_CLASS: "ready" }; /** * The keyboard. */ function Keyboard(parentEl, src, layout) { this.parentEl = parentEl; this.doc = null; this._touches = {}; // Initially, no modifier is toggled parentEl.classList.add(module.MOD_CLASSES.NONE); Object.defineProperty(this, "shift", { get: function() { return module.getModifier(parentEl, module.MOD_CLASSES.SHIFT); }, set: function(value) { module.setModifier(parentEl, module.MOD_CLASSES.SHIFT, value); } }); Object.defineProperty(this, "altgr", { get: function() { return module.getModifier(parentEl, module.MOD_CLASSES.ALTGR); }, set: function(value) { module.setModifier(parentEl, module.MOD_CLASSES.ALTGR, value); } }); // Allow actually setting the layout this._layout = layout Object.defineProperty(this, "layout", { get: function() { return this._layout; }, set: function(value) { this._layout = value; // Make sure to mark the keyboard as non-ready parentEl.classList.remove(module.READY_CLASS); // Get the layout var ajax = new XMLHttpRequest(); ajax.open("GET", value, true); ajax.send(); ajax.onload = (function(e) { try { // Apply the layout and mark the keyboard as ready if (this._applyLayout(JSON.parse(ajax.responseText))) { parentEl.classList.add(module.READY_CLASS); } else { // TODO: Handle } } catch (e) { // TODO: Handle } }).bind(this); } }); // Allow reading the name of the layout Object.defineProperty(this, "layoutName", { get: function() { return this._layoutData ? this._layoutData.meta.name : ""; } }); // Get the keyboard SVG var ajax = new XMLHttpRequest(); ajax.open("GET", src, true); ajax.send(); ajax.onload = (function(e) { // First parse the data into an SVG document var parser = new DOMParser(); var doc = parser.parseFromString(ajax.responseText, "text/xml"); // Insert the document into the DOM and get a reference to it parentEl.appendChild(doc.documentElement); this.svg = parentEl.querySelector("svg"); // Get the element that contains all keys this.keys = this.svg.querySelector("." + module.KEYS_ROOT_CLASS); // Attach touch event handlers this.keys.addEventListener("touchstart", this.onTouchStart.bind(this)); this.keys.addEventListener("touchmove", this.onTouchMove.bind(this)); this.keys.addEventListener("touchend", this.onTouchEnd.bind(this)); // Apply the layout now if specified; the keyboard will be marked // as ready once it has been loaded and applied if (layout) { this.layout = layout; } else { parentEl.classList.add(module.READY_CLASS); } }).bind(this); }; module.Keyboard = Keyboard; /** * Determines whether a DOM element represents a key. * * An element is considered as a key if its immediate parent is the element * with the ID module.KEYS_ROOT_ELEMENT. * * @param el * The DOM element to check. * @return whether the element is a key */ Keyboard.prototype._isKey = function isKey(el) { // All keys are part of the "keys" group return el.classList && el.classList.contains(module.KEY_CLASS); }; /** * Retrieves the key at the specified position. * * @param id * The ID of the key. * @return the key element if it exists */ Keyboard.prototype._getKey = function getKey(id) { return this.svg.querySelector("." + module.KEY_CLASS + "[" + module.ID_ATTR + "='" + id + "']"); }; /** * Calls callback for every key that is touched by on of the touches in * touches. * * @param touches * A list of Touch instances. * @param callback * The callback to call for each touch that is located over a key. This * callback will be passed the parameters key, which is the key * element, and touchID, which is the identifier of the Touch instance. * If the touch does not hover above a key, null will be passed as key. */ Keyboard.prototype._eachKey = function eachKey(touches, callback) { for (var i = 0; i < touches.length; i++) { var touch = touches[i]; var el = document.elementFromPoint(touch.pageX, touch.pageY); while (el) { if (this._isKey(el)) { break; } el = el.parentNode; } callback(el, touch.identifier); } }; /** * Adds text elements to an SVG elements from a layout key array. * * The text elements will have the correct classes applied. * * @param el * The parent element. This element must contain at least one child * element, which is used to center the text elements. * @param key * A key from the layout. See the layout README for a description of * the format. */ Keyboard.prototype._addTexts = function addTexts(el, key) { for (var i = 0; i < key.length; i++) { var name = key[i][0]; if (!name) { continue; } var shift = (i & 1) != 0; var altgr = (i & 2) != 0; var x = parseInt(el.firstChild.getAttribute("x")); var y = parseInt(el.firstChild.getAttribute("y")); var bounds = el.getBBox(); var text = document.createElementNS("http://www.w3.org/2000/svg", "text"); text.appendChild(document.createTextNode(name)); if (shift && altgr) { text.classList.add(module.MOD_CLASSES.BOTH); } else if (shift) { text.classList.add(module.MOD_CLASSES.SHIFT); } else if (altgr) { text.classList.add(module.MOD_CLASSES.ALTGR); } else { text.classList.add(module.MOD_CLASSES.NONE); } // Center the text in the bounding box text.setAttribute("text-anchor", "middle"); text.setAttribute("alignment-baseline", "middle"); text.setAttribute("x", x + bounds.width / 2); text.setAttribute("y", y + bounds.height / 2); el.appendChild(text); } }; /** * Applies a keyboard layout. * * All keyboard keys described in the layout will have their text labels * updated. * * If the layout is incorrect, the state of the keyboard is undefined. * * @param layout * The layout data. See the layout README for a description of the * format. * @return whether the layout was applied correctly */ Keyboard.prototype._applyLayout = function applyLayout(layout) { // Iterate over all keys for (var id in layout.layout) { if (!layout.layout.hasOwnProperty(id)) { continue; } var keyData = layout.layout[id]; // Get the actual key element var key = this._getKey(id); if (!key) { return false; } // Remove all child nodes except for the first one while (key.childNodes.length > 1) { key.removeChild(key.childNodes[1]); } // Add new nodes this._addTexts(key, keyData); } this._layoutData = layout; return true; }; Keyboard.prototype.onTouchStart = function onTouchStart(t) { this._eachKey(t.touches, (function(key, touchID) { this._touches[touchID] = key; if (key) { this._press(key); } }).bind(this)); t.preventDefault(); }; Keyboard.prototype.onTouchMove = function onTouchMove(t) { this._eachKey(t.touches, (function(key, touchID) { // If a touch hovers above a new key, release the old key var previousKey = this._touches[touchID]; if (previousKey && previousKey !== key) { this._release(previousKey); } if (key && key !== previousKey) { this._touches[touchID] = key; this._press(key); } }).bind(this)); t.preventDefault(); }; Keyboard.prototype.onTouchEnd = function onTouchEnd(t) { this._eachKey(t.changedTouches, (function(key, touchID) { var previousKey = this._touches[touchID]; this._release(previousKey); delete this._touches[touchID]; }).bind(this)); t.preventDefault(); }; /** * Handles pressing and releasing of a key. * * This method will update the class list of the key element, update the * modifier state and emit the "press" or "release" event. * * @param key * The key element being pressed or released. * @param pressed * Whether the key is being pressed. */ Keyboard.prototype._handleKey = function handleKey(key, pressed) { // Update the key class if (pressed) { key.classList.add("pressed"); } else { key.classList.remove("pressed"); } // Get any special action var action = key.getAttribute(module.ACTION_ATTR); // If the name is set in the geometry file, this is a special key, and // we enclose it in brackets var name = key.getAttribute(module.NAME_ATTR); if (name) { name = "<" + name + ">"; } // Default to non-dead keys var isDead = false; // If the geometry file does not contain the key name, we need to look // it up in the layout var id = key.getAttribute(module.ID_ATTR); if (!name && id) { var key = this._layoutData.layout[id]; var index = (this.shift << 0) | (this.altgr << 1); name = key[index][0]; isDead = key[index][1]; } // Update internal shift state; remember that special keys are enclosed // in brackets switch (name) { case "": this.altgr = pressed; break; case "": if (pressed) { this.shift = !this.shift; } break; case "": case "": this.shift = pressed; break; } if (name) { this.parentEl.dispatchEvent(new CustomEvent( pressed ? "press" : "release", {"detail": [name, isDead]})); } if (action && pressed) { this.parentEl.dispatchEvent(new CustomEvent( "action", {"detail": [action]})); key.classList.remove("pressed"); } }; /** * Handles pressing of a key. * * This will cause the parent element to dispatch the "press" event, and if * the key is a toggle key, the keys to update accordingly. * * @param key * The key element. */ Keyboard.prototype._press = function press(key) { // TODO: Allow configuration if (navigator.vibrate) { navigator.vibrate(20); } this._handleKey(key, true); }; /** * Handles releasing of a key. * * This will cause the parent element to dispatch the "release" event, and * if the key is a toggle key, the keys to update accordingly. * * @param key * The key element. */ Keyboard.prototype._release = function release(key) { this._handleKey(key, false); }; /** * Automatically instantiate all keyboards when the document is loaded. */ addEventListener("load", function() { var els = document.querySelectorAll(module.EL); for (var i = 0; i < els.length; i++) { var el = els[i]; var src = el.getAttribute("src"); var layout = el.getAttribute("layout"); el.keyboard = new Keyboard(el, src, layout); // Automatically attach the event handlers el.handleEvent("press"); el.handleEvent("release"); el.handleEvent("action"); } }); return module; })(); PKGG/K K #virtualtouchpad/html/js/lib/util.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.util = (function() { var module = {}; /** * Clones an array of touches keeping only identifier, screenX, screenY, * clientX and clientY. * * @param touches * A TouchList to clone. If this is falsy, [] is returned. * @return an array of objects */ module.cloneTouches = function(touches) { if (!touches) return []; var result = []; for (var i = 0; i < touches.length; i++) { result.push({ identifier: touches[i].identifier, screenX: touches[i].screenX, screenY: touches[i].screenY, clientX: touches[i].clientX, clientY: touches[i].clientY}); } return result; }; /** * XML escapes a string. */ String.prototype.xmlEscape = function() { return this.replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }; /** * Returns the position of an element, relative to the viewport. * * @return the array [x, y] */ Element.prototype.position = function() { var x = 0; var y = 0; var o = this; while (true) { x += o.offsetLeft; y += o.offsetTop; if (o.offsetParent === null){ break; } o = o.offsetParent; } return [x, y]; }; /** * Automatically attaches an event handler for a named event from an * attribute value. * * @param event * The name of the event. The attribute must be "on" + event. */ Element.prototype.handleEvent = function(event) { var value = this.getAttribute("on" + event); if (value) { this.addEventListener( event, function(event) { eval(value); }); } }; return module; })(); PKiHHq:)virtualtouchpad/html/js/lib/controller.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.controller = (function() { var module = {}; /** * The touchpad controller. */ function Touchpad(ws) { this.ws = ws; }; module.Touchpad = Touchpad; /** * Simulates pressing a button. * * @param button * The button name. */ Touchpad.prototype.buttonDown = function(button) { this.ws.send(JSON.stringify({ command: "mouse.down", data: { button: button}})); }; /** * Simulates releasing a button. * * @param button * The button name. */ Touchpad.prototype.buttonUp = function(button) { this.ws.send(JSON.stringify({ command: "mouse.up", data: { button: button}})); }; /** * Simulates scrolling. * * @param dx, dy * The horizontal and vertical scroll. */ Touchpad.prototype.scroll = function(dx, dy) { this.ws.send(JSON.stringify({ command: "mouse.scroll", data: { dx: dx, dy: dy}})); }; /** * Simulates moving the mouse. * * @param dx, dy * The horizontal and vertical movement. */ Touchpad.prototype.move = function(dx, dy) { this.ws.send(JSON.stringify({ command: "mouse.move", data: { dx: dx, dy: dy}})); }; /** * The keyboard controller. */ function Keyboard(ws) { this.ws = ws; }; module.Keyboard = Keyboard; /** * Simulates pressing a key. * * @param name * The name of the key. This should typically be the actual character * requested, or a key name enclosed in brackets (""). * @param isDead * Whether this is a dead key press. */ Keyboard.prototype.press = function(name, isDead) { this.ws.send(JSON.stringify({ command: "key.down", data: { name: name, is_dead: isDead}})); }; /** * Simulates releasing a key. * * @param name * The name of the key. This should typically be the actual character * requested, or a key name enclosed in brackets (""). * @param isDead * Whether this is a dead key press. */ Keyboard.prototype.release = function(name, isDead) { this.ws.send(JSON.stringify({ command: "key.up", data: { name: name, is_dead: isDead}})); }; return module; })(); PKGG xW%virtualtouchpad/html/js/lib/checks.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.checks = (function() { var module = { failures: []}; /** * Determines whether a check has failed. * * A check that has not been performed has not failed. * * @param check * The name of the check. * @return whether the check failed */ module.failed = function(check) { return module.failures.indexOf(check) != -1; } // Check for WebSocket if (typeof(WebSocket) == "undefined") { module.failures.push("WebSocket"); } // Check for touch events and make sure TouchList has the "identifiedTouch" // method if (!("ontouchstart" in window)) { module.failures.push("TouchEvents"); } else { if (typeof(TouchList.prototype.identifiedTouch) === "undefined") { TouchList.prototype.identifiedTouch = function(identifier) { for (var i = 0; i < this.length; i++) { if (this[i].identifier === identifier) { return this[i]; } } }; } } // Check for inline SVG support if (!document.implementation.hasFeature( "http://www.w3.org/TR/SVG11/feature#SVG", "1.1") && !document.implementation.hasFeature( "http://www.w3.org/TR/SVG11/feature#Image", "1.1")) { module.failures.push("SVG"); } // Check for fullscreen support for the document element if (typeof(document.documentElement.requestFullscreen) === "undefined") { if (document.documentElement.mozRequestFullScreen) { document.documentElement.requestFullscreen = document.documentElement.mozRequestFullScreen; document.exitFullscreen = document.mozCancelFullScreen; } else if (document.documentElement.webkitRequestFullscreen) { document.documentElement.requestFullscreen = function() { return document.documentElement.webkitRequestFullscreen( Element.ALLOW_KEYBOARD_INPUT); }; document.exitFullscreen = document.webkitExitFullscreen; } else { module.failures.push("Fullscreen"); } } // Check for Web Storage if (typeof("Storage") == "undefined") { module.failures.push("WebStorage"); } return module; })(); PKGG9E*virtualtouchpad/html/js/lib/translation.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.translation = (function() { var module = {}; /** * Gets the catalogue entry for a string. * * @param k * The catalogue entry key. * @return the catalogue entry, or nothing if no catalogue is loaded */ module.get = function(k) { if (module.catalog) { return module.catalog[k]; } }; /** * Translates a string into the current language. * * @param s * The string to translate. * @return a translated string, or s if no translation exists */ module.translate = function(s) { return module.get(s) || s; }; /** * Translates a plural string into the current language. * * @param s * The string to translate for plural index 0. * @param ... * The other plural strings of the original language followed by the * numeral. * @return a translated string, or the original if no translation exists */ module.translateN = function(s) { var n = arguments[arguments.length - 1]; var i = module.pluralizer(n); var c = module.get(s); if (c instanceof Array) { return c[i]; } else { return arguments[i]; } }; /** * Initialise the pluraliser from the string in the catalogue. */ addEventListener("load", function() { /** * The function used to turn numerals into text indice. * * @param n * The numeral. * @return an index into a plural translation, or nothing if no * catalogue is loaded */ module.pluralizer = function(n) { if (!module.catalog) { return; } var result = eval(module.catalog.plural); if (result === true) { return 1; } else if (result === false) { return 0; } else if (typeof(result) == "number") { return result; } else { throw "Invalid plural value: " + result; } }; // Find all elements with the x-tr attribute var xpathResult = document.evaluate( "//*[@x-tr]", document, null, XPathResult.ANY_TYPE, null); // Store the elements in a list var i; var els = []; while (i = xpathResult.iterateNext()) { i.normalize(); els.push(i); } // Translate the text of all elements for (i = 0; i < els.length; i++) { els[i].textContent = module.translate( els[i].textContent.trim().split(/\s+/).join(" ")); } }); return module; })(); _ = exports.translation.translate; _N = exports.translation.translateN; PKGGd3 &virtualtouchpad/html/js/lib/include.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.include = (function() { var module = { EL: "x-include"}; /** * The include element. */ function Include(parentEl, href) { // Get the external XML var ajax = new XMLHttpRequest(); ajax.open("GET", src, true); ajax.send(); ajax.onload = (function(e) { // First parse the data into a document var parser = new DOMParser(); var doc = parser.parseFromString(ajax.responseText, "text/xml"); // Insert the document into the DOM parentEl.parentNode.insertBefore(doc.documentElement, parentEl) parentEl.parentNode.removeChild(parentEl); }).bind(this); }; module.Include = Include; /** * Automatically replace all x-include with the XML from their href * attribute. */ addEventListener("load", function() { var els = document.querySelectorAll(module.EL); for (var i = 0; i < els.length; i++) (function(el) { var href = el.getAttribute("href"); var ajax = new XMLHttpRequest(); ajax.open("GET", href, true); ajax.send(); ajax.onload = function(e) { // First parse the data into a document var parser = new DOMParser(); var doc = parser.parseFromString( ajax.responseText, "text/xml"); // Insert the document into the DOM el.parentNode.insertBefore(doc.documentElement, el) el.parentNode.removeChild(el); }; })(els[i]); }); return module; })(); PKGG=s""'virtualtouchpad/html/js/lib/trackbar.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.trackbar = (function() { var module = { /** * The element type that is autmatically instantiated as a trackbar. */ EL: "trackbar", /** * The class that is added to the track element when the trackbar is * being modified by the user. */ ACTIVE_CLASS: "active", /** * The class of the track element of a trackbar. */ TRACK_CLASS: "track", /** * The class of the groove element of a trackbar. */ GROOVE_CLASS: "groove", /** * The class of the mark element of a trackbar. */ MARK_CLASS: "mark"}; /** * The trackbar. * * @param parentEl * The trackbar element. * @param value * The current value of the Trackbar. * @param minValue, maxValue * The minimum and maximum value of the Trackbar */ function Trackbar(parentEl, value, minValue, maxValue) { this.parentEl = parentEl; this._value = value < minValue ? minValue : value > maxValue ? maxValue : value; this.minValue = minValue; this.maxValue = maxValue; // Create the track elements this.trackEl = document.createElement("div"); this.trackEl.classList.add(module.TRACK_CLASS); this.parentEl.appendChild(this.trackEl); this.grooveEl = document.createElement("div"); this.grooveEl.classList.add(module.GROOVE_CLASS); this.trackEl.appendChild(this.grooveEl); this.markEl = document.createElement("div"); this.markEl.classList.add(module.MARK_CLASS); this.grooveEl.appendChild(this.markEl); // Add event listeners this.parentEl.addEventListener("touchstart", this.onTouchStart.bind(this)); this.parentEl.addEventListener("touchend", this.onTouchEnd.bind(this)); this.parentEl.addEventListener("touchcancel", this.onTouchCancel.bind(this)); this.parentEl.addEventListener("touchleave", this.onTouchEnd.bind(this)); this.parentEl.addEventListener("touchmove", this.onTouchMove.bind(this)); }; module.Trackbar = Trackbar; /** * Repositions the mark element to reflect the current value. */ Trackbar.prototype._refresh = function() { var v = (this._value - this.minValue) / ( this.maxValue - this.minValue); this.grooveEl.style.width = (v * 100).toFixed(0) + "%"; }; /** * Returns the trackbar value for a given x coordinate of the track * element. * * @param x * The x coordinate of the centre of the track element in viewport * coordinates. * @return a value which may be outside the bounds * [this.minValue..this.maxValue] */ Trackbar.prototype._xToValue = function(x) { var d = this.maxValue - this.minValue; return this.minValue + (x - this.parentEl.position()[0] - this.markEl.clientWidth / 2) * d / (this.parentEl.clientWidth - this.markEl.clientWidth); }; /** * Returns the current value of the trackbar. * * @return a value between minValue and maxValue */ Trackbar.prototype.value = function() { return this._value; }; /** * Sets the current value of the trackbar. * * @param value * The value to set. This is clamped to be between minValue and * maxValue. */ Trackbar.prototype.setValue = function(value) { this._value = value < this.minValue ? this.minValue : value > this.maxValue ? this.maxValue : value; this._refresh(); this.parentEl.dispatchEvent(new CustomEvent( "valuechanged", { value: this._value})); }; Trackbar.prototype.onTouchStart = function(event) { // Ignore multi-finger touches if (event.touches.length > 1) { return; } this.parentEl.classList.add(module.ACTIVE_CLASS); this._currentTouch = util.cloneTouches(event.touches)[0]; // Set value this.setValue(this._xToValue(this._currentTouch.pageX)); event.preventDefault(); }; Trackbar.prototype.onTouchEnd = function(event) { this.parentEl.classList.remove(module.ACTIVE_CLASS); this._currentTouch = undefined; event.preventDefault(); }; Trackbar.prototype.onTouchCancel = function(event) { // TODO: Implement }; Trackbar.prototype.onTouchLeave = function(event) { // TODO: Implement }; Trackbar.prototype.onTouchMove = function(event) { if (!this._currentTouch || !event.changedTouches) { return; } // Get the x position var touch = event.changedTouches.identifiedTouch( this._currentTouch.identifier); if (!touch) { return; } var x = touch.pageX; // Update the trackbar this.setValue(this._xToValue(x)); event.preventDefault(); }; /** * Finds all Trackbar instances by CSS selector. * * @param selector * The CSS selector to use. * @return a list of Trackbar instances */ module.find = function(selector) { var els = document.querySelectorAll(selector); var result = []; for (var i = 0; i < els.length; i++) { var el = els[i]; if (el.trackbar) { result.push(el.trackbar); } } return result; }; /** * Automatically instantiate all trackbars when the document is loaded. */ addEventListener("load", function() { var els = document.querySelectorAll(module.EL); for (var i = 0; i < els.length; i++) { var el = els[i]; // Read the current value from the element var value = eval(el.getAttribute("value")); if (isNaN(value)) { value = 0.0; } // Read the min and max values from the element var minValue = eval(el.getAttribute("min-value")); if (isNaN(minValue)) { minValue = 0.0; } var maxValue = eval(el.getAttribute("max-value")); if (isNaN(maxValue)) { maxValue = minValue + 1.0; } el.trackbar = new Trackbar(el, value, minValue, maxValue); // Register event handlers el.handleEvent("valuechanged"); // If property is passed, bind this control to a configuration // value var property = el.getAttribute("property"); if (property) { el.addEventListener("valuechanged", (function(property) { configuration.set(property, this.value()) }).bind(el.trackbar, property)); var defaultValue = eval(el.getAttribute("default-value")); if (isNaN(defaultValue)) { defaultValue = value; } el.trackbar.setValue( configuration.get(property, defaultValue)); } } }); return module; })(); PKGG)y *virtualtouchpad/html/js/lib/message-box.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.messagebox = (function() { var module = { /** * The class that must be set on a parent element for the message box to * be visible. */ MESSAGE_BOX_CLASS: "message-box" }; /** * Displays the message box. * * @param message * The message to display. This may be either a string, which will be * displayed verbatim, or an element, which will be set as the child of * the message box. * @param classes * A list of classes to use for the message box. */ module.show = function(message, classes) { var mbox = document.getElementById("message-box"); // Remove all previous content while (mbox.firstChild) { mbox.removeChild(mbox.firstChild); } // Add the message if (typeof(message) === "string") { mbox.innerHTML = message; } else { mbox.appendChild(message); } // Add the classes and show the element mbox.className = classes.join(", "); document.documentElement.classList.add(module.MESSAGE_BOX_CLASS); }; return module; })(); PKGGň 'virtualtouchpad/html/js/lib/features.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.features = (function() { var module = {}; /** * Shows a modal message box with missing features if any required features * are mising, otherwise does nothing. * * @return whether any required features were missing */ module.listMissing = function() { var list = document.createDocumentFragment(); /** * Inserts an error message. * * @param message * The error message. */ function insertError(message) { var li = document.createElement("li"); li.innerHTML = message; list.appendChild(li); } // We need WebSocket support to even send messages to the server if (checks.failed("WebSocket")) { insertError(_( "WebSockets are not supported")); } // We need touch events to simulate touchpad if (checks.failed("TouchEvents")) { insertError(_( "Touch events are not supported")); } // If any checks failed, add them to the displayed list if (list.childNodes.length > 0) { var message = document.createDocumentFragment(); message.appendChild(document.createTextNode(_( "Your browser does not support Virtual Touchpad. These " + "features are missing:"))); var ul = document.createElement("ul"); ul.appendChild(list); message.appendChild(ul); message.appendChild(document.createTextNode(_( "Please upgrade your browser to a newer version."))); messagebox.show( message, ["error"]); return true; } else { return false; } } return module; })(); PKGGhh,virtualtouchpad/html/js/lib/configuration.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.configuration = (function() { var module = { _storage: {}, /** * Reads a configuration value. * * This function supports booleans, numbers and strings. * * @param name * The name of the configuration value. * @param defaultValue * The value to use if the value is not stored. * * This value determines the return type of this function. If this * is not passed, a string is returned, otherwise a value with the * same type is returned. * * If the configuration value cannot be interpreted as this type, * or does not exist, or its type is unsupported, this value is * returned. * @return the parsed configuration value */ get: function(name, defaultValue) { var value = read(name); switch (typeof(defaultValue)) { case "boolean": if (value === "true") { return true; } else if (value === "false") { return false; } break; case "number": if (!isNaN(parseFloat(value))) { return parseFloat(value); } break; case "string": if (value) { return value.toString(); } break; } return defaultValue; }, /** * Sets a configuration value. * * @param name * The name of the configuration value. * @param value * The value to set. */ set: function(name, value) { write(name, value); }}; var read = checks.failed("WebStorage") ? function(name) { return module._storage[name]; } : function(name) { return localStorage[name]; }; var write = checks.failed("WebStorage") ? function(name, value, transient) { module._storage[name] = value.toString(); } : function(name, value) { localStorage[name] = value; }; return module; })(); PKiHHm(Z3%3%#virtualtouchpad/html/js/lib/view.js/*****************************************************************************/ /* virtual-touchpad */ /* Copyright (C) 2013-2015 Moses Palmér */ /* */ /* This program is free software: you can redistribute it and/or modify it */ /* under the terms of the GNU General Public License as published by the */ /* Free Software Foundation, either version 3 of the License, or (at your */ /* option) any later version. */ /* */ /* This program is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General */ /* Public License for more details. */ /* */ /* You should have received a copy of the GNU General Public License along */ /* with this program. If not, see . */ /*****************************************************************************/ exports.view = (function() { var module = { EL: "touchpad", /** * The maximum accumulated movement where touch-and-release is * considered as a click. */ CLICK_THRESHOLD: 10}; /** * The touchpad view. * * @param touchpad * The controller to use. */ function Touchview(parentEl) { this.parentEl = parentEl; var self = this; this.parentEl.addEventListener("touchstart", function() { return self.onTouchStart.apply(self, arguments); }); this.parentEl.addEventListener("touchend", function() { return self.onTouchEnd.apply(self, arguments); }); this.parentEl.addEventListener("touchcancel", function() { return self.onTouchCancel.apply(self, arguments); }); this.parentEl.addEventListener("touchleave", function() { return self.onTouchEnd.apply(self, arguments); }); this.parentEl.addEventListener("touchmove", function() { return self.onTouchMove.apply(self, arguments); }); }; module.Touchview = Touchview; /** * Calculates the movement vector given the old and new touch events. * * @param oldTouch, newTouch * The previous and current touch events. * @return the movement vector [dx, dy] */ Touchview.prototype._calculateMovement = function(oldTouch, newTouch) { var dx = newTouch.screenX - oldTouch.screenX; var dy = newTouch.screenY - oldTouch.screenY; var d = Math.sqrt(dx * dx + dy * dy); var a = Math.atan2(dy, dx); var acceleration = configuration.get("view.acceleration", 0.3); var sensitivity = configuration.get("view.sensitivity", 1.5); var h = sensitivity * Math.pow(d, 1.0 + acceleration); return [Math.cos(a) * h, Math.sin(a) * h]; } /** * Calculates the scroll vector given the old and new touch events. * * @param oldTouch, newTouch * The previous and current touch events. * @return the scroll vector [dx, dy] */ Touchview.prototype._calculateScroll = function(oldTouch, newTouch) { var dx = newTouch.screenX - oldTouch.screenX; var dy = newTouch.screenY - oldTouch.screenY; var xdirection = configuration.get("view.naturalScroll", true) ? -1 : 1; var ydirection = configuration.get("view.naturalScroll", true) ? -1 : 1; return [dx * xdirection, dy * ydirection]; } /** * Clicks a button. * * The click will be sent with a short delay. Before the click is actually * sent, clickInProgress will return true, and clickCancel may be called to * cancel sending the click. * * If a click is in progress, no action is performed. * * @param button * The button to click. */ Touchview.prototype.click = function(button) { // Do nothing if a click is in progress if (this.clickInProgress()) { return; } var currentTouches = util.cloneTouches(this.currentTouches); // Make a short delay before sending the click event this._click = setTimeout( (function() { var button = currentTouches.length == 2 ? "right" : "left"; this.parentEl.dispatchEvent(new CustomEvent( "buttondown", {"detail": [button]})); this.parentEl.dispatchEvent(new CustomEvent( "buttonup", {"detail": [button]})); delete this._click; }).bind(this), 300); }; /** * Determines whether a click is queued to be sent. * * @return whether a click will be sent in the immediate future */ Touchview.prototype.clickInProgress = function() { return !!this._click; } /** * Cancels the current click. * * @return whether the click was cancelled; this function will return false * if no click is queued */ Touchview.prototype.clickCancel = function() { if (this._click) { clearTimeout(this._click); this._click = undefined; return true; } else { return false; } } /** * Starts a DnD operation. * * This will send a button down event. */ Touchview.prototype.dndStart = function() { this._dnd = true; this.parentEl.dispatchEvent(new CustomEvent( "buttondown", {"detail": ["left"]})); } /** * Determines whether a DnD operation is in progress. * * @return whether a DnD operation is in progress */ Touchview.prototype.dndInProgress = function() { return !!this._dnd; } /** * Ends a DnD operation. * * This will send a button up event. */ Touchview.prototype.dndEnd = function() { this._dnd = false; this.parentEl.dispatchEvent(new CustomEvent( "buttonup", {"detail": ["left"]})); } Touchview.prototype.onTouchStart = function(event) { this.currentTouches = util.cloneTouches(event.touches); this._accumulatedMovement = 0; event.preventDefault(); }; Touchview.prototype.onTouchEnd = function(event) { // Click if not enough movement has been made if (this._accumulatedMovement < module.CLICK_THRESHOLD) { if (this.dndInProgress()) { this.dndEnd(); } else { this.click(this.currentTouches.length == 2 ? "right" : "left"); } } delete this.currentTouches; }; Touchview.prototype.onTouchCancel = function(event) { // TODO: Implement }; Touchview.prototype.onTouchLeave = function(event) { // TODO: Implement }; Touchview.prototype.onTouchMove = function(event) { // If this is the second click in a short period, treat this as DnD if (this.clickInProgress()) { this.clickCancel(); this.dndStart(); } if (!event.changedTouches) { return; } // Require all touches to be present if (event.changedTouches.length != this.currentTouches.length) { return; } // Locate touches that are interesting var oldTouch = this.currentTouches[0]; var newTouch = event.changedTouches.identifiedTouch( oldTouch.identifier); this._accumulatedMovement += Math.sqrt(0 + Math.pow(newTouch.screenX - oldTouch.screenX, 2) + Math.pow(newTouch.screenY - oldTouch.screenY, 2)) // Replace the current touches this.currentTouches = util.cloneTouches(event.changedTouches); if (event.changedTouches.length == 1) { var movement = this._calculateMovement(oldTouch, newTouch); this.parentEl.dispatchEvent(new CustomEvent( "move", {"detail": movement})); } else if (event.changedTouches.length == 2) { var scroll = this._calculateScroll(oldTouch, newTouch); this.parentEl.dispatchEvent(new CustomEvent( "scroll", {"detail": scroll})); } event.preventDefault(); }; /** * Automatically instantiate all touch views when the document is loaded. */ addEventListener("load", function() { var els = document.querySelectorAll(module.EL); for (var i = 0; i < els.length; i++) { var el = els[i]; el.touchview = new Touchview(el); // Automatically attach the event handlers el.handleEvent("buttondown"); el.handleEvent("buttonup"); el.handleEvent("move"); el.handleEvent("scroll"); } }); return module; })(); PKR9HH H !virtualtouchpad/event/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from virtualtouchpad.platform import implement def key_down(state, name, keysym, symbol): """Sends a key down event. :param state: The current keyboard state. This is an opaque value used by the backend. :param str name: The name of the key. This should typically be the actual character requested, or ``None``. :param int keysym: The key symbol identifier of the key being pressed. :param str symbol: The symbol name of the key. This value will be handled just like :func:`Xlib.XK.string_to_keysym` would. Thus, ``'A'`` and ``'a'`` will trigger a press of the ``'A'`` key. ``'Space'`` and ``'space'`` will trigger space. :return: the new state """ raise NotImplementedError() def key_up(state, name, keysym, symbol): """Sends a key up event. :param state: The current keyboard state. This is an opaque value used by the backend. :param str name: The name of the key. This should typically be the actual character requested, or ``None``. :param int keysym: The key symbol identifier of the key being pressed. :param str symbol: The symbol name of the key. This value will be handled just like :func:`Xlib.XK.string_to_keysym` would. Thus, ``'A'`` and ``'a'`` will trigger a press of the ``'A'`` key. ``'Space'`` and ``'space'`` will trigger space. :return: the new state """ raise NotImplementedError() def mouse_down(state, button): """Presses a mouse button. :param state: The current mouse state. This is an opaque value used by the backend. :param int button: The button index. :return: the new state """ raise NotImplementedError() def mouse_up(state, button): """Releases a mouse button. :param state: The current mouse state. This is an opaque value used by the backend. :param int button: The button index. :return: the new state """ raise NotImplementedError() def mouse_scroll(state, dx, dy): """Scrolls the mouse wheel. :param state: The current mouse state. This is an opaque value used by the backend. :param int dx: The horisontal offset to scroll. :param int dy: The vertical offset to scroll. :return: the new state """ raise NotImplementedError() def mouse_move(state, dx, dy): """Moves the mouse pointer. :param state: The current mouse state. This is an opaque value used by the backend. :param int dx: The horisontal offset to move. :param int dy: The vertical offset to move. :return: the new state """ raise NotImplementedError() implement(globals()) PKR9HrZZvirtualtouchpad/event/xorg.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from virtualtouchpad import platform with platform.modules(): import Xlib.keysymdef.xkb from Xlib import display from Xlib import X from Xlib import XK from Xlib.ext.xtest import fake_input from virtualtouchpad.platform.xorg import display_manager try: import pyatspi except ImportError: pyatspi = None #: The global X display DISPLAY = display.Display() # The scroll threshold required to actually perform scrolling SCROLL_THRESHOLD = 10 # The thresholds for movements SHRT_MAX = 32767 SHRT_MIN = -SHRT_MAX - 1 def string_to_keysym(key, default=None): """Converts a string to a keysym identifier. :param str keysym: The string to convert. :return: the corresponding key identifier :rtype: int :raises ValueError: if the string is unknown """ keysym = XK.string_to_keysym(key) if not keysym: keysym = getattr(Xlib.keysymdef.xkb, 'XK_' + key, default) if not keysym: raise ValueError('invalid symbol: %s', key) return keysym def key_down(state, name, keysym, symbol): # Convert the symbol name to an identifier keysym = string_to_keysym(symbol, keysym) with display_manager(DISPLAY) as display: # Press the key keycode = display.keysym_to_keycode(keysym) fake_input(display, X.KeyPress, keycode) def key_up(state, name, keysym, symbol): # Convert the symbol name to an identifier keysym = string_to_keysym(symbol, keysym) with display_manager(DISPLAY) as display: # Release the key keycode = display.keysym_to_keycode(keysym) fake_input(display, X.KeyRelease, keycode) def mouse_down(state, button): with display_manager(DISPLAY) as display: fake_input(display, X.ButtonPress, button) return [0, 0] def mouse_up(state, button): with display_manager(DISPLAY) as display: fake_input(display, X.ButtonRelease, button) return [0, 0] def mouse_scroll(state, dx, dy): state[0] += dx state[1] += dy with display_manager(DISPLAY) as display: # Vertical scroll vscroll = state[1] // SCROLL_THRESHOLD if vscroll > 0: vbutton = X.Button5 elif vscroll < 0: vbutton = X.Button4 for i in range(abs(vscroll)): fake_input(display, X.ButtonPress, vbutton) fake_input(display, X.ButtonRelease, vbutton) state[1] -= vscroll * SCROLL_THRESHOLD # Horizontal scroll hscroll = state[0] // SCROLL_THRESHOLD if hscroll > 0: hbutton = 7 elif hscroll < 0: hbutton = 6 for i in range(abs(hscroll)): fake_input(display, X.ButtonPress, hbutton) fake_input(display, X.ButtonRelease, hbutton) state[0] -= hscroll * SCROLL_THRESHOLD return state def mouse_move(state, dx, dy): with display_manager(DISPLAY) as display: # The movement is a short dx = max(min(dx, SHRT_MAX), SHRT_MIN) dy = max(min(dy, SHRT_MAX), SHRT_MIN) if pyatspi: pyatspi.Registry.generateMouseEvent(dx, dy, 'rel') else: display.warp_pointer(dx, dy) return [0, 0] PKR9H# $virtualtouchpad/event/_win32_syms.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . #: A mapping from X symbol name to win32 Virtual Key Codes SYMS = { 'BackSpace': 0x08, 'Return': 0x0D, 'space': 0x20, 'Tab': 0x09, 'Menu': 0x5D, 'Super_L': 0x5B, 'Super_R': 0x5C, 'Alt_L': 0xA4, 'Caps_Lock': 0x14, 'Control_L': 0xA2, 'Control_R': 0xA3, 'ISO_Level3_Shift': 0xA5, 'Shift_L': 0xA0, 'Shift_R': 0xA1, 'Escape': 0x1B, 'F1': 0x70, 'F2': 0x71, 'F3': 0x72, 'F4': 0x73, 'F5': 0x74, 'F6': 0x75, 'F7': 0x76, 'F8': 0x77, 'F9': 0x78, 'F10': 0x79, 'F11': 0x7A, 'F12': 0x7B, 'Down': 0x28, 'Left': 0x25, 'Right': 0x27, 'Up': 0x26, 'End': 0x23, 'Home': 0x24, 'Next': 0x22, 'Prior': 0x21 } PKR9H]virtualtouchpad/event/win32.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from virtualtouchpad import platform with platform.modules(): import ctypes import logging import unicodedata import win32api del win32api from ._win32_syms import SYMS log = logging.getLogger(__name__) _SendInput = ctypes.windll.user32.SendInput class MOUSEINPUT(ctypes.Structure): MOVE = 0x0001 LEFTDOWN = 0x0002 LEFTUP = 0x0004 RIGHTDOWN = 0x0008 RIGHTUP = 0x0010 MIDDLEDOWN = 0x0020 MIDDLEUP = 0x0040 WHEEL = 0x0800 BUTTON_MAPPING = { 1: (LEFTDOWN, LEFTUP), 2: (MIDDLEDOWN, MIDDLEUP), 3: (RIGHTDOWN, RIGHTUP)} _fields_ = [ ('dx', ctypes.c_int32), ('dy', ctypes.c_int32), ('mouseData', ctypes.c_uint32), ('dwFlags', ctypes.c_uint32), ('time', ctypes.c_uint32), ('dwExtraInfo', ctypes.c_void_p)] class KEYBDINPUT(ctypes.Structure): EXTENDEDKEY = 0x0001 KEYUP = 0x0002 SCANCODE = 0x0008 UNICODE = 0x0004 _fields_ = [ ('wVk', ctypes.c_uint16), ('wScan', ctypes.c_uint16), ('dwFlags', ctypes.c_uint32), ('time', ctypes.c_uint32), ('dwExtraInfo', ctypes.c_void_p)] class ANYINPUT(ctypes.Union): _fields_ = [ ('mouse', MOUSEINPUT), ('keyboard', KEYBDINPUT)] class INPUT(ctypes.Structure): MOUSE = 0 KEYBOARD = 1 _fields_ = [ ('type', ctypes.c_uint32), ('value', ANYINPUT)] def is_dead(symbol): """Returns whether a key symbol is a dead key. :param symbol: The key symbol. :type symbol: str or None :return: whether ``symbol`` is a dead key """ return symbol and symbol.startswith('dead_') def to_dead_key(name): """Converts a name to a dead key. :param str name: The name of the key. This should be the actual character representing the dead key, such as ``~`` for *TILDE*. :raises KeyError: if the dead cannot be converted to a combining character :return: a dead key state :rtype: tuple(name, character) or None """ if name is None: return None else: return ( name, unicodedata.lookup('COMBINING ' + unicodedata.name(name))) def key_event(name, symbol, flags): """Sends a keyboard event. If the length of ``name`` is ``1``, it is assumed to be a *unicode* character and the corresponding character will be sent. Otherwise, symbol is mapped through :attr:`._win32_syms.SYMS` to a virtual key code. """ if name and len(name) == 1: keyboard = KEYBDINPUT( dwFlags=KEYBDINPUT.UNICODE | flags, wScan=ord(name)) else: try: vk = SYMS[symbol] except KeyError: log.error('Unknown symbol: %s', symbol) return keyboard = KEYBDINPUT( dwFlags=flags, wVk=vk) _SendInput( 1, ctypes.byref(INPUT( type=INPUT.KEYBOARD, value=ANYINPUT( keyboard=keyboard))), ctypes.sizeof(INPUT)) def key_down(state, name, keysym, symbol): # Do we have a previous dead key? In that case, first try to combine it # with the current key, and send it, and if that fails just send the dead # key alone if state is not None: previous_name, combining = state if name and len(name) == 1: combined = unicodedata.normalize('NFC', name + combining) if len(combined) == 1: key_event(combined, None, 0) return None key_event(previous_name, None, 0) # If the current key is a dead key, save it for later if is_dead(symbol): try: return to_dead_key(name) except Exception as e: log.error('Failed to set dead key: %s', str(e)) key_event(name, symbol, 0) return None def key_up(state, name, keysym, symbol): key_event(name, symbol, KEYBDINPUT.KEYUP) return state def mouse_down(state, button): _SendInput( 1, ctypes.byref(INPUT( type=INPUT.MOUSE, value=ANYINPUT( mouse=MOUSEINPUT( dwFlags=MOUSEINPUT.BUTTON_MAPPING[button][0])))), ctypes.sizeof(INPUT)) def mouse_up(state, button): _SendInput( 1, ctypes.byref(INPUT( type=INPUT.MOUSE, value=ANYINPUT( mouse=MOUSEINPUT( dwFlags=MOUSEINPUT.BUTTON_MAPPING[button][1])))), ctypes.sizeof(INPUT)) def mouse_scroll(state, dx, dy): # TODO: Support horisontal scroll _SendInput( 1, ctypes.byref(INPUT( type=INPUT.MOUSE, value=ANYINPUT( mouse=MOUSEINPUT( dwFlags=MOUSEINPUT.WHEEL, mouseData=dy)))), ctypes.sizeof(INPUT)) def mouse_move(state, dx, dy): _SendInput( 1, ctypes.byref(INPUT( type=INPUT.MOUSE, value=ANYINPUT( mouse=MOUSEINPUT( dx=int(dx), dy=int(dy), dwFlags=MOUSEINPUT.MOVE)))), ctypes.sizeof(INPUT)) PKڋHHac**"virtualtouchpad/server/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import bottle app = bottle.Bottle() def main(port, address, log_level): import gevent.pywsgi import sys try: from geventwebsocket.handler import WebSocketHandler except ImportError: from geventwebsocket import WebSocketHandler # Importing this module will attach routes to app from . import routes sys.stdout.write('Starting server http://%s:%d/...\n' % ( address, port)) from gevent import monkey monkey.patch_all(thread=False) return gevent.pywsgi.WSGIServer( ('0.0.0.0', port), app, handler_class=WebSocketHandler) PKGGKŝ)virtualtouchpad/server/routes/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from .. import app # Importing these modules will attach routes to app from . import controller from . import keyboard from . import translations # Import static last since it is the catch-all route from . import static PKGG 'virtualtouchpad/server/routes/static.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import bottle import os from . import app from ..util import static_file #: The name of the file in every directory used as index INDEX_MIN_FILE = 'index.min.xhtml' #: The name of the file in every directory used as index unless the minified #: version exists INDEX_FILE = 'index.xhtml' @app.get('/') @app.get('/') def static(filepath='.'): if static_file.isdir(filepath): for index_file in ( os.path.join(filepath, INDEX_MIN_FILE), os.path.join(filepath, INDEX_FILE)): if static_file.exists(index_file): return static_file.get(index_file) return bottle.HTTPResponse(status=404) else: return static_file.get(filepath) PKGGuc)virtualtouchpad/server/routes/keyboard.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import bottle import json import logging from . import app from ..util import static_file log = logging.getLogger(__name__) #: The root path for layouts ROOT = 'keyboard/layout' @app.get('/keyboard/layout/default') def default_layout(): """Returns the default keyboard layout. """ layout_files = static_file.list(ROOT) if not layout_files: return bottle.HTTPResponse(status=404) # TODO: Select the one used by the current system return static_file.get('%s/%s' % (ROOT, layout_files[0])) @app.get('/keyboard/layout/') def list_layouts(): """Returns a list of all keyboard layouts. """ layout_files = static_file.list(ROOT) if not layout_files: return bottle.HTTPResponse(status=404) layouts = [] for layout_file in layout_files: path = '%s/%s' % (ROOT, layout_file) try: with static_file.open_stream(path) as f: layout = json.load(f) layouts.append({ 'url': '/%s' % path, 'name': layout['meta']['name']}) except IOError: log.exception('Failed to open %s', path) except ValueError: log.exception('Failed to load JSON value from %s', path) except: log.exception('wha?') return { 'layouts': layouts} PKGG-virtualtouchpad/server/routes/translations.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import bottle import os from . import app from ..util import static_file @app.get('/translations/') def translations(domain): accept_language = bottle.request.headers.get('Accept-Language') \ or 'default' languages = sorted(( ( language.split(';')[0].strip(), float(language.split(';q=')[1]) if ';q=' in language else 1.0) for language in accept_language.split(',')), key=lambda p: p[1], reverse=True) + [('default', 0.0)] for language, q in languages: path = os.path.join('translations', domain, language + '.js') if static_file.exists(path): return static_file.get(path) return bottle.HTTPResponse(status=404) PKGG6 6 +virtualtouchpad/server/routes/controller.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import bottle import geventwebsocket import json import logging import sys import traceback from . import app from ..dispatchers import Dispatcher, keyboard, mouse log = logging.getLogger(__name__) @app.route('/controller') def controller(): # Get the actual websocket ws = bottle.request.environ.get('wsgi.websocket') log.info( 'WebSocket with %s opened', bottle.request.environ.get('REMOTE_ADDR')) if not ws: bottle.abort(400, 'Expected WebSocket request.') def report_error(reason, exception, tb): ws.send(json.dumps(dict( reason=reason, exception=type(exception).__name__, data=str(exception), tb=traceback.extract_tb(tb)))) dispatch = Dispatcher( key=keyboard.Handler(), mouse=mouse.Handler()) while True: try: message = ws.receive() if message is None: break try: command = json.loads(message) except Exception as e: log.exception( 'An error occurred when loading JSON from %s', message) ex_type, ex, tb = sys.exc_info() report_error( 'invalid_data', e, tb) continue try: dispatch(**command) except TypeError as e: log.exception( 'Failed to dispatch command %s', command) ex_type, ex, tb = sys.exc_info() report_error( 'invalid_command', e, tb) continue except Exception as e: log.exception( 'An error occurred while dispatching %s', command) ex_type, ex, tb = sys.exc_info() report_error( 'internal_error', e, tb) continue except geventwebsocket.WebSocketError: log.exception('Failed to read WebSocket data') break PKiHH2D D +virtualtouchpad/server/dispatchers/mouse.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from pynput.mouse import Button, Controller class Handler(object): """A handler for mouse events. """ #: The scroll threshold required to actually perform scrolling SCROLL_THRESHOLD = 10 def __init__(self): self.d = Controller() self.ax = 0 self.ay = 0 def down(self, button='left'): """Triggers a a mouse press event. :param str button: The button name. This must be one of the values defined for :class:`pynput.mouse.Button`. """ self.d.press(Button[button]) def up(self, button='left'): """Triggers a a mouse release event. :param str button: The button name. This must be one of the values defined for :class:`pynput.mouse.Button`. """ self.d.release(Button[button]) def scroll(self, dx=0, dy=0): """Triggers a mouse scroll event. :param int dx: The horisontal offset to scroll. :param int dy: The vertical offset to scroll. """ self.ax += dx self.ay += dy # Vertical scroll xscroll = int(self.ax // self.SCROLL_THRESHOLD) self.ax -= xscroll * self.SCROLL_THRESHOLD # Horizontal scroll yscroll = int(self.ay // self.SCROLL_THRESHOLD) self.ay -= yscroll * self.SCROLL_THRESHOLD if xscroll or yscroll: self.d.scroll( xscroll, yscroll) def move(self, dx=0, dy=0): """Triggers a mouse move event. :param int dx: The horisontal offset to move. :param int dy: The vertical offset to move. """ self.d.move(dx, dy) PKiHH^G<<.virtualtouchpad/server/dispatchers/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import logging log = logging.getLogger(__name__) class Dispatcher(object): """A class used to dispatch events to event handlers. """ def __init__(self, **handlers): """Creates a dispatcher for a collection of handlers. :param handlers: The handlers to register. These must be callable, and they will be registered as the key names. """ self._handlers = handlers def __call__(self, command, data): """Dispatches a command. :param str command: The command to dispatch. :param dict data: The arguments. :raises KeyError: if ``command`` is an unknown handler """ try: name, method = command.split('.', 1) handler = getattr(self._handlers[name], method) except ValueError: handler = self._handlers[command] try: return handler(**data) except Exception as e: log.exception( 'Failed to handle %s(%s): %s' % ( command, ', '.join('%s=%s' % i for i in data.items()), str(e))) PKiHHve0 0 .virtualtouchpad/server/dispatchers/keyboard.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from pynput.keyboard import KeyCode, Key, Controller class Handler(object): def __init__(self): self.d = Controller() def keycode(self, name, is_dead): """Resolves a key description to a value that can be passed to :meth:`pynput.keyboard.Controller.press` and :meth:`~pynput.keyboard.Controller.release`. :param str name: The name of the key. This should typically be the actual character requested. If it starts with ``'<'`` and ends with ``'>'``, the key value is looked up in :class:`pynput.keyboard.Key`, otherwise it is passed straight to :meth:`pynput.keyboard.Controller.press`. :return: a key value """ if is_dead: return KeyCode.from_dead(name) elif name[0] == '<' and name[-1] == '>': return Key[name[1:-1]] else: return name def down(self, name, is_dead=False): """Triggers a key down event. :param str name: The name of the key. This is passed to :meth:`keycode`. :param bool is_dead: Whether a dead key press is requested. In that case, ``name`` must be a unicode character with a corresponding combining codepoint, and the key will be combined with the next character typed. """ self.d.press(self.keycode(name, is_dead)) def up(self, name, is_dead=False): """Triggers a key up event. :param str name: The name of the key. This is passed to :meth:`keycode`. :param bool is_dead: Whether a dead key press is requested. In that case, ``name`` must be a unicode character with a corresponding combining codepoint, and the key will be combined with the next character typed. """ self.d.release(self.keycode(name, is_dead)) PKGG@'virtualtouchpad/server/util/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . PKGGTDII*virtualtouchpad/server/util/static_file.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import bottle import logging import mimetypes import pkg_resources import os import sys import time from virtualtouchpad import __name__ as PKG_RESOURCES_PACKAGE log = logging.getLogger(__name__) # Set a default value for STATIC_ROOT only if it is accessible DEFAULT_STATIC_ROOT = os.path.join( os.path.dirname(__file__), os.path.pardir, 'html') STATIC_ROOT = os.getenv( 'VIRTUAL_TOUCHPAD_STATIC_ROOT', DEFAULT_STATIC_ROOT if os.access(DEFAULT_STATIC_ROOT, os.R_OK) else None) def exists(path): """Returns whether a static file exists. :param str path: The path of the static file. """ if STATIC_ROOT is not None: # If VIRTUAL_TOUCHPAD_STATIC_ROOT is set, simply check whether we can # read the file return os.access(os.path.join(STATIC_ROOT, path), os.R_OK) # Otherwise, check with pkg_resource return pkg_resources.resource_exists( PKG_RESOURCES_PACKAGE, os.path.join('html', path)) def isdir(path): """Returns whether a static file exists and is a directory. :param str path: The path of the static file. """ if STATIC_ROOT is not None: # If VIRTUAL_TOUCHPAD_STATIC_ROOT is set, simply check whether we can # read the file return os.path.isdir(os.path.join(STATIC_ROOT, path)) # Otherwise, check with pkg_resource return pkg_resources.resource_isdir( PKG_RESOURCES_PACKAGE, os.path.join('html', path)) def list(path): """Lists all resources available under ``path``. :param str path: The path to check. :return: a list of resources :rtype: [str] """ if STATIC_ROOT is not None: return os.listdir(os.path.join(STATIC_ROOT, path)) return pkg_resources.resource_listdir( PKG_RESOURCES_PACKAGE, os.path.join('html', path)) def open_stream(path): """Opens a file. :param str path: The path of the static file. :return: a file-like object """ if STATIC_ROOT is not None: return open(os.path.join(STATIC_ROOT, path)) else: return pkg_resources.resource_stream( PKG_RESOURCES_PACKAGE, os.path.join('html', path)) def get(path): """Returns a :class:`bottle.HTTPResponse` or :class:`bottle.HTTPError` containing either the file requested or an error message. :param str path: The path of the static file. """ if STATIC_ROOT is not None: # If VIRTUAL_TOUCHPAD_STATIC_ROOT is set, simply use bottle return bottle.static_file(path, root=STATIC_ROOT) # Otherwise, try to serve a resource from the egg try: path = pkg_resources.resource_filename( PKG_RESOURCES_PACKAGE, os.path.join('html', path)) return bottle.static_file( os.path.basename(path), root=os.path.dirname(path)) except KeyError: # The file does not exist; we try to serve a file that we are # certain does not exist to trigger a 404 return bottle.static_file( path, root=os.path.join(os.path.dirname(__file__), 'html')) except NotImplementedError: # pkg_resources does not support resource_filename when running from # a zip file if hasattr(sys, 'frozen'): pass else: raise # Open the file and get its size try: stream = pkg_resources.resource_stream( PKG_RESOURCES_PACKAGE, os.path.join('html', path)) stream.seek(0, os.SEEK_END) size = stream.tell() stream.seek(0, os.SEEK_SET) if bottle.request.method == 'HEAD': body = '' else: body = stream.read() except IOError: log.exception('File %s does not exist', path) return bottle.HTTPError(404, 'File does not exist.') headers = dict() headers['Content-Length'] = size # Guess the content type and encoding mimetype, encoding = mimetypes.guess_type(path) if mimetype: headers['Content-Type'] = mimetype if encoding: headers['Content-Encoding'] = encoding # Check the file mtime; we use the egg file or the current binary try: st = os.stat(os.path.join(__file__, os.path.pardir, os.path.pardir)) except OSError: st = os.stat(os.path.abspath(sys.argv[0])) last_modified = time.strftime( '%a, %d %b %Y %H:%M:%S GMT', time.gmtime(st.st_mtime)) headers['Last-Modified'] = last_modified if bottle.request.environ.get('HTTP_IF_MODIFIED_SINCE'): if_modified_since = bottle.parse_date(bottle.request.environ.get( 'HTTP_IF_MODIFIED_SINCE').split(";")[0].strip()) if if_modified_since is not None \ and if_modified_since >= int(st.st_mtime): headers['Date'] = time.strftime( '%a, %d %b %Y %H:%M:%S GMT', time.gmtime()) return bottle.HTTPResponse(status=304, **headers) return bottle.HTTPResponse(body, **headers) PKGG MM$virtualtouchpad/platform/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import logging import os import pkg_resources import re from contextlib import contextmanager log = logging.getLogger(__name__) class ImplementationImportError(Exception): """Raised when a module required for a platform specific implementation does not exist. """ pass @contextmanager def modules(): """A context manager for trying to import platform dependent modules. Use this in a ``with`` statement when importing platform dependent modules to allow :func:`implement` to gracefully try another implementation. """ try: yield except ImportError as e: raise ImplementationImportError(e) _package, _subpackage = __package__.split('.', 1) _path = _subpackage.replace('.', os.path.sep) __all__ = [ directory for directory in pkg_resources.resource_listdir(_package, _path) if pkg_resources.resource_isdir(_package, os.path.join(_path, directory)) and directory[0] != '_'] #: The regular expression used to match importable modules _MODULE_RE = re.compile(r'[a-zA-Z][a-zA-Z0-9_]*\.pyc?$') def _package_importables(package_name): """Yields all importable modules and packages in a package. Names beginning with ``'_'`` are ignored. :param str package_name: The name of the package. """ package, subpackage = package_name.split('.', 1) path = subpackage.replace('.', os.path.sep) for name in pkg_resources.resource_listdir(package, path): if name[0] == '_': continue full = os.path.join(path, name) if not pkg_resources.resource_isdir(package, full): if _MODULE_RE.match(name): yield '.'.join((package_name, name.rsplit('.', 1)[0])) else: if any(pkg_resources.resource_exists( package, os.path.join(full, f)) for f in ( '__init__.py', '__init__.pyc')): yield '.'.join((package_name, name)) def implement(globals_dict): """Loads the platform dependent implementation and populates a dict. :param dict globals_dict: The globals dict. Use :func:`globals` when calling this function from another module. Only callable symbols not beginning with '_' in this dictionary will be imported from the driver module. :raises ImportError: if no platform driver was found, or a global callable in the driver was not present in ``globals_dict``, or the function signature of a global callable in the driver did not match in ``globals_dict`` """ import importlib import inspect # Get the name of the platform and load the driver module package_name = globals_dict['__package__'] driver = None for candidate in _package_importables(globals_dict['__package__']): try: driver = importlib.import_module( '.%s' % candidate.rsplit('.', 1)[-1], package_name) except ImplementationImportError as e: log.info('Not loading %s.%s: %s', package_name, candidate, str(e)) if driver is None: raise ImportError( 'Failed to locate platform driver for package %s', globals_dict['__package__']) # Get symbols exported from the driver for name in dir(driver): value = getattr(driver, name) # We ignore all private symbols if name[0] == '_': continue # The symbol must exist as a global callable in this module old_value = globals_dict.get(name, None) if old_value is None: continue if not callable(old_value): raise ImportError( 'error in %s: symbol <%s> must not be global', driver.__name__, name) # If the symbol is callable, we need a reference from which to retrieve # an argument specification if inspect.isclass(value): callable_value = value.__init__ old_callable_value = old_value.__init__ elif inspect.isfunction(value): callable_value = value old_callable_value = old_value else: callable_value = None old_callable_value = None # If the symbol is callable, the function signatures must match if callable(callable_value): argspec = inspect.getargspec(callable_value) old_argspec = inspect.getargspec(old_callable_value) if argspec != old_argspec: raise ImportError( 'error in %s: invalid method signature for <%s>', driver.__name__, name) # Try to expand the documentation try: if value.__doc__: value.__doc__ += '\n\n' + old_value.__doc__ else: value.__doc__ = old_value.__doc__ except AttributeError: pass # Replace the global globals_dict[name] = value PKGGzr virtualtouchpad/platform/xorg.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import logging log = logging.getLogger(__name__) def display_manager(display): """Traps *X* errors and raises a ``RuntimeError`` at the end if any error occurred. :param display: The *X* display. :type display: Xlib.display.Display :return: the display :rtype: Xlib.display.Display """ from contextlib import contextmanager @contextmanager def manager(): errors = [] def handler(*args): errors.append(args) old_handler = display.set_error_handler(handler) yield display display.sync() display.set_error_handler(old_handler) if errors: log.error('X requests failed: %s', ', '.join( str(e) for e, v in errors)) raise RuntimeError(errors) return manager() PKZ\:H. #: The resource ID of the main icon IDI_MAINICON = 1001 #: The pkg_resources name of the main icon stream PATH_MAINICON = ('virtualtouchpad', 'html/favicon.ico') PKGGl#virtualtouchpad/systray/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from virtualtouchpad.platform import implement class SystemTrayIcon(object): """An object representing a system tray icon. """ def __init__(self, description, on_click): """Creates a new system tray icon. :param str description: The short description of this system tray icon. :param callable on_click: A callback for when the systray icon is clicked. """ self._description = description self._on_click = on_click @property def description(self): """The short description of this system tray icon""" return self._description def on_click(self): if self._on_click: self._on_click() implement(globals()) PKGG&>  (virtualtouchpad/systray/xorg/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from virtualtouchpad import platform with platform.modules(): from .xsystray import * PKGG ""(virtualtouchpad/systray/xorg/xsystray.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import os import pkg_resources import threading import PIL.Image from Xlib import X from Xlib import display from virtualtouchpad.platform.xorg import display_manager from virtualtouchpad import __name__ as PKG_RESOURCES_PACKAGE from .. import SystemTrayIcon class SystemTrayIcon(SystemTrayIcon): _XEMBED_INFO = '_XEMBED_INFO' XEMBED_VERSION = 0 XEMBED_MAPPED = 1 _NET_SYSTEM_TRAY_OPCODE = '_NET_SYSTEM_TRAY_OPCODE' SYSTEM_TRAY_REQUEST_DOCK = 0 def _send_message(self, window, message, d1=0, d2=0, d3=0): """Sends a generic systray message. This method does not trap ``X`` errors; that is up to the caller. :param window: An ``X`` window. :param int message: The message to send. :param int d1: Message specific data. :param int d2: Message specific data. :param int d3: Message specific data. """ self._display.send_event( self.systray_manager, display.event.ClientMessage( type=X.ClientMessage, client_type=self._display.intern_atom( self._NET_SYSTEM_TRAY_OPCODE), window=window.id, data=( 32, ( X.CurrentTime, message, d1, d2, d3))), event_mask=X.NoEventMask) def _dock_window(self): """Docks the window in the systray. This method traps ``X`` errors. """ with self.display: self._send_message( self.systray_manager, self.SYSTEM_TRAY_REQUEST_DOCK, self.window.id) def _get_icon_data(self, width, height): """Returns the icon image. :param int width: The requested width. :param int height: The requested height. :return: icon data, which may already be cached :rtype: (width, height, data) """ if self._icon_data is None \ or self._icon_data.size != (width, height): self._icon_data = PIL.Image.new( 'RGB', (width, height)) self._icon_data.paste(self._icon.resize( (width, height), PIL.Image.ANTIALIAS)) return self._icon_data def _on_event(self, e): """The default event handler. :param e: The current ``X`` event. :raises StopIteration: if e is an ``X.DestroyNotify`` event """ if e and e.type == X.DestroyNotify: raise StopIteration() def on_expose(self, e): geometry = e.window.get_geometry() self.window.put_pil_image( self._gc, 0, 0, self._get_icon_data(geometry.width, geometry.height)) def on_buttonpress(self, e): self.on_click() def _mainloop(self, handlers={}): """The main ``X`` event loop. This is blocking, so it should be run in a separate thread. This method will break when an event handler raises ``StopIteration``. :param handlers: Event handler overrides. The keys are on the format ``'on_' + ``. If a key for an event is missing, the corresponding method in self is called. If that does not exist, the value for the key ``'on_event'`` is used, and if that does not exist a default event handler is called. """ self._display = display.Display() with self.display: self._dock_window() # Find the default handler for events default_handler = handlers.get('on_event', self._on_event) for e in self.events(): # The name of the handler function handler_name = 'on_' + e.__class__.__name__.lower() # Execute the handler; prefer a handler from handlers, then fall # back on instance methods, and then fall back on no action try: handlers.get( handler_name, getattr( self, handler_name, default_handler))(e) except StopIteration: break def __init__(self, description, on_click): """Creates a systray tray icon. :param str description: A descriptive text to apply to the icon. :param callable on_click: A callback for when the systray icon is clicked. """ super(SystemTrayIcon, self).__init__(description, on_click) self._display = None self._window = None # Load the icon from ../../../html/img/ self._icon = PIL.Image.open( pkg_resources.resource_stream( PKG_RESOURCES_PACKAGE, os.path.join( 'html', 'img', 'icon196x196.png'))) self._icon_data = None self._thread = threading.Thread(target=self._mainloop) self._thread.daemon = True self._thread.start() @property def display(self): """The X display as a context manager that will collect X errors on exit and raise a RuntimeError if any errors occurred""" # Make sure we have a Display instance if self._display is None: raise RuntimeError('display not initiated') return display_manager(self._display) @property def window(self): """The X window to use as systray icon; the window will be created when read unless already created""" if self._window: return self._window with self.display as display: # Create the window screen = display.screen() self._window = screen.root.create_window( -1, -1, 1, 1, 0, screen.root_depth, event_mask=X.ExposureMask, window_class=X.InputOutput) self._window.set_wm_class( 'VirtualTouchpadSystemTrayIcon', 'VirtualTouchpad') self._window.set_wm_name( self._description) # Enable XEMBED for the window atom = display.intern_atom(self._XEMBED_INFO) self._window.change_property(atom, atom, 32, [ self.XEMBED_VERSION, self.XEMBED_MAPPED]) self._gc = self._window.create_gc() return self._window @property def systray_manager(self): """The X window that owns the systray selection, or None if no window does""" while True: with self.display as display: display.grab_server() systray_manager = display.get_selection_owner( display.intern_atom( '_NET_SYSTEM_TRAY_S%d' % display.get_default_screen())) display.ungrab_server() display.flush() if systray_manager != X.NONE: return display.create_resource_object( 'window', systray_manager.id) # TODO: Wait for a systray manager to appear break def events(self): """Generates X events until the ``X`` connection is terminated. If the ``X`` connection is killed, the last event yielded will be ``None``. :raises RuntimeError: if the display has not been initiated """ if self._display is None: raise RuntimeError('display not initiated') while True: # Read the next event or silently exit try: e = self._display.next_event() except: e = None yield e if e is None: break def destroy(self): """Destroys the system tray icon. """ if getattr(self, '_window', None): self._window.destroy() PKGGP-$$)virtualtouchpad/systray/win32/__init__.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . from virtualtouchpad import platform with platform.modules(): from .win32systray import * PKZ\:HMvv-virtualtouchpad/systray/win32/win32systray.py# coding=utf-8 # virtual-touchpad # Copyright (C) 2013-2015 Moses Palmér # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see . import os import pkg_resources import tempfile import threading import win32con from .. import SystemTrayIcon import virtualtouchpad.platform.win32 as win try: import winxpgui as win32gui except ImportError: import win32gui class SystemTrayIcon(SystemTrayIcon): WINDOW_CLASS_NAME = 'VirtualTouchpadWindow' WM_NOTIFY = win32con.WM_USER + 20 def _add_icon(self): """Adds a systray icon. """ if self._notify_id: message = win32gui.NIM_MODIFY else: message = win32gui.NIM_ADD self._notify_id = ( self.window, 0, win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP, self.WM_NOTIFY, self.icon, self._description) win32gui.Shell_NotifyIcon(message, self._notify_id) def _delete_icon(self): """Deletes a systray icon. If the icon has not been added, this method has no effect. """ if not self._notify_id: return win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._notify_id) self._notify_id = None def _on_notify(self, event): if event == win32con.WM_LBUTTONDOWN: self.on_click() def _mainloop(self): """The main *win32* event loop. This is blocking, so it should be run in a separate thread. """ self._add_icon() win32gui.PumpMessages() self._delete_icon() def __init__(self, description, on_click): """Creates a systray tray icon. :param str description: A descriptive text to apply to the icon. :param callable on_click: A callback for when the systray icon is clicked. """ super(SystemTrayIcon, self).__init__(description, on_click) self._window = None self._notify_id = None self._thread = threading.Thread(target=self._mainloop) self._thread.daemon = True self._thread.start() @property def icon(self): """The *win32* icon handle for the systray icon; the icon will be loaded if has not yet been created""" if hasattr(self, '_icon'): return self._icon self._icon = ( self._icon_from_app() or self._icon_from_pkg_resources() or self._icon_from_fs()) return self._icon def _icon_from_app(self): """Loads the icon as a resource from the currently running application. This works only if the application has been packaged with *py2exe*. """ instance = win32gui.GetModuleHandle(None) try: return win32gui.LoadImage( instance, win.IDI_MAINICON, win32con.IMAGE_ICON, 0, 0, win32con.LR_DEFAULTSIZE) except: pass def _icon_from_pkg_resources(self): """Loads the icon icon data stream using ``pkg_resources``, writes it to a temporary file and then loads it as an icon. The temporary file is removed afterwards. """ icon_path = os.path.join( os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir, os.path.pardir, os.path.pardir, 'build', 'icos', 'icon-all.ico') instance = win32gui.GetModuleHandle(None) try: with pkg_resources.resource_stream(*win.PATH_MAINICON) as f: data = f.read() fd, icon_path = tempfile.mkstemp() try: with os.fdopen(fd, 'wb') as f: f.write(data) return win32gui.LoadImage( instance, icon_path, win32con.IMAGE_ICON, 0, 0, win32con.LR_DEFAULTSIZE | win32con.LR_LOADFROMFILE) finally: try: os.unlink(icon_path) except: pass except: pass def _icon_from_fs(self): """Loads the icon from the build directory in the file system. This works only if the icon is actually available in the file system. """ icon_path = os.path.join( os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir, os.path.pardir, os.path.pardir, 'build', 'icos', 'icon-all.ico') instance = win32gui.GetModuleHandle(None) try: return win32gui.LoadImage( instance, icon_path, win32con.IMAGE_ICON, 0, 0, win32con.LR_DEFAULTSIZE | win32con.LR_LOADFROMFILE) except: pass @property def window(self): """The *win32* window to use as systray icon; the window will be created when read unless already created""" if self._window: return self._window # Register the window class window_class = win32gui.WNDCLASS() window_class.hInstance = win32gui.GetModuleHandle(None) window_class.hIcon = self.icon window_class.lpszClassName = self.WINDOW_CLASS_NAME window_class.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW window_class.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW) window_class.hbrBackground = win32con.COLOR_WINDOW # TODO: Create mapping from message to method window_class.lpfnWndProc = { SystemTrayIcon.WM_NOTIFY: lambda wnd, msg, w, l: self._on_notify(l)} class_atom = win32gui.RegisterClass(window_class) # Create the window self._window = win32gui.CreateWindow( class_atom, self.WINDOW_CLASS_NAME, win32con.WS_OVERLAPPED | win32con.WS_SYSMENU, 0, 0, win32con.CW_USEDEFAULT, win32con.CW_USEDEFAULT, 0, 0, window_class.hInstance, None) return self._window def destroy(self): """Removes the systray icon. """ self._delete_icon() PKHHKV#/virtual_touchpad-0.13.dist-info/DESCRIPTION.rstVirtual Touchpad ================ This application allows you to use a mobile phone or tablet as a touchpad and keyboard for your computer. No software needs to be installed on the device. Quick Start ----------- On *Windows*, you can use the pre-packaged binary ``virtualtouchpad.exe``. When you run it, an icon will appear in the notification area. Hovering over this icon reveals the URL to use on your phone or tablet. If no pre-built executable exists for your platform, launch it from a terminal: python -m virtualtouchpad This will start an HTTP server. It will print the line Starting server http://:/... Connect to the URL displayed. Installation ------------ Install this application by running the following command: pip install virtual-touchpad If you want to have access to pre-release versions, you can clone the *git* repository available from the linked *home page* below. Install by running this command: cd $VIRTUAL_TOUCHPAD_REPO python setup.py install Installation issues ~~~~~~~~~~~~~~~~~~~ When installing, the dependencies for this application are also downloaded. Some of the dependencies are native libraries and must be compiled before they can be used. There is no standard way of providing any dependencies for the native libraries through this website, so they must thus be present on your computer before you run the installation, as do *Python* development headers files. The easies way to install the headers is via the packager manager provided by your operating system. The names of the packages required depend on your specific operating system. Release Notes ============= v0.13 - Mac OSX Support ----------------------- * Added support for *Mac OSX* by replacing internal keyboard and mouse handling with pynput_ and, for now, making the systray icon optional. .. _pynput: https://pypi.python.org/pypi/pynput v0.12.4 - Corrected packaging ----------------------------- * Ensure that only dependencies for the current platform are required. * Allow loading the *systray icon* for *Windows* when running from a wheel. v0.12.3 - Corrected clicks -------------------------- * Corrected touch pad clicks. v0.12.2 - Corrected imports --------------------------- * Corrected imports. v0.12.1 - No more PIL --------------------- * Replaced dependency on *PIL* with *Pillow*. This should make it possible to install from *PyPi*. v0.12 - Shiny Keyboard ---------------------- * Support for keyboards has been added. For now only two layouts are included. * The user interface has been polished. * *Virtual Touchpad* now broadcasts its presence on the network using *mDNS*. v0.11 - Translations -------------------- * *Virtual Touchpad* can now be translated into other languages. * Added *Swedish* translation. v0.10 - Systray on Windows -------------------------- * Added systray icon for *Windows*. v0.9.2 - Fixed building on Windows ---------------------------------- * *Virtual Touchpad* now supports zip-safe again. * The build script does not fail if *ImageMagick* ``convert`` is not the first ``convert`` on the path. v0.9.1 - Fixed systray window on Linux -------------------------------------- * The systray window is no longer mapped on *Linux*. v0.9 - Systray on Linux ----------------------- * Added systray icon for *Linux*. v0.8 - Configure sensitivity ---------------------------- * The sensitivity and acceleration of the trackpad is now configurable. * Clicking is now easier and allows the finger to move slightly across the screen. v0.7 - Run from single file --------------------------- * *Virtual Touchpad* can now be run from a zipped egg. * Py2exe is now supported to pack *Virtual Touchpad* into a single exe file on *Windows*. v0.6 - Windows support ---------------------- * It is now possible to run *Virtual Touchpad* on *Windows*. v0.5 - Installation possible ---------------------------- * Corrected snapping of bottom tool bar. * Corrected bugs in setup script that prevented *Virtual Touchpad* from being installed. v0.4 - Basic help ----------------- * Made scrolling a lot smoother. * Added basic *FAQ*. v0.3 - Extended user interface ------------------------------ * Added support for *drag-and-drop*. * Added a bottom toolbar with a fullscreen button. * Increased size of message box text. v0.2 - Initial release ---------------------- * Basic touchpad support, with hard-coded sensitivity and acceleration. * Basic offline support using *AppCache*. PKHHkq-virtual_touchpad-0.13.dist-info/metadata.json{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: End Users/Desktop", "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows :: Windows NT/2000", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2.7"], "extensions": {"python.details": {"contacts": [{"email": "moses.palmer@gmail.com", "name": "Moses Palm\u00e9r", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/moses-palmer/virtual-touchpad"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "keywords": ["control", "mouse", "control", "keyboard"], "license": "GPLv3", "metadata_version": "2.0", "name": "virtual-touchpad", "platform": "linux", "run_requires": [{"requires": ["bottle (>=0.11)", "gevent (>=0.13)", "gevent-websocket (>=0.9)", "netifaces (>=0.8)", "pynput (>=0.6)", "zeroconf (>=0.17)"]}, {"environment": "sys_platform == \"linux2\"", "requires": ["Pillow"]}, {"environment": "sys_platform == \"win32\"", "requires": ["pywin32"]}], "summary": "Turns your mobile or tablet into a touchpad and keyboard for your computer.", "version": "0.13"}PKHHY@..(virtual_touchpad-0.13.dist-info/pbr.json{"is_release": true, "git_version": "76ada06"}PKHH0!=-virtual_touchpad-0.13.dist-info/top_level.txtvirtualtouchpad PKSZAG2(virtual_touchpad-0.13.dist-info/zip-safe PKHH''\\%virtual_touchpad-0.13.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKHHgt))(virtual_touchpad-0.13.dist-info/METADATAMetadata-Version: 2.0 Name: virtual-touchpad Version: 0.13 Summary: Turns your mobile or tablet into a touchpad and keyboard for your computer. Home-page: https://github.com/moses-palmer/virtual-touchpad Author: Moses Palmér Author-email: moses.palmer@gmail.com License: GPLv3 Keywords: control mouse,control keyboard Platform: linux Platform: windows Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows :: Windows NT/2000 Classifier: Operating System :: POSIX Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Requires-Dist: bottle (>=0.11) Requires-Dist: gevent (>=0.13) Requires-Dist: gevent-websocket (>=0.9) Requires-Dist: netifaces (>=0.8) Requires-Dist: pynput (>=0.6) Requires-Dist: zeroconf (>=0.17) Requires-Dist: Pillow; sys_platform == "linux2" Requires-Dist: pywin32; sys_platform == "win32" Virtual Touchpad ================ This application allows you to use a mobile phone or tablet as a touchpad and keyboard for your computer. No software needs to be installed on the device. Quick Start ----------- On *Windows*, you can use the pre-packaged binary ``virtualtouchpad.exe``. When you run it, an icon will appear in the notification area. Hovering over this icon reveals the URL to use on your phone or tablet. If no pre-built executable exists for your platform, launch it from a terminal: python -m virtualtouchpad This will start an HTTP server. It will print the line Starting server http://:/... Connect to the URL displayed. Installation ------------ Install this application by running the following command: pip install virtual-touchpad If you want to have access to pre-release versions, you can clone the *git* repository available from the linked *home page* below. Install by running this command: cd $VIRTUAL_TOUCHPAD_REPO python setup.py install Installation issues ~~~~~~~~~~~~~~~~~~~ When installing, the dependencies for this application are also downloaded. Some of the dependencies are native libraries and must be compiled before they can be used. There is no standard way of providing any dependencies for the native libraries through this website, so they must thus be present on your computer before you run the installation, as do *Python* development headers files. The easies way to install the headers is via the packager manager provided by your operating system. The names of the packages required depend on your specific operating system. Release Notes ============= v0.13 - Mac OSX Support ----------------------- * Added support for *Mac OSX* by replacing internal keyboard and mouse handling with pynput_ and, for now, making the systray icon optional. .. _pynput: https://pypi.python.org/pypi/pynput v0.12.4 - Corrected packaging ----------------------------- * Ensure that only dependencies for the current platform are required. * Allow loading the *systray icon* for *Windows* when running from a wheel. v0.12.3 - Corrected clicks -------------------------- * Corrected touch pad clicks. v0.12.2 - Corrected imports --------------------------- * Corrected imports. v0.12.1 - No more PIL --------------------- * Replaced dependency on *PIL* with *Pillow*. This should make it possible to install from *PyPi*. v0.12 - Shiny Keyboard ---------------------- * Support for keyboards has been added. For now only two layouts are included. * The user interface has been polished. * *Virtual Touchpad* now broadcasts its presence on the network using *mDNS*. v0.11 - Translations -------------------- * *Virtual Touchpad* can now be translated into other languages. * Added *Swedish* translation. v0.10 - Systray on Windows -------------------------- * Added systray icon for *Windows*. v0.9.2 - Fixed building on Windows ---------------------------------- * *Virtual Touchpad* now supports zip-safe again. * The build script does not fail if *ImageMagick* ``convert`` is not the first ``convert`` on the path. v0.9.1 - Fixed systray window on Linux -------------------------------------- * The systray window is no longer mapped on *Linux*. v0.9 - Systray on Linux ----------------------- * Added systray icon for *Linux*. v0.8 - Configure sensitivity ---------------------------- * The sensitivity and acceleration of the trackpad is now configurable. * Clicking is now easier and allows the finger to move slightly across the screen. v0.7 - Run from single file --------------------------- * *Virtual Touchpad* can now be run from a zipped egg. * Py2exe is now supported to pack *Virtual Touchpad* into a single exe file on *Windows*. v0.6 - Windows support ---------------------- * It is now possible to run *Virtual Touchpad* on *Windows*. v0.5 - Installation possible ---------------------------- * Corrected snapping of bottom tool bar. * Corrected bugs in setup script that prevented *Virtual Touchpad* from being installed. v0.4 - Basic help ----------------- * Made scrolling a lot smoother. * Added basic *FAQ*. v0.3 - Extended user interface ------------------------------ * Added support for *drag-and-drop*. * Added a bottom toolbar with a fullscreen button. * Increased size of message box text. v0.2 - Initial release ---------------------- * Basic touchpad support, with hard-coded sensitivity and acceleration. * Basic offline support using *AppCache*. PKHH&virtual_touchpad-0.13.dist-info/RECORDvirtual_touchpad-0.13.dist-info/DESCRIPTION.rst,sha256=0vTysdRniUtwttxt2v830XxjJEfZ3EhNAQkePqu4FCM,4602 virtual_touchpad-0.13.dist-info/METADATA,sha256=662e4e7sK5_WYL3owOqw2SJKVmwB9XyYLavgxyIYye4,5673 virtual_touchpad-0.13.dist-info/RECORD,, virtual_touchpad-0.13.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 virtual_touchpad-0.13.dist-info/metadata.json,sha256=BOBx5PqGrIz7oMjW7stugbyfO0JifVanTX6bfSmMC1k,1249 virtual_touchpad-0.13.dist-info/pbr.json,sha256=Bp0ZmrwrfUKmjR7xdYt3kDchlFTELYsjhDV3_EbyDos,46 virtual_touchpad-0.13.dist-info/top_level.txt,sha256=f8ST-YMGazjIsKXQ14kaccfO2bjoI0N8PV_OU5aK6-I,16 virtual_touchpad-0.13.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 virtualtouchpad/__init__.py,sha256=JQcFfCvbvBs0kP_ZPTOOwkgFUp8-UNywtpFJZBHixnQ,755 virtualtouchpad/__main__.py,sha256=Gc7IKzhx3gWqEGmJbDwNiCNU_2O_NXZMs8IqP_NzAbU,3871 virtualtouchpad/_info.py,sha256=gS1yUmVV6GfiO60twuNcaeeJN_0GHgbxJIS59zR280M,67 virtualtouchpad/announce.py,sha256=4Ak2YmXqRl-rYevV57dFom51oit6yagb_ns24ME_xCs,1891 virtualtouchpad/event/__init__.py,sha256=32J6UCRzpcRVk7RTNSuV6P_6GUfueUsPnKHFIEH9X0U,3400 virtualtouchpad/event/_win32_syms.py,sha256=1ZlRPFWi0c351_zQjCU2PDENoD2WjG0ScTSRm6kcpgM,1441 virtualtouchpad/event/win32.py,sha256=1EmHI4TuAdfA8gV_be5hXAXqwTfTIORgSlrC08Hazuo,5894 virtualtouchpad/event/xorg.py,sha256=r8oxS-iq_dgT7xDQmnU4VUtkGsVaqyepOerCdM6rrgw,3930 virtualtouchpad/html/favicon.ico,sha256=CXrb0YRD471DTPLqjyrylLOU5ffFKfYRgeUy5EMJ2dU,89510 virtualtouchpad/html/index.min.xhtml,sha256=iEVWPyizZTalJX1GLCBRagr0j3QUmm7vQ9QbfFwp0UA,36110 virtualtouchpad/html/index.xhtml,sha256=aK8IymsJzAPFHzLQCycv5UlHo19nFUcSh571yUGC0G0,5946 virtualtouchpad/html/virtual-touchpad.appcache,sha256=zgxB_9Xz420It47BhJP035v5MV4zYD0PNPPSPpzhABs,107 virtualtouchpad/html/css/app.css,sha256=IqUhQJU3QkrINoF89XpOc4qQyDA9g_26ROfXf9vWpuE,3658 virtualtouchpad/html/css/help.css,sha256=IIuHlGtqp5RteqyTHiAHtzCr9iMNn1ppx-MU-iGV7jU,495 virtualtouchpad/html/css/keyboard.css,sha256=FSRYOYsNUF_3QHgtEY4EzDeai5w-m-But5pQfkt2Ug8,1659 virtualtouchpad/html/css/message-box.css,sha256=6UPMbt___1ZhXcBqgPcYzlqInlMsnUe7twt07PHdlIQ,384 virtualtouchpad/html/css/touchpad.css,sha256=Ao1awSlJ5o4TgFXAsUIHHayD6iZc9gfhxQOPhCDaqLE,223 virtualtouchpad/html/css/trackbar.css,sha256=OMjFsg2jJ9Jjtwy9nh260d1PGj44-unkKa_R-tu38Go,733 virtualtouchpad/html/img/fullscreen.svg,sha256=fSc4M0B4a62A-1E3r37EvnYn0YZX6tKQOwSMyfKVDF0,913 virtualtouchpad/html/img/help.svg,sha256=SgcKRuYfdXxQy4ophySZ-sSr8NAzRSNN-qK3mhRXXng,463 virtualtouchpad/html/img/icon114x114.png,sha256=ypNmbvskY2QwEYH0NdiTa2ubU57x5hQqWut2WgcdU4E,17521 virtualtouchpad/html/img/icon144x144.png,sha256=cuuQbKFx5MhlreUqC29U4PDogdTpiHg-6ycVPqc4dZg,22844 virtualtouchpad/html/img/icon196x196.png,sha256=kHGwBa10wXKxMyDvNMWPRTJvrpAOPt1vwhiIcKcklBE,33867 virtualtouchpad/html/img/icon48x48.png,sha256=f5g0HVB3eMsy5UGIyGVxM9r_uGTpC8kHDv4YUos-pmg,6729 virtualtouchpad/html/img/icon57x57.png,sha256=YuNfmt7wI1om8VFE0iWRszvS7FGxWw7ACWIa_XCmxO0,7806 virtualtouchpad/html/img/icon72x72.png,sha256=7n0sIp85HML_TPOYeuXG1ZBxEU0Dj6ym9N5sMWdJp7M,10266 virtualtouchpad/html/img/icon96x96.png,sha256=Gn5MO24Fc8Mael-8r1Yg42mfSkZQIADkkqT1s3Ix2uw,14163 virtualtouchpad/html/img/settings.svg,sha256=TsCIAXyIlNv-davWMz9TVd9-4XHPaJ_GZypDFt5b25I,399 virtualtouchpad/html/js/app.js,sha256=UAXkeJ-4E03UjJU3VhBSaa_aitvW0tXa7jGBzIje7lk,13933 virtualtouchpad/html/js/lib/checks.js,sha256=OvBdrQqzBvEb72Ml3-uddk9Wp8I1ds4W6FEdqxAmb6A,3739 virtualtouchpad/html/js/lib/configuration.js,sha256=uwZcJJWmuELiU1XaveSB3tM_XOfOYy1DdM4eD4-kOyU,3688 virtualtouchpad/html/js/lib/controller.js,sha256=3-YuZCDFKv56knnBQESOtoWIq55jZSUklAXOJYnoupQ,3990 virtualtouchpad/html/js/lib/features.js,sha256=v__I6Uq55-Dlw5E27D-wp534B7ZFV4FKn3m1zXrWyVs,3205 virtualtouchpad/html/js/lib/include.js,sha256=TSC4U1o3M428wMvrBYDSs2TAIczDuVBy7_S4xz9LqKk,3029 virtualtouchpad/html/js/lib/init.js,sha256=tv-HIyaUvEUXe4gCqUJ_tosi3i758SX-FGCdJEyIJNw,1406 virtualtouchpad/html/js/lib/keyboard.js,sha256=vOq6u07YwQsB-BXGGjXaP5UZ9zVZVivjtNmBHceTTxU,18095 virtualtouchpad/html/js/lib/message-box.js,sha256=KPbNj1Pih9W07hV2-FQyWZwVS7XX52Uv2ZE4blv3udE,2557 virtualtouchpad/html/js/lib/trackbar.js,sha256=26f8a-fZ-Oup07-8LYUXr1RvxYux6XDuz3IFBJHmd_w,8833 virtualtouchpad/html/js/lib/translation.js,sha256=NL1jV-0YaFXGjF6fZNF10qYv1HzTHyVL3eiXOVIKm4s,4275 virtualtouchpad/html/js/lib/util.js,sha256=rYfy8n3GIpzae2chQikO1tFHY7l1KY5jc7SVbmdb0_M,3403 virtualtouchpad/html/js/lib/view.js,sha256=Uzj9doNH4Ssj-meE8n-kpgHhgsysFCbikxqa4xeL1fs,9523 virtualtouchpad/html/keyboard/buttons.svg,sha256=zL9UbdORRWFDRFS8HJ5vtt22823daj_62hqYeE7TmLM,9781 virtualtouchpad/html/keyboard/geometry/function-keys.svg,sha256=_Fuq0GfFJS7b-4NqcjtKVxv-5IzEBVAB4YfA66apo44,4932 virtualtouchpad/html/keyboard/geometry/large.svg,sha256=Blpv6KR_bsEu63HjyMVubNuLka9cBTpF2lYCR5YLHGA,6106 virtualtouchpad/html/keyboard/layout/en.layout,sha256=wZC6zneWbti1s26D81-FhcVpgnkN_dSXIydxXiUnVF4,19776 virtualtouchpad/html/keyboard/layout/se.layout,sha256=_RmxI1fi_h97AFZRJodZcJBbRP7cAYTMXNVDsUBOgUY,15678 virtualtouchpad/html/translations/help/default.js,sha256=VI5DIbGm8E2lQvhHoqGzuOfnrLmvGKZmE3nWzjZDZ5s,95 virtualtouchpad/html/translations/help/sv.js,sha256=N0zomy4iGxmfecZC2Y6Xw0UH7CEJHg7oOkI9oGvv8DM,1152 virtualtouchpad/html/translations/index/default.js,sha256=VI5DIbGm8E2lQvhHoqGzuOfnrLmvGKZmE3nWzjZDZ5s,95 virtualtouchpad/html/translations/index/sv.js,sha256=vxsiF1RcaStdUdVlCsmYr1zfUQOZnSZKl2Ds-MyjGMQ,943 virtualtouchpad/platform/__init__.py,sha256=iJqprrO_kZsrKr1sUdvgzz9gPgkLHNyfra3eKb1fJTM,5709 virtualtouchpad/platform/win32.py,sha256=rSbxnoau4vLDI_D_mX5EofaZdRUnTMA_oQnqw_Rc_Ho,872 virtualtouchpad/platform/xorg.py,sha256=B9V7IqaxEBQ86ARl4YQF2WWY8EOSbLhYK2Q90ZYlhMM,1521 virtualtouchpad/server/__init__.py,sha256=Pev8QXCfJ3rYvk1awiN_w0enaCKIjA6n4kMfX6y2Te8,1322 virtualtouchpad/server/dispatchers/__init__.py,sha256=byEW3PCswE24yDAlorRZW7CJqgQx_Z3ycqAtjxQUKlo,1852 virtualtouchpad/server/dispatchers/keyboard.py,sha256=iMlinJKRn_k45B5uEqCvgfPBouti_1CL7F0Bz0zbipA,2608 virtualtouchpad/server/dispatchers/mouse.py,sha256=VITHwmFucLTsch_XkUHkJt992yrBW5LOZKqA11zJduQ,2372 virtualtouchpad/server/routes/__init__.py,sha256=KSWMMepl29GZ4WXaddDuPoTbd1tOR0z051gaGSotWww,931 virtualtouchpad/server/routes/controller.py,sha256=74RtcNfjRmZbVLR03-tsOzO1fMo64Lh-KQLh0-sBz9E,2870 virtualtouchpad/server/routes/keyboard.py,sha256=GfmvLUoFHFdV8dUkv32SdK22wDyyjZk_A-2_mBM9_hw,2053 virtualtouchpad/server/routes/static.py,sha256=-SpqOPyn16CTbSoh0rW_367B1Y5RcYAIJLF8bKVYe-Q,1441 virtualtouchpad/server/routes/translations.py,sha256=EyBYNVZAhoD9mwzj4hklQ_Ee1_YOA5uAkNrW95fI7sg,1442 virtualtouchpad/server/util/__init__.py,sha256=zn-UC9traCy_mkq4DBTNnBrcLbZrmPEEmyEz34tDl5Y,708 virtualtouchpad/server/util/static_file.py,sha256=8IMN5wQskdPxn6dT7sW9NeAyvaVqsJkDUhi-08EB2EQ,5705 virtualtouchpad/systray/__init__.py,sha256=Pn-mj8vP6dNQhy8t-nA5pR8GpmEo7cpuPPVJPApcojA,1440 virtualtouchpad/systray/win32/__init__.py,sha256=wp8p69SpIQSyVKHfBoLl5DpMN9BMxp-jfoCqZ-eutsI,804 virtualtouchpad/systray/win32/win32systray.py,sha256=j2gvlljXVC-8B1Itzv5pXQmCZirYQTVIwP7aQ2gXLjQ,7030 virtualtouchpad/systray/xorg/__init__.py,sha256=MjqMi_U-dao6W4DvvipfjCiwwKualk34h2o2HBYQlgM,800 virtualtouchpad/systray/xorg/xsystray.py,sha256=ZIjf2TZeDg2HTCs5iWwO5RtfqaspJK2iNH9Zbsv2DGw,8726 PKGGjIvirtualtouchpad/__init__.pyPKGG`cc,virtualtouchpad/announce.pyPKHHPCC virtualtouchpad/_info.pyPKiHH _A virtualtouchpad/__main__.pyPKGGJC]] virtualtouchpad/html/favicon.icoPKGGDH\:: }xvirtualtouchpad/html/index.xhtmlPKHHd 6Ikk.virtualtouchpad/html/virtual-touchpad.appcachePKiHHz8$virtualtouchpad/html/index.min.xhtmlPKNG#I5&5&)virtualtouchpad/html/keyboard/buttons.svgPKiHHdv0xDvirtualtouchpad/html/keyboard/geometry/large.svgPKiHHQq*DD8\virtualtouchpad/html/keyboard/geometry/function-keys.svgPKҬEG겴@M@M.:pvirtualtouchpad/html/keyboard/layout/en.layoutPKiHH)>=>=.ƽvirtualtouchpad/html/keyboard/layout/se.layoutPKGGM>__2Pvirtualtouchpad/html/translations/index/default.jsPKHHs-virtualtouchpad/html/translations/index/sv.jsPKGGM>__1virtualtouchpad/html/translations/help/default.jsPKHHDπ,virtualtouchpad/html/translations/help/sv.jsPK6YAGˡW"%qvirtualtouchpad/html/css/touchpad.cssPKGGԻJJ virtualtouchpad/html/css/app.cssPKGG9/-!virtualtouchpad/html/css/help.cssPK7NG F{{%Ivirtualtouchpad/html/css/keyboard.cssPKGGlL(virtualtouchpad/html/css/message-box.cssPKGG.B%virtualtouchpad/html/css/trackbar.cssPKGG VS7S7&"virtualtouchpad/html/img/icon96x96.pngPKGGȨ~~&Zvirtualtouchpad/html/img/icon57x57.pngPKGG@virtualtouchpad/event/_win32_syms.pyPKR9H]bDvirtualtouchpad/event/win32.pyPKڋHHac**"[virtualtouchpad/server/__init__.pyPKGGKŝ)avirtualtouchpad/server/routes/__init__.pyPKGG 'dvirtualtouchpad/server/routes/static.pyPKGGuc)jvirtualtouchpad/server/routes/keyboard.pyPKGG-*svirtualtouchpad/server/routes/translations.pyPKGG6 6 +yvirtualtouchpad/server/routes/controller.pyPKiHH2D D +virtualtouchpad/server/dispatchers/mouse.pyPKiHH^G<<.#virtualtouchpad/server/dispatchers/__init__.pyPKiHHve0 0 .virtualtouchpad/server/dispatchers/keyboard.pyPKGG@''virtualtouchpad/server/util/__init__.pyPKGGTDII*0virtualtouchpad/server/util/static_file.pyPKGG MM$virtualtouchpad/platform/__init__.pyPKGGzr Pvirtualtouchpad/platform/xorg.pyPKZ\:H  (virtualtouchpad/systray/xorg/__init__.pyPKGG ""(mvirtualtouchpad/systray/xorg/xsystray.pyPKGGP-$$)virtualtouchpad/systray/win32/__init__.pyPKZ\:HMvv-4 virtualtouchpad/systray/win32/win32systray.pyPKHHKV#/$virtual_touchpad-0.13.dist-info/DESCRIPTION.rstPKHHkq-<7virtual_touchpad-0.13.dist-info/metadata.jsonPKHHY@..(h<virtual_touchpad-0.13.dist-info/pbr.jsonPKHH0!=-<virtual_touchpad-0.13.dist-info/top_level.txtPKSZAG2(7=virtual_touchpad-0.13.dist-info/zip-safePKHH''\\%~=virtual_touchpad-0.13.dist-info/WHEELPKHHgt))(>virtual_touchpad-0.13.dist-info/METADATAPKHH&Tvirtual_touchpad-0.13.dist-info/RECORDPKMMq