├── debian ├── compat ├── docs ├── python-whiteboard.manpages ├── menu ├── README.source ├── rules ├── control ├── copyright ├── python-whiteboard.1 ├── manpage.xml └── changelog ├── .gitignore ├── stuff ├── screen.png ├── threads.py ├── calibration2.ui ├── pbar.ui ├── wiimote.py ├── cursor.py ├── configuration.py ├── calibration.py ├── mainwindow.ui ├── configuration.ui ├── linuxWiimoteLib.py ├── pywhiteboard.py └── about.ui ├── generate_trans.sh ├── dist └── python-whiteboard.desktop ├── python-whiteboard ├── python-whiteboard-test ├── makefile ├── README └── License.txt /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/docs: -------------------------------------------------------------------------------- 1 | README 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.qm 3 | -------------------------------------------------------------------------------- /debian/python-whiteboard.manpages: -------------------------------------------------------------------------------- 1 | debian/python-whiteboard.1 -------------------------------------------------------------------------------- /stuff/screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnegre/python-whiteboard/HEAD/stuff/screen.png -------------------------------------------------------------------------------- /debian/menu: -------------------------------------------------------------------------------- 1 | ?package(python-whiteboard):needs="X11" section="Applications/Education"\ 2 | title="python-whiteboard" command="/usr/bin/python-whiteboard" 3 | -------------------------------------------------------------------------------- /generate_trans.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | pylupdate4 stuff/*.ui stuff/*.py -ts trans/pywhiteboard_es.ts trans/pywhiteboard_ca.ts trans/pywhiteboard_it.ts trans/pywhiteboard_fr.ts trans/pywhiteboard_zh.ts 4 | 5 | -------------------------------------------------------------------------------- /dist/python-whiteboard.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Type=Application 4 | Encoding=UTF-8 5 | Name=python-whiteboard 6 | Icon=pywb_pixmap.xpm 7 | Exec=/usr/bin/python-whiteboard 8 | Categories=Application;Education 9 | 10 | -------------------------------------------------------------------------------- /stuff/threads.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import PyQt5.Qt as qt 5 | 6 | 7 | 8 | def CreateThreadClass(func): 9 | class Thread(qt.QThread): 10 | def run(self): 11 | func() 12 | return Thread 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /debian/README.source: -------------------------------------------------------------------------------- 1 | python-whiteboard for Debian 2 | ---------------------------- 3 | 4 | the source comes from https://github.com/pnegre/python-whiteboard.git 5 | 6 | modifications are managed by quilt, see 7 | "/usr/share/doc/quilt/README.source" 8 | 9 | 10 | 11 | 12 | -- Georges Khaznadar , Sat, 28 May 2011 23:41:52 +0200 13 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | # Sample debian/rules that uses debhelper. 4 | # This file was originally written by Joey Hess and Craig Small. 5 | # As a special exception, when this file is copied by dh-make into a 6 | # dh-make output file, you may use that output file without restriction. 7 | # This special exception was added by Craig Small in version 0.37 of dh-make. 8 | 9 | # Uncomment this to turn on verbose mode. 10 | #export DH_VERBOSE=1 11 | 12 | %: 13 | dh $@ 14 | -------------------------------------------------------------------------------- /python-whiteboard: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os 5 | 6 | STUFFDIR = '/usr/share/python-whiteboard' 7 | 8 | def main(): 9 | global STUFFDIR 10 | if not os.path.isdir(STUFFDIR): 11 | print("Can't find " + STUFFDIR) 12 | sys.exit(1) 13 | 14 | #apply our directories and test environment 15 | os.chdir(STUFFDIR) 16 | sys.path.insert(0, STUFFDIR) 17 | 18 | print("Using directory: " + STUFFDIR) 19 | 20 | import pywhiteboard 21 | pywhiteboard.main() 22 | 23 | 24 | if __name__ == '__main__': main() 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /python-whiteboard-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, os 5 | 6 | def main(): 7 | localpath = os.path.split(os.path.abspath(sys.argv[0]))[0] 8 | teststuff = os.path.join(localpath, 'stuff') 9 | if not os.path.isdir(teststuff): 10 | print("Not a testing environment") 11 | sys.exit(0) 12 | 13 | #apply our directories and test environment 14 | os.chdir(teststuff) 15 | sys.path.insert(0, teststuff) 16 | 17 | print("Using directory: " + teststuff) 18 | 19 | import pywhiteboard 20 | pywhiteboard.main() 21 | 22 | 23 | if __name__ == '__main__': main() 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | all: languages 2 | 3 | languages: 4 | lrelease trans/*.ts 5 | 6 | clean: 7 | rm -f trans/*.qm 8 | rm -f stuff/*.pyc 9 | 10 | install: 11 | mkdir -p $(DESTDIR)/usr/share/python-whiteboard 12 | mkdir -p $(DESTDIR)/usr/share/pixmaps 13 | mkdir -p $(DESTDIR)/usr/share/applications 14 | mkdir -p $(DESTDIR)/usr/bin 15 | mkdir -p $(DESTDIR)/usr/share/qt4/translations/ 16 | cp python-whiteboard $(DESTDIR)/usr/bin 17 | cp stuff/* $(DESTDIR)/usr/share/python-whiteboard 18 | cp dist/pywb_pixmap.xpm $(DESTDIR)/usr/share/pixmaps 19 | cp dist/python-whiteboard.desktop $(DESTDIR)/usr/share/applications 20 | cp trans/*.qm $(DESTDIR)/usr/share/qt4/translations/ 21 | 22 | deb: 23 | fakeroot dpkg-buildpackage -us -uc 24 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: python-whiteboard 2 | Section: python 3 | Priority: extra 4 | Maintainer: Georges Khaznadar 5 | Build-Depends: debhelper (>= 7.0.50~), 6 | python-all (>=2.6 ), libqt4-dev 7 | X-Python-Version: >= 2.5 8 | Standards-Version: 3.9.2 9 | Homepage: https://github.com/pnegre/python-whiteboard 10 | Uploaders: Georges Khaznadar 11 | 12 | 13 | Package: python-whiteboard 14 | Architecture: all 15 | Depends: python-bluez (>= 0.14), python-qt4 (>= 4.0), python-numpy (>= 1:1.0), 16 | python-xlib (>= 0.10), 17 | ${python:Depends}, ${misc:Depends} 18 | Description: Make your own electronic whiteboard 19 | python whiteboard is a program that lets you build your own 20 | electronic whiteboard. You only need a wii remote and a IR pen 21 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://dep.debian.net/deps/dep5 2 | Upstream-Name: python-whiteboard 3 | Source: https://github.com/pnegre/python-whiteboard.git 4 | 5 | Files: * 6 | Copyright: 2010-2011 Pere Negre 7 | License: GPL-2 8 | This program is free software; you can redistribute it and/or modify 9 | it under the terms of the GNU General Public License as published by 10 | the Free Software Foundation; either version 2 of the License, or 11 | (at your option) any later version. 12 | . 13 | This program is distributed in the hope that it will be useful, 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | GNU General Public License for more details. 17 | . 18 | see "/usr/share/common-licenses/GPL-2" 19 | 20 | Files: debian/* 21 | Copyright: 2011 Georges Khaznadar 22 | License: GPL-2+ 23 | This package is free software; you can redistribute it and/or modify 24 | it under the terms of the GNU General Public License as published by 25 | the Free Software Foundation; either version 2 of the License, or 26 | (at your option) any later version. 27 | . 28 | This package is distributed in the hope that it will be useful, 29 | but WITHOUT ANY WARRANTY; without even the implied warranty of 30 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 31 | GNU General Public License for more details. 32 | . 33 | You should have received a copy of the GNU General Public License 34 | along with this program. If not, see 35 | . 36 | On Debian systems, the complete text of the GNU General 37 | Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". 38 | -------------------------------------------------------------------------------- /debian/python-whiteboard.1: -------------------------------------------------------------------------------- 1 | '\" t 2 | .\" Title: PYTHON-WHITEBOARD 3 | .\" Author: Georges Khaznadar 4 | .\" Generator: DocBook XSL Stylesheets v1.75.2 5 | .\" Date: 05/28/2011 6 | .\" Manual: python-whiteboard User Manual 7 | .\" Source: python-whiteboard 8 | .\" Language: English 9 | .\" 10 | .TH "PYTHON\-WHITEBOARD" "1" "05/28/2011" "python-whiteboard" "python-whiteboard User Manual" 11 | .\" ----------------------------------------------------------------- 12 | .\" * Define some portability stuff 13 | .\" ----------------------------------------------------------------- 14 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 | .\" http://bugs.debian.org/507673 16 | .\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html 17 | .\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 18 | .ie \n(.g .ds Aq \(aq 19 | .el .ds Aq ' 20 | .\" ----------------------------------------------------------------- 21 | .\" * set default formatting 22 | .\" ----------------------------------------------------------------- 23 | .\" disable hyphenation 24 | .nh 25 | .\" disable justification (adjust text to left margin only) 26 | .ad l 27 | .\" ----------------------------------------------------------------- 28 | .\" * MAIN CONTENT STARTS HERE * 29 | .\" ----------------------------------------------------------------- 30 | .SH "NAME" 31 | python-whiteboard \- Make your own electronic whiteboard 32 | .SH "SYNOPSIS" 33 | .HP \w'\fBpython\-whiteboard\fR\ 'u 34 | \fBpython\-whiteboard\fR 35 | .SH "DESCRIPTION" 36 | .PP 37 | \fBpython\-whiteboard\fR 38 | is a program that lets you build your own electronic whiteboard\&. You only need a wii remote and a IR pen 39 | .SH "AUTHOR" 40 | .PP 41 | \fBGeorges Khaznadar\fR <\&georgesk@ofset\&.org\&> 42 | .RS 4 43 | Wrote this manpage for the Debian system\&. 44 | .RE 45 | .SH "COPYRIGHT" 46 | .br 47 | Copyright \(co 2011 Georges Khaznadar 48 | .br 49 | .PP 50 | Permission is granted to copy, distribute and/or modify this document under the terms of the GNU General Public License, Version 2 or (at your option) any later version published by the Free Software Foundation\&. 51 | .PP 52 | On Debian systems, the complete text of the GNU General Public License can be found in 53 | /usr/share/common\-licenses/GPL\&. 54 | .sp 55 | -------------------------------------------------------------------------------- /stuff/calibration2.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 539 10 | 481 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 539 22 | 481 23 | 24 | 25 | 26 | 27 | 539 28 | 481 29 | 30 | 31 | 32 | Dialog 33 | 34 | 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 17 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Qt::Horizontal 61 | 62 | 63 | 64 | 40 65 | 20 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | Cancel calibration 74 | 75 | 76 | 77 | 78 | 79 | 80 | Qt::Horizontal 81 | 82 | 83 | 84 | 40 85 | 20 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Linux Electronic Whiteboard with Wiimote 2 | ======================================== 3 | 4 | With this software you can build and operate a low-cost electronic 5 | whiteboard. You only need a wiimote, an IR pen and a great OS: gnu/linux. 6 | 7 | If you have git installed on your system you can get the latest (development) 8 | version, typing: 9 | 10 | $ git clone git://github.com/pnegre/python-whiteboard.git 11 | 12 | To download packaged versions of the program, point your browser to 13 | http://github.com/pnegre/python-whiteboard/downloads 14 | 15 | It's recommended to disable the desktop effects, to avoid program crashes and 16 | malfunctions. 17 | 18 | 19 | Compilation and installation 20 | ---------------------------- 21 | 22 | Use make: 23 | 24 | $ make all 25 | # make install 26 | 27 | If you want to create a .deb package: 28 | 29 | $ make deb 30 | 31 | Required packages: 32 | 33 | - To run the program: python3-bluez python3-qt5 python3-numpy python3-xlib 34 | - To run make: libqt5-dev qt5-dev-tools python-qt5-dev 35 | - To build the deb package: build-essential fakeroot dpkg-dev debhelper 36 | 37 | 38 | Configuration 39 | ------------- 40 | 41 | The configuration screen has been integrated into the main screen. Siply push 42 | the button labeled "Show configuration". 43 | 44 | There are three tabs. In the first one, you can assign actions to the offscreen 45 | areas. In the second one, you can manage the wiimote devices. And in the third 46 | one, you'll find some more options. 47 | 48 | 49 | Calibration 50 | ----------- 51 | 52 | The calibration process can be done in two ways: 53 | 54 | - You can choose "fullscreen calibration": The application will change into a 55 | white fullscreen and will draw four crosses, that you have to mark clockwise. 56 | 57 | - If you don't mark the "fullscreen" check in the configuration dialog, the 58 | calibration process is done in the same way, but you have to point at the 59 | four corners of the SCREEN, in the same order as before: top-left, top-right, 60 | bottom-left, bottom-right. 61 | 62 | After that, you simply push "activate". 63 | 64 | In the calibration screen, you can use the UP/DOWN keys to actually move 65 | inwards the calibration points, making it easier to do the process. 66 | 67 | 68 | Translations 69 | ------------ 70 | 71 | If you want to contribute with some translations, make sure you have the proper 72 | tools installed on your system. In Ubuntu/Debian you'll need the following packages: 73 | 74 | - qt4-dev-tools 75 | - pyqt4-dev-tools 76 | 77 | (and all its dependencies, of course). 78 | 79 | Also, make sure that you have the latest version of the program (check it with git). 80 | 81 | To update a translation, go to the trans/ directory and execute the following: 82 | 83 | $ linguist pywhiteboard_whatever.ts 84 | 85 | And supply the translations. 86 | 87 | If you want to contribute a fresh, new translation for an unsupported language, 88 | you first have to generate the .ts file (look at the "generate_trans.sh". 89 | 90 | Have fun! 91 | 92 | 93 | -------------------------------------------------------------------------------- /stuff/pbar.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 319 10 | 144 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 27 | 16777215 28 | 16777215 29 | 30 | 31 | 32 | Progress... 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 41 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 42 | p, li { white-space: pre-wrap; } 43 | </style></head><body style=" font-family:'Sans'; font-size:10pt; font-weight:400; font-style:normal;"> 44 | <p align="center" style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Press Buttons 1 and 2 on your Wiimote</p></body></html> 45 | 46 | 47 | 48 | 49 | 50 | 51 | 0 52 | 53 | 54 | -1 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | true 66 | 67 | 68 | Choose 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | Qt::Horizontal 82 | 83 | 84 | 85 | 40 86 | 20 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | Cancel 95 | 96 | 97 | 98 | 99 | 100 | 101 | Qt::Horizontal 102 | 103 | 104 | 105 | 40 106 | 20 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /debian/manpage.xml: -------------------------------------------------------------------------------- 1 | 2 | .
will be generated. You may view the 15 | manual page with: nroff -man .
| less'. A typical entry 16 | in a Makefile or Makefile.am is: 17 | 18 | DB2MAN = /usr/share/sgml/docbook/stylesheet/xsl/docbook-xsl/manpages/docbook.xsl 19 | XP = xsltproc -''-nonet -''-param man.charmap.use.subset "0" 20 | 21 | manpage.1: manpage.xml 22 | $(XP) $(DB2MAN) $< 23 | 24 | The xsltproc binary is found in the xsltproc package. The XSL files are in 25 | docbook-xsl. A description of the parameters you can use can be found in the 26 | docbook-xsl-doc-* packages. Please remember that if you create the nroff 27 | version in one of the debian/rules file targets (such as build), you will need 28 | to include xsltproc and docbook-xsl in your Build-Depends control field. 29 | Alternatively use the xmlto command/package. That will also automatically 30 | pull in xsltproc and docbook-xsl. 31 | 32 | Notes for using docbook2x: docbook2x-man does not automatically create the 33 | AUTHOR(S) and COPYRIGHT sections. In this case, please add them manually as 34 | ... . 35 | 36 | To disable the automatic creation of the AUTHOR(S) and COPYRIGHT sections 37 | read /usr/share/doc/docbook-xsl/doc/manpages/authors.html. This file can be 38 | found in the docbook-xsl-doc-html package. 39 | 40 | Validation can be done using: `xmllint -''-noout -''-valid manpage.xml` 41 | 42 | General documentation about man-pages and man-page-formatting: 43 | man(1), man(7), http://www.tldp.org/HOWTO/Man-Page/ 44 | 45 | --> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 59 | 60 | 61 | 62 | ]> 63 | 64 | 65 | 66 | &dhtitle; 67 | &dhpackage; 68 | 69 | 70 | &dhfirstname; 71 | &dhsurname; 72 | Wrote this manpage for the Debian system. 73 |
74 | &dhemail; 75 |
76 |
77 |
78 | 79 | 2011 80 | &dhusername; 81 | 82 | 83 | Permission is granted to copy, distribute and/or modify this 84 | document under the terms of the GNU General Public License, 85 | Version 2 or (at your option) any later version published by 86 | the Free Software Foundation. 87 | On Debian systems, the complete text of the GNU General Public 88 | License can be found in 89 | /usr/share/common-licenses/GPL. 90 | 91 |
92 | 93 | &dhucpackage; 94 | &dhsection; 95 | 96 | 97 | &dhpackage; 98 | Make your own electronic whiteboard 99 | 100 | 101 | 102 | &dhpackage; 103 | 104 | 105 | 106 | DESCRIPTION 107 | &dhpackage; is a program that lets you 108 | build your own 109 | electronic whiteboard. You only need a wii remote and a IR pen 110 | 111 | 112 |
113 | 114 | -------------------------------------------------------------------------------- /stuff/wiimote.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys, re 5 | 6 | import PyQt5.Qt as qt 7 | 8 | from numpy import matrix, linalg 9 | import bluetooth 10 | 11 | import linuxWiimoteLib as wiLib 12 | from configuration import Configuration 13 | from threads import CreateThreadClass 14 | import threading 15 | 16 | 17 | def old_div(a, b): 18 | if isinstance(a, int) and isinstance(b, int): 19 | return a // b 20 | else: 21 | return a / b 22 | 23 | def calculateArea(points): 24 | print(points) 25 | p1 = points[0] 26 | p2 = points[1] 27 | p3 = points[2] 28 | p4 = points[3] 29 | 30 | vb = [p2[0]-p1[0], p2[1]-p1[1]] 31 | va = [p4[0]-p1[0], p4[1]-p1[1]] 32 | 33 | paral_A_area = va[0]*vb[1] - va[1]*vb[0] 34 | 35 | va = [p2[0]-p3[0], p2[1]-p3[1]] 36 | vb = [p4[0]-p3[0], p4[1]-p3[1]] 37 | 38 | paral_B_area = va[0]*vb[1] - va[1]*vb[0] 39 | 40 | result = old_div(float(paral_A_area),2) + old_div(float(paral_B_area),2) 41 | print(paral_A_area) 42 | print(paral_B_area) 43 | return result 44 | 45 | 46 | 47 | 48 | 49 | 50 | class Wiimote(object): 51 | CALIBRATED, NONCALIBRATED = list(range(2)) 52 | MAX_X = 1023 53 | MAX_Y = 768 54 | 55 | def __init__(self): 56 | self.wii = None 57 | self.error = False 58 | self.pos = None 59 | self.state = Wiimote.NONCALIBRATED 60 | self.calibrationPoints = [] 61 | self.screenPoints = [] 62 | self.utilization = 0.0 63 | self.funcIR = None 64 | self.funcBTN = None 65 | self.enableCallback = False 66 | self.lock = threading.Lock() 67 | 68 | 69 | def create_wiimote_callback(self): 70 | # Closure 71 | def wiimote_callback(px,py): 72 | self.lock.acquire() 73 | if self.enableCallback and self.funcIR is not None: 74 | self.lock.release() 75 | self.funcIR(self.getPos([px,py])) 76 | else: 77 | self.lock.release() 78 | 79 | return wiimote_callback 80 | 81 | 82 | def putCallbackIR(self,funcIR): 83 | self.funcIR = funcIR 84 | 85 | def putCallbackBTN(self,funcBTN): 86 | self.funcBTN = funcBTN 87 | 88 | 89 | def detectWiimotes(self): 90 | try: 91 | self.wiimotesDetected = [] 92 | devices = bluetooth.discover_devices(duration=10, lookup_names=True) 93 | for device in devices: 94 | if re.match('.*nintendo.*', device[1].lower()): 95 | self.wiimotesDetected.append(device) 96 | return 97 | 98 | except bluetooth.BluetoothError as errString: 99 | self.wii = None 100 | self.error = True 101 | return 102 | 103 | 104 | def bind(self, device): 105 | try: 106 | self.addr = str(device[0]) 107 | self.wii = wiLib.Wiimote() 108 | self.wii.Connect(device) 109 | self.wii.SetRumble(True) 110 | qt.QThread.msleep(200) 111 | self.wii.SetRumble(False) 112 | self.wii.setIRCallBack(self.create_wiimote_callback()) 113 | self.wii.activate_IR(int(Configuration().getValueStr("sensitivity"))) 114 | self.wii.SetLEDs(True, False, False, False) 115 | self.error = False 116 | return 117 | 118 | except RuntimeError as errString: 119 | self.wii = None 120 | return 121 | 122 | except: 123 | self.wii = None 124 | self.error = True 125 | print("Unexpected error:", sys.exc_info()[0]) 126 | raise 127 | 128 | def isConnected(self): 129 | if self.wii: return True 130 | return False 131 | 132 | def enable(self): 133 | self.lock.acquire() 134 | self.error = False 135 | self.wii.setIRSensitivity(int(Configuration().getValueStr("sensitivity"))) 136 | self.enableCallback = True 137 | self.lock.release() 138 | 139 | def disable(self): 140 | self.lock.acquire() 141 | self.enableCallback = False 142 | self.lock.release() 143 | 144 | def close(self): 145 | self.disable() 146 | self.wii.Disconnect() 147 | self.wii = None 148 | 149 | def checkStatus(self): 150 | return True 151 | #if self.wii == None or self.error == True: return False 152 | #try: 153 | #self.wii.request_status() 154 | #return True 155 | #except: 156 | #return False 157 | 158 | def battery(self): 159 | return self.wii.WiimoteState.Battery 160 | 161 | def getPos(self,p): 162 | if self.state == Wiimote.NONCALIBRATED: 163 | return p 164 | if self.state == Wiimote.CALIBRATED: 165 | return [ 166 | old_div((self.h11*p[0] + self.h12*p[1] + self.h13), \ 167 | (self.h31*p[0] + self.h32*p[1] + 1)), 168 | old_div((self.h21*p[0] + self.h22*p[1] + self.h23), \ 169 | (self.h31*p[0] + self.h32*p[1] + 1)) ] 170 | 171 | def calibrate(self, p_screen, p_wii): 172 | l = [] 173 | for i in range(0,4): 174 | l.append( [p_wii[i][0], p_wii[i][1], 1, 0, 0, 0, 175 | (-p_screen[i][0] * p_wii[i][0]), 176 | (-p_screen[i][0] * p_wii[i][1])] ) 177 | l.append( [0, 0, 0, p_wii[i][0], p_wii[i][1], 1, 178 | (-p_screen[i][1] * p_wii[i][0]), 179 | (-p_screen[i][1] * p_wii[i][1])] ) 180 | 181 | A = matrix(l) 182 | 183 | x = matrix( [ 184 | [p_screen[0][0]], 185 | [p_screen[0][1]], 186 | [p_screen[1][0]], 187 | [p_screen[1][1]], 188 | [p_screen[2][0]], 189 | [p_screen[2][1]], 190 | [p_screen[3][0]], 191 | [p_screen[3][1]], 192 | ]) 193 | 194 | self.hCoefs = linalg.solve(A, x) 195 | self.h11 = self.hCoefs[0] 196 | self.h12 = self.hCoefs[1] 197 | self.h13 = self.hCoefs[2] 198 | self.h21 = self.hCoefs[3] 199 | self.h22 = self.hCoefs[4] 200 | self.h23 = self.hCoefs[5] 201 | self.h31 = self.hCoefs[6] 202 | self.h32 = self.hCoefs[7] 203 | 204 | self.calibrationPoints = list(p_wii) 205 | self.screenPoints = list(p_screen) 206 | self.state = Wiimote.CALIBRATED 207 | 208 | area_inside = calculateArea(self.calibrationPoints) 209 | total_area = Wiimote.MAX_X * Wiimote.MAX_Y 210 | self.utilization = old_div(float(area_inside),float(total_area)) 211 | 212 | 213 | def createConnectThread(self, selectedmac, pool): 214 | def func(): 215 | if selectedmac == '*': 216 | self.detectWiimotes() 217 | if len(self.wiimotesDetected) == 0: 218 | return 219 | 220 | for p in self.wiimotesDetected: 221 | if not p[0] in pool: 222 | pool.append(p[0]) 223 | 224 | else: 225 | mac = str(selectedmac) 226 | name = bluetooth.lookup_name(mac) 227 | self.bind([mac,name]) 228 | 229 | thread = CreateThreadClass(func) 230 | return thread() 231 | 232 | 233 | 234 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | python-whiteboard (1.0.5) unstable; urgency=low 2 | 3 | * Patch by Pietro Pilolli. Fixed selecting fixed wiimote. 4 | 5 | -- Pere Negre Fri, 25 Oct 2013 22:24:02 +0200 6 | 7 | python-whiteboard (1.0.4) unstable; urgency=low 8 | 9 | * Patch by Pietro Pilolli. Fixed problem with old wiimote model. 10 | 11 | -- Pere Negre Sun, 03 Mar 2013 21:38:16 +0100 12 | 13 | python-whiteboard (1.0.3) unstable; urgency=low 14 | 15 | * Updated italian translation. 16 | 17 | -- pnegre Mon, 12 Nov 2012 23:58:28 +0100 18 | 19 | python-whiteboard (1.0.2) unstable; urgency=low 20 | 21 | * Fixed compatibility issues with wiimote plus. 22 | 23 | -- pnegre Fri, 07 Sep 2012 14:55:27 +0200 24 | 25 | python-whiteboard (1.0.1) unstable; urgency=low 26 | 27 | * Interaction with wiimote device using linuxWiimoteLib (from Stephane Duchesneau) 28 | * cwiid is no longer a requirement. 29 | 30 | -- pnegre Fri, 07 Sep 2012 14:55:27 +0200 31 | 32 | python-whiteboard (1.0.0) unstable; urgency=low 33 | 34 | * Added patch by Pietro Pilolli: enhanced sensitivity detection 35 | * Disabled systray icon because of Unity (Ubuntu) incompatibility 36 | 37 | -- pnegre Sat, 28 Jul 2012 13:04:06 +0200 38 | 39 | python-whiteboard (0.9.9) unstable; urgency=low 40 | 41 | * Added code to detect to prevent multiple instance running 42 | * Minor fixes 43 | 44 | -- pnegre Sat, 08 Oct 2011 15:02:22 +0200 45 | 46 | python-whiteboard (0.9.8) unstable; urgency=low 47 | 48 | * Added option to pick the first wiimote available when connecting. 49 | 50 | -- pnegre Sat, 11 Jun 2011 10:54:52 +0200 51 | 52 | python-whiteboard (0.9.7) unstable; urgency=low 53 | 54 | * Applied debian patches (thanks to George Khaznadar) 55 | * Long click 56 | 57 | -- pnegre Tue, 07 Jun 2011 23:15:59 +0200 58 | 59 | python-whiteboard (0.9.6+git20110528-3) unstable; urgency=low 60 | 61 | * added a French localization 62 | 63 | -- Georges Khaznadar Sun, 29 May 2011 20:38:52 +0200 64 | 65 | python-whiteboard (0.9.6+git20110528-2) unstable; urgency=low 66 | 67 | * updated the name and the address of the author 68 | 69 | -- Georges Khaznadar Sun, 29 May 2011 00:30:24 +0200 70 | 71 | python-whiteboard (0.9.6+git20110528-1) unstable; urgency=low 72 | 73 | * Initial release (Closes: #628434) 74 | * added build-dependencies: quilt, python-all, libqt4-dev 75 | * moved stuff to /usr/share 76 | * written a short manpage 77 | 78 | -- Georges Khaznadar Sat, 28 May 2011 23:18:08 +0200 79 | 80 | python-whiteboard (0.9.6) unstable; urgency=low 81 | 82 | * Fixed cursor 83 | 84 | -- pnegre Sun, 06 Mar 2011 00:30:33 +0100 85 | 86 | python-whiteboard (0.9.5) unstable; urgency=low 87 | 88 | * Some minor fixes 89 | * Connection code changed a bit 90 | 91 | -- pnegre Sat, 19 Feb 2011 14:58:09 +0100 92 | 93 | python-whiteboard (0.9.4) unstable; urgency=low 94 | 95 | * Added profiles feature 96 | * Added chinese translation 97 | 98 | -- pnegre Mon, 06 Sep 2010 15:49:06 +0200 99 | 100 | python-whiteboard (0.9.3) unstable; urgency=low 101 | 102 | * Minor fixes 103 | 104 | -- pnegre Thu, 12 Aug 2010 11:25:04 +0200 105 | 106 | python-whiteboard (0.9.2) unstable; urgency=low 107 | 108 | * The program is capable to detect more than one wiimote device 109 | * The program allows the user to select the wiimote 110 | * Pushing "A" on the wiimote device triggers calibration window 111 | * New option to load automatically the saved calibration matrix on startup 112 | 113 | -- pnegre Wed, 11 Aug 2010 17:21:40 +0200 114 | 115 | python-whiteboard (0.9.1) unstable; urgency=low 116 | 117 | * Minor fixes. 118 | 119 | -- pnegre Tue, 10 Aug 2010 18:43:23 +0200 120 | 121 | python-whiteboard (0.9) unstable; urgency=low 122 | 123 | * Revamped ui 124 | 125 | -- pnegre Tue, 10 Aug 2010 14:21:30 +0200 126 | 127 | python-whiteboard (0.8) unstable; urgency=low 128 | 129 | * Small fixes 130 | 131 | -- pnegre Tue, 27 Jul 2010 23:39:52 +0200 132 | 133 | python-whiteboard (0.7) unstable; urgency=low 134 | 135 | * Big improvements in respect to cpu usage 136 | * Switched from polling interface to a callback interface for the wiimote messages 137 | * Changes in configuration screen 138 | * Added internationalization support. Italian translation included 139 | 140 | -- pnegre Tue, 27 Jul 2010 22:57:43 +0200 141 | 142 | python-whiteboard (0.6) unstable; urgency=low 143 | 144 | * Removed toggle configuration from main window 145 | * Added autoconnect capability 146 | 147 | -- pnegre Sun, 25 Jul 2010 11:10:49 +0200 148 | 149 | python-whiteboard (0.5) unstable; urgency=low 150 | 151 | * Fullscreen calibration working properly on gnome desktop (i think) 152 | * New configuration dialog 153 | * The user can restrict the bluetooth connection to certain devices 154 | 155 | -- pnegre Mon, 19 Jul 2010 23:25:34 +0200 156 | 157 | python-whiteboard (0.4) unstable; urgency=low 158 | 159 | * Enhancements in calibration screen 160 | 161 | -- pnegre Sat, 03 Jul 2010 12:20:15 +0200 162 | 163 | python-whiteboard (0.3) unstable; urgency=low 164 | 165 | * It is possible now to calibrate without going fullscreen 166 | 167 | -- pnegre Fri, 21 May 2010 10:23:09 +0200 168 | 169 | python-whiteboard (0.2-1) unstable; urgency=low 170 | 171 | * Minor fix in calibration window 172 | 173 | -- pnegre Thu, 13 May 2010 08:54:07 +0200 174 | 175 | python-whiteboard (0.1-2.10) unstable; urgency=low 176 | 177 | * Added loading calibration capability. 178 | 179 | -- pnegre Wed, 05 May 2010 19:56:44 +0200 180 | 181 | python-whiteboard (0.1-2.9) unstable; urgency=low 182 | 183 | * Improved calibration screen 184 | * Added message on pbar screen 185 | 186 | -- pnegre Wed, 05 May 2010 09:45:12 +0200 187 | 188 | python-whiteboard (0.1-2.8) unstable; urgency=low 189 | 190 | * Off-limit clicks working. 191 | 192 | -- pnegre Tue, 04 May 2010 12:13:52 +0200 193 | 194 | python-whiteboard (0.1-2.7) unstable; urgency=low 195 | 196 | * Added systray Icon. Minor fixes. 197 | 198 | -- pnegre Tue, 04 May 2010 08:58:24 +0200 199 | 200 | python-whiteboard (0.1-2.6) unstable; urgency=low 201 | 202 | * Calibration screen does not use pygame, dropped dependency 203 | 204 | -- pnegre Mon, 03 May 2010 13:42:00 +0200 205 | 206 | python-whiteboard (0.1-2.5) unstable; urgency=low 207 | 208 | * Checked dependencies 209 | 210 | -- pnegre Fri, 30 Apr 2010 20:16:19 +0200 211 | 212 | python-whiteboard (0.1-2.4) unstable; urgency=low 213 | 214 | * Minor fixes 215 | 216 | -- pnegre Thu, 29 Apr 2010 12:06:38 +0200 217 | 218 | python-whiteboard (0.1-2.3) unstable; urgency=low 219 | 220 | * Minor fixes 221 | 222 | -- pnegre Thu, 29 Apr 2010 08:34:10 +0200 223 | 224 | python-whiteboard (0.1-2.2) unstable; urgency=low 225 | 226 | * Added Desktop entry 227 | 228 | -- pnegre Wed, 28 Apr 2010 14:02:25 +0200 229 | 230 | python-whiteboard (0.1-2.1) unstable; urgency=low 231 | 232 | * Added Icon 233 | 234 | -- pnegre Wed, 28 Apr 2010 12:58:52 +0200 235 | 236 | python-whiteboard (0.1-2) unstable; urgency=low 237 | 238 | * Checked more dependencies 239 | 240 | -- pnegre Wed, 28 Apr 2010 12:12:25 +0200 241 | 242 | python-whiteboard (0.1-1.1) unstable; urgency=low 243 | 244 | * Checked dependencies. 245 | 246 | -- pnegre Wed, 28 Apr 2010 12:00:34 +0200 247 | 248 | python-whiteboard (0.1-1) unstable; urgency=low 249 | 250 | * First release. 251 | 252 | -- pnegre Wed, 28 Apr 2010 10:13:19 +0200 253 | -------------------------------------------------------------------------------- /stuff/cursor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from builtins import zip 4 | from builtins import range 5 | from builtins import object 6 | import Xlib.display 7 | import Xlib.ext.xtest 8 | import time 9 | 10 | import PyQt5.Qt as qt 11 | 12 | from configuration import Configuration 13 | 14 | 15 | def clock(): 16 | return int(time.time()*1000) 17 | 18 | 19 | class Filter(object): 20 | def __init__(self): 21 | self.data = [] 22 | conf = Configuration() 23 | self.limit = int(conf.getValueStr("smoothing")) 24 | 25 | def update(self,p): 26 | self.data.append(p) 27 | if len(self.data) > self.limit: 28 | self.data.pop(0) 29 | 30 | n = len(self.data) 31 | res = [0,0] 32 | for p in self.data: 33 | res[0] += p[0] 34 | res[1] += p[1] 35 | res[0] /= n 36 | res[1] /= n 37 | return res 38 | 39 | class Click(object): 40 | UP_TIMEOUT = 100 41 | 42 | def __init__(self,cursor): 43 | self.initialTime = clock() 44 | self.cursor = cursor 45 | self.cursor.mouse_up() 46 | self.cursor.mouse_down() 47 | 48 | # Called when needing to update without IR Data 49 | def updateWithoutData(self): 50 | if (clock() - self.initialTime) > Click.UP_TIMEOUT: 51 | self.cursor.mouse_up() 52 | return False 53 | 54 | return True 55 | 56 | # Update event with IR Data 57 | def updateWithData(self): 58 | self.initialTime = clock() 59 | 60 | 61 | class FakeCursor(object): 62 | LEFT_BUTTON = 1 63 | MIDDLE_BUTTON = 2 64 | RIGHT_BUTTON = 3 65 | ONLY_MOVE = 4 66 | RIGHT_CLICK_TIMEOUT = 750 67 | CLICK_TIMEOUT = 60 68 | ZONE1, ZONE2, ZONE3, ZONE4 = list(range(4)) 69 | 70 | def __init__(self,wii): 71 | self.display = Xlib.display.Display() 72 | self.screen = self.display.screen() 73 | self.wii = wii 74 | self.click = None 75 | self.filt = None 76 | self.clickType = FakeCursor.LEFT_BUTTON 77 | self.zones = {} 78 | self.mutex = qt.QMutex() 79 | self.mustFinish = False 80 | self.thread = None 81 | self.noClicks = False 82 | # Start of click - measure timeout for right click 83 | self.begin = None 84 | # Keepalive of IR signal 85 | self.lastdata = 0 86 | # Starts click (left or right) on timeout or move 87 | self.run = False 88 | # Delay to only move, before click 89 | self.toclick = False 90 | 91 | 92 | def setZone(self,zone,clickType): 93 | self.zones[zone] = clickType 94 | 95 | def setZones(self,actions): 96 | for z,a in zip((FakeCursor.ZONE1,FakeCursor.ZONE2,FakeCursor.ZONE3,FakeCursor.ZONE4),actions): 97 | if a == '2': 98 | self.setZone(z,FakeCursor.RIGHT_BUTTON) 99 | elif a == '0': 100 | self.setZone(z,FakeCursor.LEFT_BUTTON) 101 | elif a == '3': 102 | self.setZone(z,FakeCursor.MIDDLE_BUTTON) 103 | elif a == '1': 104 | self.setZone(z,FakeCursor.ONLY_MOVE) 105 | else: 106 | self.setZone(z,FakeCursor.LEFT_BUTTON) 107 | 108 | 109 | def move(self,pos): 110 | Xlib.ext.xtest.fake_input(self.display, Xlib.X.MotionNotify, x=int(pos[0]), y=int(pos[1])) 111 | self.display.sync() 112 | 113 | 114 | def mouse_down(self): 115 | if self.noClicks or self.clickType == FakeCursor.ONLY_MOVE: 116 | self.clickType = None 117 | return 118 | button = self.clickType 119 | Xlib.ext.xtest.fake_input(self.display, Xlib.X.ButtonPress, button) 120 | self.display.sync() 121 | 122 | 123 | def mouse_up(self): 124 | if self.clickType is None: 125 | return 126 | button = self.clickType 127 | Xlib.ext.xtest.fake_input(self.display, Xlib.X.ButtonRelease, button) 128 | self.display.sync() 129 | 130 | 131 | # Returns True if point is within screen limits 132 | def checkLimits(self,pos): 133 | if pos[1] < 0: 134 | # Zone 1 135 | self.clickType = self.zones[FakeCursor.ZONE1] 136 | return False 137 | if pos[0] > self.screen['width_in_pixels']: 138 | # Zone 2 139 | self.clickType = self.zones[FakeCursor.ZONE2] 140 | return False 141 | if pos[1] > self.screen['height_in_pixels']: 142 | # Zone 3 143 | self.clickType = self.zones[FakeCursor.ZONE3] 144 | return False 145 | if pos[0] < 0: 146 | # Zone 4 147 | self.clickType = self.zones[FakeCursor.ZONE4] 148 | return False 149 | 150 | return True 151 | 152 | 153 | # This function fabricates the callback function that is to be 154 | # passed to the wiimote object. It's called every time that IR data 155 | # is available. It is necessary to do it this way because the callback 156 | # has to be aware of the cursor object. 157 | def makeCallback(self): 158 | def callback(q): 159 | self.mutex.lock() 160 | if self.checkLimits(q): 161 | if not self.filt: 162 | self.filt = Filter() 163 | p = self.filt.update(q) 164 | 165 | self.move(p) 166 | 167 | if self.run: 168 | if not self.click: 169 | self.click = Click(self) 170 | else: 171 | self.click.updateWithData() 172 | else: 173 | if not self.begin: 174 | self.begin = clock() 175 | self.clkP = p 176 | else: 177 | self.lastdata = clock() 178 | # timeout for "only move" flash 179 | if clock() - self.begin > FakeCursor.CLICK_TIMEOUT: 180 | self.toclick = True 181 | 182 | # timeout for "right click" 183 | if clock() - self.begin > FakeCursor.RIGHT_CLICK_TIMEOUT: 184 | self.begin = None 185 | self.run = True 186 | self.clickType = FakeCursor.RIGHT_BUTTON 187 | else: 188 | dX = p[0]-self.clkP[0] 189 | dY = p[1]-self.clkP[1] 190 | # if the cursor moves, start click (drag) immediately 191 | if (dX*dX)+(dY*dY) > 16: 192 | self.begin = None 193 | self.run = True 194 | # Returns to the original point and clicks there 195 | self.move(self.clkP) 196 | self.click = Click(self) 197 | 198 | self.mutex.unlock() 199 | 200 | return callback 201 | 202 | 203 | # This function creates the cursor thread. It makes the callback to the 204 | # thread and enables the wiimote device 205 | def runThread(self): 206 | def runFunc(): 207 | while 1: 208 | qt.QThread.usleep(50) 209 | self.mutex.lock() 210 | # A click had occurred: a short burst (noot an impulse) with no movement 211 | if self.toclick and not self.run and clock() - self.lastdata > Click.UP_TIMEOUT: 212 | self.begin = None 213 | self.lastdata = None 214 | self.toclick = False 215 | self.run = True 216 | self.click = Click(self) 217 | 218 | if self.click and self.click.updateWithoutData() == False: 219 | self.click = None 220 | self.filt = None 221 | self.begin = None 222 | self.lastdata = None 223 | self.run = False 224 | self.toclick = False 225 | self.clickType = FakeCursor.LEFT_BUTTON 226 | if self.mustFinish: 227 | self.mutex.unlock() 228 | break 229 | self.mutex.unlock() 230 | 231 | from threads import CreateThreadClass 232 | 233 | self.mustFinish = False 234 | self.wii.disable() 235 | self.wii.putCallbackIR(self.makeCallback()) 236 | self.wii.enable() 237 | thread = CreateThreadClass(runFunc) 238 | self.thread = thread() 239 | self.thread.start() 240 | 241 | 242 | # Destroys the cursor thread and disables the wiimote device 243 | def finish(self): 244 | self.mutex.lock() 245 | self.mustFinish = True 246 | self.mutex.unlock() 247 | self.thread.wait() 248 | self.wii.disable() 249 | self.wii.putCallbackIR(None) 250 | self.thread = None 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /stuff/configuration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from builtins import str 4 | from builtins import range 5 | from builtins import object 6 | from PyQt5 import QtCore, QtGui, QtWidgets, uic 7 | import PyQt5.Qt as qt 8 | 9 | 10 | CONFIG_VERSION = 12 11 | 12 | 13 | class Configuration(object): 14 | 15 | class __impl(object): 16 | """ Implementation of the singleton interface """ 17 | 18 | def __init__(self): 19 | self.settings = QtCore.QSettings("pywhiteboard","pywhiteboard") 20 | 21 | self.defaults = { 22 | "fullscreen": "Yes", 23 | "selectedmac": '*', 24 | "zone1": "1", 25 | "zone2": "2", 26 | "zone3": "3", 27 | "zone4": "0", 28 | "autoconnect": "Yes", 29 | "autocalibration": "Yes", 30 | "sensitivity": "6", 31 | "smoothing": "10", 32 | "moveonly": "No", 33 | "automatrix": "No", 34 | "nowaitdevices": "Yes", 35 | } 36 | 37 | version = self.getValueStr("version") 38 | if version == '' or int(version) < CONFIG_VERSION: 39 | self.settings.clear() 40 | self.saveValue("version",str(CONFIG_VERSION)) 41 | 42 | self.activeGroup = None 43 | self.setGroup("default") 44 | 45 | 46 | def wipe(self): 47 | self.settings.clear() 48 | 49 | 50 | def saveValue(self,name,value): 51 | self.settings.setValue(name,QtCore.QVariant(value)) 52 | 53 | 54 | def getValueStr(self,name): 55 | v = self.settings.value(name) 56 | if v is None: 57 | v = '' 58 | v = str(v) 59 | if v != '': return v 60 | if v == '' and name in list(self.defaults.keys()): 61 | return self.defaults[name] 62 | else: return '' 63 | 64 | 65 | def writeArray(self,name,lst): 66 | self.settings.beginWriteArray(name) 67 | for i,dct in enumerate(lst): 68 | self.settings.setArrayIndex(i) 69 | for k in list(dct.keys()): 70 | self.settings.setValue(k,dct[k]) 71 | self.settings.endArray() 72 | 73 | 74 | def readArray(self,name): 75 | n = self.settings.beginReadArray(name) 76 | result = [] 77 | for i in range(0,n): 78 | self.settings.setArrayIndex(i) 79 | kys = self.settings.childKeys() 80 | d = dict() 81 | for k in kys: 82 | d[str(k)] = str(self.settings.value(k)) 83 | result.append(d) 84 | self.settings.endArray() 85 | return result 86 | 87 | 88 | def setGroup(self,name): 89 | if self.activeGroup: 90 | self.settings.endGroup() 91 | pastGroup = self.activeGroup 92 | self.activeGroup = name 93 | self.settings.beginGroup(name) 94 | return pastGroup 95 | 96 | 97 | ########### Get and set profile list ######################## 98 | def getProfileList(self): 99 | activeGroup = self.setGroup("default") 100 | result = [] 101 | n = self.settings.beginReadArray("profiles") 102 | for i in range(0,n): 103 | self.settings.setArrayIndex(i) 104 | result.append(str(self.settings.value('item').toString())) 105 | self.settings.endArray() 106 | self.setGroup(activeGroup) 107 | return result 108 | 109 | 110 | def setProfileList(self, profileList): 111 | activeGroup = self.setGroup("default") 112 | self.settings.beginWriteArray("profiles") 113 | for i, profileName in enumerate(profileList): 114 | self.settings.setArrayIndex(i) 115 | self.settings.setValue('item', profileName) 116 | self.settings.endArray() 117 | self.setGroup(activeGroup) 118 | ############################################################## 119 | 120 | 121 | # storage for the instance reference 122 | __instance = None 123 | 124 | def __init__(self): 125 | """ Create singleton instance """ 126 | # Check whether we already have an instance 127 | if Configuration.__instance is None: 128 | # Create and remember i10nstance 129 | Configuration.__instance = Configuration.__impl() 130 | 131 | # Store instance reference as the only member in the handle 132 | self.__dict__['_Configuration__instance'] = Configuration.__instance 133 | 134 | def __getattr__(self, attr): 135 | """ Delegate access to implementation """ 136 | return getattr(self.__instance, attr) 137 | 138 | def __setattr__(self, attr, value): 139 | """ Delegate access to implementation """ 140 | return setattr(self.__instance, attr, value) 141 | 142 | 143 | 144 | class ConfigDialog(QtWidgets.QDialog): 145 | 146 | def __init__(self, parent, wii=None): 147 | super(ConfigDialog, self).__init__(parent) 148 | self.ui = uic.loadUi("configuration.ui",self) 149 | 150 | self.wii = wii 151 | 152 | self.ui.check_fullscreen.stateChanged.connect(self.checkStateChanged) 153 | self.ui.check_autoconnect.stateChanged.connect(self.checkStateChanged) 154 | self.ui.check_autocalibration.stateChanged.connect(self.checkStateChanged) 155 | self.ui.check_automatrix.stateChanged.connect(self.checkStateChanged) 156 | self.ui.check_nowait.stateChanged.connect(self.checkStateChanged) 157 | 158 | self.ui.button_addDev.clicked.connect(self.addDevice) 159 | self.ui.button_remDev.clicked.connect(self.removeDevice) 160 | 161 | pixmap = QtGui.QPixmap("screen.png") 162 | self.areasScene = QtWidgets.QGraphicsScene() 163 | self.areasScene.addPixmap(pixmap) 164 | self.screenAreas.setScene(self.areasScene) 165 | self.screenAreas.show() 166 | 167 | self.ui.combo1.currentIndexChanged.connect(self.changeCombo) 168 | self.ui.combo2.currentIndexChanged.connect(self.changeCombo) 169 | self.ui.combo3.currentIndexChanged.connect(self.changeCombo) 170 | self.ui.combo4.currentIndexChanged.connect(self.changeCombo) 171 | self.updateCombos() 172 | 173 | self.ui.slider_ir.setMinimum(1) 174 | self.ui.slider_ir.setMaximum(6) 175 | self.ui.slider_ir.valueChanged.connect(self.sliderIrMoved) 176 | 177 | self.ui.slider_smoothing.setMinimum(1) 178 | self.ui.slider_smoothing.setMaximum(10) 179 | self.ui.slider_smoothing.valueChanged.connect(self.sliderSmMoved) 180 | 181 | self.refreshWidgets() 182 | self.checkButtons() 183 | 184 | 185 | 186 | def refreshWidgets(self): 187 | conf = Configuration() 188 | self.ui.check_fullscreen.setChecked(conf.getValueStr("fullscreen") == "Yes") 189 | self.ui.check_autoconnect.setChecked(conf.getValueStr("autoconnect") == "Yes") 190 | self.ui.check_autocalibration.setChecked(conf.getValueStr("autocalibration") == "Yes") 191 | self.ui.check_automatrix.setChecked(conf.getValueStr("automatrix") == "Yes") 192 | self.ui.check_nowait.setChecked(conf.getValueStr("nowaitdevices") == "Yes") 193 | self.updateCombos() 194 | self.setupMacTable() 195 | 196 | sens = int(conf.getValueStr("sensitivity")) 197 | self.ui.slider_ir.setValue(sens) 198 | smth = int(conf.getValueStr("smoothing")) 199 | self.ui.slider_smoothing.setValue(smth) 200 | 201 | 202 | 203 | def checkButtons(self): 204 | if self.wii == None: 205 | self.ui.button_addDev.setEnabled(False) 206 | else: 207 | self.ui.button_addDev.setEnabled(True) 208 | 209 | 210 | 211 | def setupMacTable(self): 212 | self.ui.tableMac.setColumnCount(2) 213 | self.ui.tableMac.setHorizontalHeaderLabels([self.tr('Address'), self.tr('Comment')]) 214 | self.ui.tableMac.setSelectionMode(QtWidgets.QTableWidget.SingleSelection) 215 | self.ui.tableMac.setSelectionBehavior(QtWidgets.QTableWidget.SelectRows) 216 | self.refreshMacTable() 217 | header = self.ui.tableMac.horizontalHeader() 218 | header.setStretchLastSection(True) 219 | self.ui.tableMac.cellClicked.connect(self.macTableCellSelected) 220 | 221 | 222 | def macTableCellSelected(self,r,c): 223 | address = str(self.ui.tableMac.item(r,0).text()) 224 | conf = Configuration() 225 | conf.saveValue('selectedmac',address) 226 | 227 | 228 | def refreshMacTable(self): 229 | while self.ui.tableMac.item(0,0): 230 | self.ui.tableMac.removeRow(0) 231 | 232 | self.ui.tableMac.insertRow(0) 233 | item = QtWidgets.QTableWidgetItem('*') 234 | self.ui.tableMac.setItem(0,0,item) 235 | item = QtWidgets.QTableWidgetItem(self.tr('All devices')) 236 | self.ui.tableMac.setItem(0,1,item) 237 | self.ui.tableMac.selectRow(0) 238 | conf = Configuration() 239 | lst = conf.readArray('maclist') 240 | for elem in lst: 241 | rc = self.ui.tableMac.rowCount() 242 | self.ui.tableMac.insertRow(rc) 243 | item = QtWidgets.QTableWidgetItem(elem['address']) 244 | self.ui.tableMac.setItem(rc,0,item) 245 | item = QtWidgets.QTableWidgetItem(elem['comment']) 246 | self.ui.tableMac.setItem(rc,1,item) 247 | selected = conf.getValueStr('selectedmac') 248 | if selected == elem['address']: 249 | self.ui.tableMac.selectRow(rc) 250 | 251 | 252 | 253 | 254 | def addDevice(self): 255 | if self.wii == None: return 256 | conf = Configuration() 257 | d = conf.readArray('maclist') 258 | address = self.wii.addr 259 | for item in d: 260 | if item['address'] == address: return 261 | 262 | comment, ok = QtWidgets.QInputDialog.getText(self, 263 | self.tr("Comment"), self.tr('Wii device description')) 264 | 265 | if ok: 266 | d.append( {'address': address, 'comment': comment} ) 267 | conf.writeArray('maclist',d) 268 | self.refreshMacTable() 269 | 270 | 271 | def removeDevice(self): 272 | conf = Configuration() 273 | mlist = conf.readArray('maclist') 274 | for it in self.ui.tableMac.selectedItems(): 275 | if it.column() == 0: 276 | address = it.text() 277 | mlist = [ elem for elem in mlist if elem['address'] != address ] 278 | conf.writeArray('maclist',mlist) 279 | self.refreshMacTable() 280 | conf.saveValue('selectedmac','*') 281 | return 282 | 283 | 284 | def sliderSmMoved(self,val): 285 | conf = Configuration() 286 | conf.saveValue("smoothing",str(val)) 287 | self.ui.label_smoothing.setText(self.tr("Smoothing: ") + str(val)) 288 | 289 | 290 | def sliderIrMoved(self, val): 291 | conf = Configuration() 292 | conf.saveValue("sensitivity",str(val)) 293 | self.ui.label_sensitivity.setText(self.tr("IR Sensitivity: ") + str(val)) 294 | 295 | 296 | def finish(self): 297 | self.close() 298 | 299 | 300 | def updateCombos(self): 301 | conf = Configuration() 302 | for combo,zone in [(self.ui.combo1,"zone1"), (self.ui.combo2,"zone2"), (self.ui.combo3,"zone3"), (self.ui.combo4,"zone4")]: 303 | ind = int(conf.getValueStr(zone)) 304 | combo.setCurrentIndex(ind) 305 | 306 | def changeCombo(self,i): 307 | sender = self.sender() 308 | conf = Configuration() 309 | if sender == self.ui.combo1: 310 | conf.saveValue("zone1",str(i)) 311 | elif sender == self.ui.combo2: 312 | conf.saveValue("zone2",str(i)) 313 | elif sender == self.ui.combo3: 314 | conf.saveValue("zone3",str(i)) 315 | elif sender == self.ui.combo4: 316 | conf.saveValue("zone4",str(i)) 317 | 318 | def checkStateChanged(self,i): 319 | yesno = 'Yes' 320 | if i == 0: yesno = 'No' 321 | sender = self.sender() 322 | conf = Configuration() 323 | if sender == self.ui.check_fullscreen: 324 | conf.saveValue('fullscreen',yesno) 325 | if sender == self.ui.check_autoconnect: 326 | conf.saveValue('autoconnect',yesno) 327 | if sender == self.ui.check_autocalibration: 328 | conf.saveValue('autocalibration',yesno) 329 | if sender == self.ui.check_automatrix: 330 | conf.saveValue('automatrix',yesno) 331 | if sender == self.ui.check_nowait: 332 | conf.saveValue('nowaitdevices',yesno) 333 | 334 | 335 | def closeEvent(self,e): 336 | e.accept() 337 | 338 | 339 | 340 | -------------------------------------------------------------------------------- /stuff/calibration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys,time 4 | 5 | import wiimote 6 | 7 | from PyQt5 import QtCore, QtGui, QtWidgets, uic 8 | import PyQt5.Qt as qt 9 | 10 | from configuration import Configuration 11 | 12 | 13 | class CalibrationAbort(Exception): 14 | pass 15 | 16 | def old_div(a, b): 17 | if isinstance(a, int) and isinstance(b, int): 18 | return a // b 19 | else: 20 | return a / b 21 | 22 | def clock(): 23 | return int(time.time()*1000) 24 | 25 | 26 | class SandClock(object): 27 | READY, FIN1, FIN2 = list(range(3)) 28 | 29 | def __init__(self,scene,px,py,radius=30): 30 | self.scene = scene 31 | self.radius = radius 32 | self.elipse = None 33 | self.circle = None 34 | self.setCenter(px,py) 35 | self.initialize() 36 | 37 | 38 | def setCenter(self,x,y): 39 | if self.elipse: 40 | self.scene.removeItem(self.elipse) 41 | self.scene.removeItem(self.circle) 42 | 43 | self.elipse = self.scene.addEllipse(x-old_div(self.radius,2), y-old_div(self.radius,2), self.radius, self.radius, 44 | qt.QPen(QtCore.Qt.red, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin), 45 | qt.QBrush(QtCore.Qt.red)) 46 | self.circle = self.scene.addEllipse(x-old_div(self.radius,2), y-old_div(self.radius,2), self.radius, self.radius, 47 | qt.QPen(QtCore.Qt.black, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)) 48 | self.elipse.setVisible(False) 49 | self.circle.setVisible(True) 50 | self.scene.update() 51 | 52 | 53 | def initialize(self): 54 | self.totalTicks = 0 55 | self.lastTick = 0 56 | self.state = SandClock.READY 57 | 58 | def update(self,p): 59 | t = clock() 60 | delta = 0 61 | if self.lastTick != 0: delta = t - self.lastTick 62 | if p == None: 63 | if delta > 100: 64 | if self.state == SandClock.FIN1: 65 | self.state = SandClock.FIN2 66 | if delta > 250: 67 | self.initialize() 68 | return 69 | self.point = list(p) 70 | self.lastTick = t 71 | if self.totalTicks < 700: 72 | self.totalTicks += delta 73 | else: 74 | self.state = SandClock.FIN1 75 | 76 | def draw(self): 77 | if self.totalTicks: 78 | self.elipse.setVisible(True) 79 | dgrs = 5760*self.totalTicks/700 80 | self.elipse.setSpanAngle(dgrs) 81 | self.scene.update() 82 | else: 83 | self.elipse.setVisible(False) 84 | self.scene.update() 85 | 86 | def finished(self): 87 | return self.state == SandClock.FIN2 88 | 89 | def getPoint(self): 90 | return self.point 91 | 92 | 93 | 94 | class SmallScreen(object): 95 | def __init__(self,scx,scy,scene): 96 | self.scene = scene 97 | self.parentx = scx 98 | self.parenty = scy 99 | self.dx = 200 100 | self.dy = 200 101 | self.square = scene.addRect(qt.QRectF(old_div(scx,2)-100,old_div(scy,2)-100,200,200)) 102 | self.point = scene.addRect(qt.QRectF(old_div(self.parentx,2)-2,old_div(self.parenty,2)-2,4,4)) 103 | def drawPoint(self,pos): 104 | px = -100 + pos[0]*self.dx/wiimote.Wiimote.MAX_X-2 105 | py = 100 - pos[1]*self.dy/wiimote.Wiimote.MAX_Y-2 106 | self.point.setPos(px,py) 107 | 108 | 109 | 110 | def crossPoly(x,y): 111 | pol = QtGui.QPolygonF() 112 | pol.append(qt.QPointF(x,y)) 113 | pol.append(qt.QPointF(x-5,y-5)) 114 | pol.append(qt.QPointF(x,y)) 115 | pol.append(qt.QPointF(x+5,y+5)) 116 | pol.append(qt.QPointF(x,y)) 117 | pol.append(qt.QPointF(x-5,y+5)) 118 | pol.append(qt.QPointF(x,y)) 119 | pol.append(qt.QPointF(x+5,y-5)) 120 | return pol 121 | 122 | 123 | 124 | class CalibrateDialog2(QtWidgets.QDialog): 125 | wiiCallback = QtCore.pyqtSignal(int, int) 126 | 127 | def __init__(self,parent,wii): 128 | QtWidgets.QWidget.__init__(self,parent) 129 | self.wii = wii 130 | self.ui = uic.loadUi("calibration2.ui",self) 131 | 132 | screenGeom = QtWidgets.QDesktopWidget().screenGeometry() 133 | wdt = screenGeom.width()-2 134 | hgt = screenGeom.height()-2 135 | 136 | viewport = [ self.ui.graphicsView.maximumViewportSize().width(), 137 | self.ui.graphicsView.maximumViewportSize().height() 138 | ] 139 | 140 | self.scene = qt.QGraphicsScene() 141 | self.scene.setSceneRect(0,0, viewport[0], viewport[1]) 142 | self.ui.graphicsView.setScene(self.scene) 143 | 144 | self.smallScreen = SmallScreen(viewport[0], viewport[1], self.scene) 145 | self.sandclock = SandClock(self.scene,old_div(viewport[0],2),old_div(viewport[1],2)) 146 | 147 | self.CalibrationPoints = [ 148 | [0,0], [wdt,0], [wdt,hgt], [0,hgt] 149 | ] 150 | self.wiiPoints = [] 151 | self.textMessages = [ 152 | self.tr("TOP-LEFT"), 153 | self.tr("TOP-RIGHT"), 154 | self.tr("BOTTOM-RIGHT"), 155 | self.tr("BOTTOM-LEFT") ] 156 | 157 | self.ui.label.setText(self.textMessages.pop(0)) 158 | 159 | self.ui.but_cancel.clicked.connect(self.close) 160 | 161 | self.shcut1 = QtWidgets.QShortcut(self) 162 | self.shcut1.setKey("Esc") 163 | self.shcut1.activated.connect(self.close) 164 | 165 | self.mutex = qt.QMutex() 166 | 167 | self.wii.disable() 168 | self.wii.putCallbackIR(self.makeWiiCallback()) 169 | self.wii.enable() 170 | 171 | self.timer = qt.QTimer(self) 172 | self.timer.setInterval(70) 173 | self.timer.timeout.connect(self.doWork) 174 | self.timer.start() 175 | 176 | self.wiiCallback.connect(self._wiiCallback) 177 | 178 | @QtCore.pyqtSlot(int, int) 179 | def _wiiCallback(self, pos0, pos1): 180 | pos = [pos0, pos1] 181 | self.mutex.lock() 182 | self.smallScreen.drawPoint(pos) 183 | self.sandclock.update(pos) 184 | self.sandclock.draw() 185 | # Restart the timer 186 | self.timer.start() 187 | self.mutex.unlock() 188 | 189 | def makeWiiCallback(self): 190 | def callback(pos): 191 | self.wiiCallback.emit(pos[0], pos[1]) 192 | return callback 193 | 194 | def doWork(self): 195 | self.mutex.lock() 196 | self.sandclock.update(None) 197 | self.sandclock.draw() 198 | 199 | if self.sandclock.finished(): 200 | self.wiiPoints.append(self.sandclock.getPoint()) 201 | self.sandclock.initialize() 202 | if len(self.wiiPoints) == 4: 203 | self.mutex.unlock() 204 | self.close() 205 | return 206 | self.ui.label.setText(self.textMessages.pop(0)) 207 | 208 | self.mutex.unlock() 209 | 210 | def closeEvent(self,e): 211 | self.timer.stop() 212 | self.wii.disable() 213 | e.accept() 214 | 215 | 216 | 217 | 218 | class CalibrateDialog(QtWidgets.QDialog): 219 | wiiCallback = QtCore.pyqtSignal(int, int) 220 | 221 | def __init__(self,parent,wii): 222 | screenGeom = QtWidgets.QDesktopWidget().screenGeometry() 223 | self.wdt = screenGeom.width() 224 | self.hgt = screenGeom.height() 225 | 226 | # Thanks, Pietro Pilolli!! 227 | QtWidgets.QWidget.__init__(self, parent, 228 | QtCore.Qt.FramelessWindowHint | 229 | QtCore.Qt.WindowStaysOnTopHint | 230 | QtCore.Qt.X11BypassWindowManagerHint ) 231 | self.setGeometry(0, 0, self.wdt, self.hgt) 232 | 233 | self.wii = wii 234 | self.setContentsMargins(0,0,0,0) 235 | 236 | sh = QtWidgets.QShortcut(self) 237 | sh.setKey("Esc") 238 | sh.activated.connect(self.close) 239 | 240 | sh = QtWidgets.QShortcut(self) 241 | sh.setKey("Down") 242 | sh.activated.connect(self.decCrosses) 243 | 244 | sh = QtWidgets.QShortcut(self) 245 | sh.setKey("Up") 246 | sh.activated.connect(self.incCrosses) 247 | 248 | self.scene = qt.QGraphicsScene() 249 | self.scene.setSceneRect(0,0, self.wdt, self.hgt) 250 | self.gv = QtWidgets.QGraphicsView() 251 | self.gv.setScene(self.scene) 252 | self.gv.setStyleSheet( "QGraphicsView { border-style: none; }" ) 253 | self.layout = QtWidgets.QVBoxLayout() 254 | self.layout.setContentsMargins(0,0,0,0) 255 | self.layout.setSpacing(0) 256 | self.layout.addWidget(self.gv) 257 | self.setLayout(self.layout) 258 | 259 | self.CalibrationPoints = [ 260 | [40,40], [self.wdt-40,40], [self.wdt-40,self.hgt-40], [40,self.hgt-40] 261 | ] 262 | self.clock = clock() 263 | self.mutex = qt.QMutex() 264 | self.updateCalibrationPoints(0) 265 | 266 | self.wii.putCallbackIR(self.makeWiiCallback()) 267 | self.wii.enable() 268 | 269 | self.timer = qt.QTimer(self) 270 | self.timer.setInterval(70) 271 | self.timer.timeout.connect(self.doWork) 272 | self.timer.start() 273 | 274 | self.wiiCallback.connect(self._wiiCallback) 275 | 276 | 277 | def decCrosses(self): 278 | if self.CalibrationPoints[0][0] < 350: 279 | self.updateCalibrationPoints(10) 280 | 281 | def incCrosses(self): 282 | if self.CalibrationPoints[0][0] > 40: 283 | self.updateCalibrationPoints(-10) 284 | 285 | def updateCalibrationPoints(self,delta=0): 286 | self.mutex.lock() 287 | self.scene.clear() 288 | self.marks = [] 289 | self.wiiPoints = [] 290 | self.CalibrationPoints[0][0] += delta 291 | self.CalibrationPoints[1][0] -= delta 292 | self.CalibrationPoints[2][0] -= delta 293 | self.CalibrationPoints[3][0] += delta 294 | self.CalibrationPoints[0][1] += delta 295 | self.CalibrationPoints[1][1] += delta 296 | self.CalibrationPoints[2][1] -= delta 297 | self.CalibrationPoints[3][1] -= delta 298 | for p in self.CalibrationPoints: 299 | self.scene.addPolygon(crossPoly(*p)) 300 | m = self.scene.addRect(p[0]-5,p[1]-5,10,10, 301 | qt.QPen(QtCore.Qt.red, 2, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin)) 302 | m.setVisible(False) 303 | self.marks.append([m, p]) 304 | self.scene.update() 305 | 306 | self.smallScreen = SmallScreen(self.wdt,self.hgt,self.scene) 307 | self.sandclock = SandClock(self.scene,*self.marks[0][1]) 308 | txt = self.scene.addSimpleText(self.tr("Push UP/DOWN to alter the crosses' position")) 309 | txt.setPos(old_div(self.wdt,2) - old_div(txt.boundingRect().width(),2), 40) 310 | self.mutex.unlock() 311 | 312 | @QtCore.pyqtSlot(int, int) 313 | def _wiiCallback(self, pos0, pos1): 314 | pos = [pos0, pos1] 315 | self.mutex.lock() 316 | self.smallScreen.drawPoint(pos) 317 | self.sandclock.update(pos) 318 | self.sandclock.draw() 319 | # Restart the timer 320 | self.timer.start() 321 | self.mutex.unlock() 322 | 323 | def makeWiiCallback(self): 324 | k = [0] 325 | def callback(pos): 326 | t = clock() 327 | if (t-k[0]) < 30: return 328 | self.wiiCallback.emit(pos[0], pos[1]) 329 | k[0] = t 330 | return callback 331 | 332 | def doWork(self): 333 | self.mutex.lock() 334 | self.sandclock.update(None) 335 | self.sandclock.draw() 336 | 337 | if len(self.marks): 338 | m = self.marks[0][0] 339 | c = clock() - self.clock 340 | if c >= 300: 341 | if m.isVisible(): m.setVisible(False) 342 | else: m.setVisible(True) 343 | self.clock = clock() 344 | 345 | if self.sandclock.finished(): 346 | self.wiiPoints.append(self.sandclock.getPoint()) 347 | self.marks.pop(0)[0].setVisible(True) 348 | if len(self.wiiPoints) == 4: 349 | self.mutex.unlock() 350 | self.close() 351 | return 352 | self.sandclock.initialize() 353 | self.sandclock.setCenter(*self.marks[0][1]) 354 | 355 | self.mutex.unlock() 356 | 357 | 358 | def closeEvent(self,e): 359 | self.timer.stop() 360 | self.wii.disable() 361 | e.accept() 362 | 363 | 364 | def doCalibration(parent,wii): 365 | conf = Configuration() 366 | wii.disable() 367 | wii.putCallbackBTN(None) 368 | 369 | if conf.getValueStr("fullscreen") == "Yes": 370 | dialog = CalibrateDialog(parent,wii) 371 | dialog.showFullScreen() 372 | dialog.grabKeyboard() 373 | dialog.exec_() 374 | dialog.releaseKeyboard() 375 | else: 376 | dialog = CalibrateDialog2(parent,wii) 377 | dialog.exec_() 378 | 379 | if len(dialog.wiiPoints) == 4: 380 | wii.calibrate(dialog.CalibrationPoints,dialog.wiiPoints) 381 | else: 382 | raise CalibrationAbort() 383 | 384 | -------------------------------------------------------------------------------- /stuff/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 273 10 | 583 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 273 22 | 583 23 | 24 | 25 | 26 | 27 | 771 28 | 583 29 | 30 | 31 | 32 | MainWindow 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 251 41 | 518 42 | 43 | 44 | 45 | 46 | 251 47 | 518 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Connect 63 | 64 | 65 | 66 | 67 | 68 | 69 | Calibrate 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | Activate 81 | 82 | 83 | 84 | 85 | 86 | 87 | Show Settings 88 | 89 | 90 | true 91 | 92 | 93 | false 94 | 95 | 96 | false 97 | 98 | 99 | 100 | 101 | 102 | 103 | Profile: 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | Default 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Wiimote Battery level: 120 | 121 | 122 | 123 | 124 | 125 | 126 | 24 127 | 128 | 129 | 130 | 131 | 132 | 133 | Utilization: 0% 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 0 142 | 0 143 | 144 | 145 | 146 | Qt::ScrollBarAlwaysOff 147 | 148 | 149 | Qt::ScrollBarAlwaysOff 150 | 151 | 152 | 153 | 0.000000000000000 154 | 0.000000000000000 155 | 0.000000000000000 156 | 0.000000000000000 157 | 158 | 159 | 160 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 161 | 162 | 163 | 164 | 165 | 166 | 167 | Load Calibration 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | QFrame::StyledPanel 178 | 179 | 180 | QFrame::Raised 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | Mouse Control 190 | 191 | 192 | false 193 | 194 | 195 | false 196 | 197 | 198 | false 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | Move Only 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | Qt::Vertical 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 0 251 | 0 252 | 273 253 | 25 254 | 255 | 256 | 257 | 258 | File 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | Help 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | Exit 279 | 280 | 281 | 282 | 283 | Quit 284 | 285 | 286 | 287 | 288 | Help 289 | 290 | 291 | 292 | 293 | Configuration 294 | 295 | 296 | 297 | 298 | New Profile 299 | 300 | 301 | 302 | 303 | Delete Current Profile 304 | 305 | 306 | 307 | 308 | false 309 | 310 | 311 | Restart 312 | 313 | 314 | 315 | 316 | Wipe configuration 317 | 318 | 319 | 320 | 321 | 322 | 323 | -------------------------------------------------------------------------------- /stuff/configuration.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 467 10 | 483 11 | 12 | 13 | 14 | Configuration 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | true 24 | 25 | 26 | 27 | 449 28 | 0 29 | 30 | 31 | 32 | 2 33 | 34 | 35 | 36 | Toggles 37 | 38 | 39 | 40 | 41 | 42 | false 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | Area 1 54 | 55 | 56 | 57 | 58 | 59 | 60 | Area 2 61 | 62 | 63 | 64 | 65 | 66 | 67 | Area 3 68 | 69 | 70 | 71 | 72 | 73 | 74 | Area 4 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Left Click 87 | 88 | 89 | 90 | 91 | Only Move 92 | 93 | 94 | 95 | 96 | Right Click 97 | 98 | 99 | 100 | 101 | Middle Click 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Left Click 111 | 112 | 113 | 114 | 115 | Only Move 116 | 117 | 118 | 119 | 120 | Right Click 121 | 122 | 123 | 124 | 125 | Middle Click 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | Left Click 135 | 136 | 137 | 138 | 139 | Only Move 140 | 141 | 142 | 143 | 144 | Right Click 145 | 146 | 147 | 148 | 149 | Middle Click 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | Left Click 159 | 160 | 161 | 162 | 163 | Only Move 164 | 165 | 166 | 167 | 168 | Right Click 169 | 170 | 171 | 172 | 173 | Middle Click 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | General options 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | Auto connect 197 | 198 | 199 | 200 | 201 | 202 | 203 | Don't wait for devices. Pick the first one 204 | 205 | 206 | 207 | 208 | 209 | 210 | Select allowed devices: 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | Add connected device 225 | 226 | 227 | 228 | 229 | 230 | 231 | Qt::Horizontal 232 | 233 | 234 | 235 | 40 236 | 20 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | Remove device 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | Advanced 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | Fullscreen Calibration 265 | 266 | 267 | 268 | 269 | 270 | 271 | Do calibration after connection 272 | 273 | 274 | 275 | 276 | 277 | 278 | Use calibration matrix from settings if available 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | Smoothing: 290 | 291 | 292 | 293 | 294 | 295 | 296 | Qt::Horizontal 297 | 298 | 299 | QSlider::NoTicks 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | IR Sensitivity: 311 | 312 | 313 | 314 | 315 | 316 | 317 | 6 318 | 319 | 320 | Qt::Horizontal 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | Qt::Vertical 336 | 337 | 338 | 339 | 20 340 | 40 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | button_addDev 359 | button_remDev 360 | 361 | 362 | 363 | 364 | -------------------------------------------------------------------------------- /stuff/linuxWiimoteLib.py: -------------------------------------------------------------------------------- 1 | # LICENSE: MIT (X11) License which follows: 2 | # 3 | # Copyright (c) 2008 Stephane Duchesneau 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | # 23 | # Modified by Pere Negre and Pietro Pilolli 24 | # 25 | 26 | import threading 27 | import time 28 | import bluetooth 29 | 30 | def old_div(a, b): 31 | if isinstance(a, int) and isinstance(b, int): 32 | return a // b 33 | else: 34 | return a / b 35 | 36 | def i2bs(val): 37 | lst = [] 38 | while val: 39 | lst.append(val&0xff) 40 | val = val >> 8 41 | lst.reverse() 42 | return lst 43 | 44 | class WiimoteState(object): 45 | Battery = None 46 | 47 | class ButtonState(object): 48 | A = False 49 | B = False 50 | Down = False 51 | Home = False 52 | Left = False 53 | Minus = False 54 | One = False 55 | Plus = False 56 | Right = False 57 | Two = False 58 | Up = False 59 | 60 | class IRState(object): 61 | RawX1 = 0 62 | RawX2 = 0 63 | RawX3 = 0 64 | RawX4 = 0 65 | 66 | RawY1 = 0 67 | RawY2 = 0 68 | RawY3 = 0 69 | RawY4 = 0 70 | 71 | Found1 = 0 72 | Found2 = 0 73 | Found3 = 0 74 | Found4 = 0 75 | 76 | Size1 = 0 77 | Size2 = 0 78 | Size3 = 0 79 | Size4 = 0 80 | 81 | X1 = X2 = X3 = X4 = 0.0 82 | Y1 = Y2 = Y3 = Y4 = 0.0 83 | 84 | #Mode = None 85 | MidX = 0 86 | MidY = 0 87 | RawMidX = 0 88 | RawMidY = 0 89 | 90 | class LEDState(object): 91 | LED1 = False 92 | LED2 = False 93 | LED3 = False 94 | LED4 = False 95 | 96 | class Parser(object): 97 | """ Sets the values contained in a signal """ 98 | A = 0x0008 99 | B = 0x0004 100 | Down = 0x0400 101 | Home = 0x0080 102 | Left = 0x0100 103 | Minus = 0x0010 104 | One = 0x0002 105 | Plus = 0x1000 106 | Right = 0x0200 107 | Two = 0x0001 108 | Up = 0x0800 109 | 110 | def parseButtons(self,signal, ButtonState): #signal is 16bit long intl 111 | ButtonState.A = bool(signal&self.A) 112 | ButtonState.B = bool(signal&self.B) 113 | ButtonState.Down = bool(signal&self.Down) 114 | ButtonState.Home = bool(signal&self.Home) 115 | ButtonState.Left = bool(signal&self.Left) 116 | ButtonState.Minus = bool(signal&self.Minus) 117 | ButtonState.One = bool(signal&self.One) 118 | ButtonState.Plus = bool(signal&self.Plus) 119 | ButtonState.Right = bool(signal&self.Right) 120 | ButtonState.Two = bool(signal&self.Two) 121 | ButtonState.Up = bool(signal&self.Up) 122 | 123 | def parseIR(self,signal,irstate): 124 | irstate.RawX1 = signal[0] + ((signal[2] & 0x30) >>4 << 8) 125 | irstate.RawY1 = signal[1] + (signal[2] >> 6 << 8) 126 | irstate.Size1 = signal[2] & 0x0f 127 | if irstate.RawY1 == 1023: irstate.Found1 = False 128 | else: irstate.Found1 = True 129 | 130 | irstate.RawX2 = signal[3] + ((signal[5] & 0x30) >>4 << 8) 131 | irstate.RawY2 = signal[4] + (signal[5] >> 6 << 8) 132 | irstate.Size2 = signal[5] & 0x0f 133 | if irstate.RawY2 == 1023: irstate.Found2 = False 134 | else: irstate.Found2 = True 135 | 136 | irstate.RawX3 = signal[6] + ((signal[8] & 0x30) >>4 << 8) 137 | irstate.RawY3 = signal[7] + (signal[8] >> 6 << 8) 138 | irstate.Size3 = signal[8] & 0x0f 139 | if irstate.RawY3 == 1023: irstate.Found3 = False 140 | else: irstate.Found3 = True 141 | 142 | irstate.RawX4 = signal[9] + ((signal[11] & 0x30) >>4 << 8) 143 | irstate.RawY4 = signal[10] + (signal[11] >> 6 << 8) 144 | irstate.Size4 = signal[11] & 0x0f 145 | if irstate.RawY4 == 1023: irstate.Found4 = False 146 | else: irstate.Found4 = True 147 | 148 | if irstate.Found1: 149 | if irstate.Found2: 150 | irstate.RawMidX = old_div((irstate.RawX1 + irstate.RawX2), 2) 151 | irstate.RawMidY = old_div((irstate.RawY1 + irstate.RawY2), 2) 152 | else: 153 | irstate.RawMidX = irstate.RawX1 154 | irstate.RawMidY = irstate.RawY1 155 | irstate.MidX = old_div(float(irstate.RawMidX), 1024) 156 | irstate.MidY = old_div(float(irstate.RawMidY), 768) 157 | else: irstate.MidX = irstate.MidY = 0 158 | 159 | 160 | class Setter(object): 161 | """The opposite from the Parser class: returns the signal needed to set the values in the wiimote""" 162 | LED1 = 0x10 163 | LED2 = 0x20 164 | LED3 = 0x40 165 | LED4 = 0x80 166 | 167 | def SetLEDs(self,ledstate): 168 | signal = 0 169 | if ledstate.LED1: signal += self.LED1 170 | if ledstate.LED2: signal += self.LED2 171 | if ledstate.LED3: signal += self.LED3 172 | if ledstate.LED4: signal += self.LED4 173 | return signal 174 | 175 | 176 | class InputReport(object): 177 | Buttons = 2 #2 to 8 not implemented yet !!! only IR is implemented 178 | Status = 4 179 | ReadData = 5 180 | ButtonsExtension = 6 181 | 182 | class Wiimote(threading.Thread): 183 | state = None 184 | running = False 185 | WiimoteState = WiimoteState 186 | InputReport = InputReport 187 | 188 | 189 | def __init__(self): 190 | threading.Thread.__init__(self) 191 | self.parser = Parser() 192 | self.setter = Setter() 193 | self.IRCallback = None 194 | 195 | def Connect(self, device): 196 | self.bd_addr = device[0] 197 | self.name = device[1] 198 | self.controlsocket = bluetooth.BluetoothSocket(bluetooth.L2CAP) 199 | self.controlsocket.connect((self.bd_addr,17)) 200 | self.datasocket = bluetooth.BluetoothSocket(bluetooth.L2CAP) 201 | self.datasocket.connect((self.bd_addr,19)) 202 | self.sendsocket = self.controlsocket 203 | self.CMD_SET_REPORT = 0x52 204 | 205 | if self.name == "Nintendo RVL-CNT-01-TR": 206 | self.CMD_SET_REPORT = 0xa2 207 | self.sendsocket = self.datasocket 208 | 209 | try: 210 | self.datasocket.settimeout(1) 211 | except NotImplementedError: 212 | print("socket timeout not implemented with this bluetooth module") 213 | 214 | print("Connected to ", self.bd_addr) 215 | self._get_battery_status() 216 | self.start() #start this thread 217 | return True 218 | 219 | def char_to_binary_string(self,ascii): 220 | bin = [] 221 | 222 | while (ascii > 0): 223 | if (ascii & 1) == 1: 224 | bin.append("1") 225 | else: 226 | bin.append("0") 227 | ascii = ascii >> 1 228 | 229 | bin.reverse() 230 | binary = "".join(bin) 231 | zerofix = (8 - len(binary)) * '0' 232 | 233 | return zerofix + binary 234 | 235 | def SetLEDs(self, led1,led2,led3,led4): 236 | self.WiimoteState.LEDState.LED1 = led1 237 | self.WiimoteState.LEDState.LED2 = led2 238 | self.WiimoteState.LEDState.LED3 = led3 239 | self.WiimoteState.LEDState.LED4 = led4 240 | 241 | self._send_data((0x11,self.setter.SetLEDs(self.WiimoteState.LEDState))) 242 | 243 | 244 | def run(self): 245 | print("starting") 246 | self.running = True 247 | while self.running: 248 | try: 249 | x= bytearray(self.datasocket.recv(32)) 250 | except bluetooth.BluetoothError: 251 | continue 252 | self.state = "" 253 | for each in x[:17]: 254 | self.state += self.char_to_binary_string(each) + " " 255 | if len(x) >= 4: 256 | self.parser.parseButtons((x[2]<<8) + x[3], self.WiimoteState.ButtonState) 257 | if len(x) >= 19: 258 | self.parser.parseIR(x[7:19],self.WiimoteState.IRState) 259 | self.doIRCallback() 260 | 261 | self.datasocket.close() 262 | self.controlsocket.close() 263 | print("Bluetooth socket closed succesfully.") 264 | self.Dispose() 265 | print("stopping") 266 | 267 | def Dispose(self): 268 | self.Disconnect() 269 | 270 | def Disconnect(self): 271 | self.running = False 272 | self.WiimoteState.Battery = None 273 | 274 | def join(self):#will be called last... 275 | self.Dispose() 276 | 277 | def _send_data(self,data): 278 | bin_data = bytearray() 279 | bin_data.append(self.CMD_SET_REPORT) 280 | for each in data: 281 | bin_data.append(each) 282 | self.sendsocket.send(bytes(bin_data)) 283 | 284 | def _write_to_mem(self, address, value): 285 | val = i2bs(value) 286 | val_len=len(val) 287 | val += [0]*(16-val_len) 288 | msg = [0x16] + i2bs(address) + [val_len] +val 289 | self._send_data(msg) 290 | 291 | def SetRumble(self,on): 292 | if on: self._send_data((0x11,0x01)) 293 | else: self._send_data((0x11,0x00)) 294 | 295 | def activate_IR(self, sens=6): 296 | self._send_data(i2bs(0x120033)) #mode IR 297 | self._send_data(i2bs(0x1304))#enable transmission 298 | self._send_data(i2bs(0x1a04))#enable transmission 299 | 300 | self.setIRSensitivity(sens) 301 | 302 | def setIRSensitivity(self, n): 303 | if n < 1 or n > 6: 304 | return 305 | 306 | self._write_to_mem(0x04b00030,0x08) 307 | time.sleep(0.1) 308 | 309 | if n == 1: 310 | self._write_to_mem(0x04b00000,0x0200007101006400fe) 311 | time.sleep(0.1) 312 | self._write_to_mem(0x04b0001a,0xfd05) 313 | elif n == 2: 314 | self._write_to_mem(0x04b00000,0x0200007101009600b4) 315 | time.sleep(0.1) 316 | self._write_to_mem(0x04b0001a,0xb304) 317 | elif n == 3: 318 | self._write_to_mem(0x04b00000,0x020000710100aa0064) 319 | time.sleep(0.1) 320 | self._write_to_mem(0x04b0001a,0x6303) 321 | elif n == 4: 322 | self._write_to_mem(0x04b00000,0x020000710100c80036) 323 | time.sleep(0.1) 324 | self._write_to_mem(0x04b0001a,0x3503) 325 | elif n == 5: 326 | self._write_to_mem(0x04b00000,0x070000710100720020) 327 | time.sleep(0.1) 328 | self._write_to_mem(0x04b0001a,0x1f03) 329 | # MAX 330 | elif n == 6: 331 | self._write_to_mem(0x04b00000,0x000000000000900041) 332 | time.sleep(0.1) 333 | self._write_to_mem(0x04b0001a,0x4000) 334 | 335 | time.sleep(0.1) 336 | self._write_to_mem(0x04b00033,0x33) 337 | 338 | def _get_battery_status(self): 339 | self._send_data((0x15,0x00)) 340 | self.running2 = True 341 | while self.running2: 342 | try: 343 | x = bytearray(self.datasocket.recv(32)) 344 | except bluetooth.BluetoothError: 345 | continue 346 | self.state = "" 347 | if len(x) >= 7: 348 | self.running2 = False 349 | battery_level = float(x[7]) 350 | self.WiimoteState.Battery = battery_level / 208. 351 | 352 | 353 | def setIRCallBack(self, func): 354 | self.IRCallback = func 355 | 356 | def doIRCallback(self): 357 | if self.IRCallback == None: return 358 | irstate = self.WiimoteState.IRState 359 | 360 | if irstate.Found1: 361 | self.IRCallback(irstate.RawX1, irstate.RawY1) 362 | elif irstate.Found2: 363 | self.IRCallback(irstate.RawX2, irstate.RawY2) 364 | elif irstate.Found3: 365 | self.IRCallback(irstate.RawX3, irstate.RawY3) 366 | elif irstate.Found4: 367 | self.IRCallback(irstate.RawX4, irstate.RawY4) 368 | 369 | 370 | if __name__ == "__main__": 371 | wiimote = Wiimote() 372 | print("Press 1 and 2 on wiimote (or SYNC on wiimote plus) to make it discoverable") 373 | wiimote.Connect() 374 | wiimote.activate_IR() 375 | while 1: 376 | time.sleep(0.1) 377 | #print wiimote.state 378 | print(wiimote.WiimoteState.ButtonState.A, wiimote.WiimoteState.ButtonState.B, wiimote.WiimoteState.ButtonState.Up, wiimote.WiimoteState.ButtonState.Down, wiimote.WiimoteState.ButtonState.Left, wiimote.WiimoteState.ButtonState.Right, wiimote.WiimoteState.ButtonState.Minus, wiimote.WiimoteState.ButtonState.Plus, wiimote.WiimoteState.ButtonState.Home, wiimote.WiimoteState.ButtonState.One, wiimote.WiimoteState.ButtonState.Two, wiimote.WiimoteState.IRState.RawX1, wiimote.WiimoteState.IRState.RawY1, wiimote.WiimoteState.IRState.Size1, wiimote.WiimoteState.IRState.RawX2, wiimote.WiimoteState.IRState.RawY2, wiimote.WiimoteState.IRState.Size2) 379 | #print wiimote.IRState.Found1 380 | 381 | 382 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /stuff/pywhiteboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | from wiimote import Wiimote 5 | from cursor import FakeCursor 6 | from threads import * 7 | 8 | from calibration import doCalibration, CalibrationAbort 9 | from configuration import Configuration, ConfigDialog 10 | 11 | 12 | import sys, time, locale, traceback 13 | import hashlib 14 | 15 | from PyQt5 import QtCore, QtGui, QtWidgets, uic 16 | import PyQt5.Qt as qt 17 | 18 | def old_div(a, b): 19 | if isinstance(a, int) and isinstance(b, int): 20 | return a // b 21 | else: 22 | return a / b 23 | 24 | class AboutDlg(QtWidgets.QDialog): 25 | 26 | def __init__(self, parent=None): 27 | super(AboutDlg, self).__init__(parent) 28 | self.ui = uic.loadUi("about.ui",self) 29 | self.ui.butOK.clicked.connect(self.close) 30 | 31 | 32 | 33 | class PBarDlg(QtWidgets.QDialog): 34 | def __init__(self, parent=None): 35 | super(PBarDlg,self).__init__(parent, qt.Qt.CustomizeWindowHint | qt.Qt.WindowTitleHint) 36 | 37 | self.ui = uic.loadUi("pbar.ui",self) 38 | self.cancelled = False 39 | self.choice = 0 40 | self.ui.butCancel.clicked.connect(self.cancelConnection) 41 | self.ui.butChoose.clicked.connect(self.makeChoice) 42 | self.ui.butChoose.hide() 43 | 44 | def reInit(self,mac='*'): 45 | self.cancelled = False 46 | self.choice = 0 47 | self.ui.butChoose.hide() 48 | self.ui.butCancel.setEnabled(True) 49 | self.ui.butChoose.setEnabled(True) 50 | if mac == '*': 51 | self.ui.label.setText(self.tr("Press 1+2 on your wiimote or SYNC on your wiimote plus")) 52 | else: 53 | self.ui.label.setText(self.tr("Press 1+2 or SYNC on") + " " + mac) 54 | 55 | def cancelConnection(self): 56 | self.cancelled = True 57 | self.ui.butCancel.setEnabled(False) 58 | self.ui.label.setText(self.tr("Cancelling...")) 59 | 60 | def makeChoice(self): 61 | self.choice = True 62 | self.ui.label.setText(self.tr("Wait...")) 63 | self.ui.butChoose.setEnabled(False) 64 | self.ui.butCancel.setEnabled(False) 65 | 66 | def inform(self,txt): 67 | self.ui.butChoose.setText(txt) 68 | self.ui.butChoose.show() 69 | 70 | 71 | class MainWindow(QtWidgets.QMainWindow): 72 | 73 | def __init__(self, parent=None): 74 | super(MainWindow, self).__init__(parent) 75 | self.ui = uic.loadUi("mainwindow.ui",self) 76 | self.setWindowTitle("python-whiteboard") 77 | self.setWindowFlags( qt.Qt.CustomizeWindowHint | qt.Qt.WindowMinimizeButtonHint | 78 | qt.Qt.WindowCloseButtonHint ) 79 | 80 | self.connected = False 81 | self.calibrated = False 82 | self.active = False 83 | self.mustquit = False 84 | 85 | self.wii = None 86 | self.cursor = None 87 | 88 | self.batteryLevel.reset() 89 | self.batteryLevel.setRange(0,99) 90 | self.batteryLevel.setValue(0) 91 | 92 | conf = Configuration() 93 | 94 | self.ui.pushButtonConnect.clicked.connect(self.connectWii) 95 | self.ui.pushButtonCalibrate.clicked.connect(self.calibrateWiiScreen) 96 | self.ui.pushButtonActivate.clicked.connect(self.activateWii) 97 | self.ui.pushButtonLoadCal.clicked.connect(self.calibrateWiiFromSettings) 98 | self.ui.pushButtonSettings.clicked.connect(self.showHideSettings) 99 | self.ui.comboProfiles.currentIndexChanged.connect(self.changeProfile) 100 | self.updateButtons() 101 | 102 | self.ui.actionQuit.triggered.connect(self.mustQuit) 103 | self.ui.actionHelp.triggered.connect(self.showAboutDlg) 104 | self.ui.actionNew_Profile.triggered.connect(self.addProfile) 105 | self.ui.actionDelete_Current_Profile.triggered.connect(self.delCurrentProfile) 106 | self.ui.actionWipe_configuration.triggered.connect(self.wipeConfiguration) 107 | 108 | self.ui.moveOnlyCheck.setChecked( conf.getValueStr('moveonly') == 'Yes' ) 109 | self.ui.moveOnlyCheck.stateChanged.connect(self.checkMoveOnly) 110 | 111 | if conf.getValueStr("autoconnect") == "Yes": 112 | self.timer = qt.QTimer(self) 113 | self.timer.setInterval(500) 114 | self.timer.timeout.connect(self.autoConnect) 115 | self.timer.start() 116 | 117 | self.timer2 = qt.QTimer(self) 118 | self.timer2.setInterval(4000) 119 | self.timer2.timeout.connect(self.checkWii) 120 | self.timer2.start() 121 | 122 | self.confDialog = ConfigDialog(self, self.wii) 123 | layout = QtWidgets.QGridLayout() 124 | layout.addWidget(self.confDialog) 125 | self.ui.confContainer.setLayout(layout) 126 | self.ui.confContainer.setVisible(False) 127 | 128 | self.refreshProfiles() 129 | 130 | self.center() 131 | 132 | 133 | def changeProfile(self,i): 134 | conf = Configuration() 135 | if i == 0: 136 | conf.setGroup("default") 137 | else: 138 | g = str(self.ui.comboProfiles.currentText()) 139 | conf.setGroup(hashlib.md5(g.encode('utf-8')).hexdigest()) 140 | 141 | self.confDialog.refreshWidgets() 142 | self.ui.moveOnlyCheck.setChecked( conf.getValueStr('moveonly') == 'Yes' ) 143 | 144 | 145 | def refreshProfiles(self): 146 | conf = Configuration() 147 | self.ui.comboProfiles.clear() 148 | self.ui.comboProfiles.addItem(self.tr("default")) 149 | 150 | for p in conf.getProfileList(): 151 | self.ui.comboProfiles.addItem(p) 152 | 153 | self.confDialog.refreshWidgets() 154 | self.ui.moveOnlyCheck.setChecked( conf.getValueStr('moveonly') == 'Yes' ) 155 | 156 | 157 | def addProfile(self): 158 | profName, ok = QtWidgets.QInputDialog.getText(self, 159 | self.tr("New Profile"), self.tr('Name:')) 160 | 161 | profName = str(profName) 162 | if ok and profName != '': 163 | conf = Configuration() 164 | profiles = conf.getProfileList() 165 | for p in profiles: 166 | if p == profName: return 167 | profiles.append(profName) 168 | conf.setProfileList(profiles) 169 | self.refreshProfiles() 170 | i = self.ui.comboProfiles.findText(profName) 171 | self.ui.comboProfiles.setCurrentIndex(i) 172 | 173 | 174 | def delCurrentProfile(self): 175 | i = self.ui.comboProfiles.currentIndex() 176 | currentProfile = str(self.ui.comboProfiles.currentText()) 177 | if i == 0: return 178 | conf = Configuration() 179 | profiles = conf.getProfileList() 180 | profiles = [ p for p in profiles if p != currentProfile ] 181 | conf.setProfileList(profiles) 182 | self.refreshProfiles() 183 | self.ui.comboProfiles.setCurrentIndex(0) 184 | 185 | 186 | def wipeConfiguration(self): 187 | conf = Configuration() 188 | conf.wipe() 189 | msgbox = QtWidgets.QMessageBox(self) 190 | msgbox.setText(self.tr("The application will close. Please restart manually") ) 191 | msgbox.setModal( True ) 192 | ret = msgbox.exec_() 193 | self.mustQuit() 194 | 195 | 196 | def showHideSettings(self): 197 | self.ui.confContainer.setVisible(not self.ui.confContainer.isVisible()) 198 | QtWidgets.QApplication.processEvents() 199 | if self.ui.confContainer.isVisible(): 200 | self.ui.pushButtonSettings.setText(self.tr('Hide settings')) 201 | # Res¡ze to max 202 | self.resize(1000,1000) 203 | else: 204 | self.ui.pushButtonSettings.setText(self.tr('Show settings')) 205 | self.adjustSize() 206 | 207 | 208 | def checkMoveOnly(self,i): 209 | conf = Configuration() 210 | if self.sender().isChecked(): 211 | conf.saveValue('moveonly','Yes') 212 | if self.cursor: 213 | self.cursor.noClicks = True 214 | else: 215 | conf.saveValue('moveonly','No') 216 | if self.cursor: 217 | self.cursor.noClicks = False 218 | 219 | 220 | def showAboutDlg(self): 221 | about = AboutDlg(self) 222 | about.show() 223 | about.exec_() 224 | 225 | 226 | def checkWii(self): 227 | if self.wii == None: return 228 | if self.connected == False: return 229 | if self.wii.checkStatus() == False: 230 | # Deactivate cursor 231 | self.deactivateWii() 232 | # Deactivate device 233 | self.connected = False 234 | self.calibrated = False 235 | self.active = False 236 | self.pushButtonConnect.setText(self.tr("Connect")) 237 | self.updateButtons() 238 | self.ui.label_utilization.setText(self.tr("Utilization: 0%")) 239 | self.clearScreenGraphic() 240 | self.batteryLevel.setValue(0) 241 | 242 | msgbox = QtWidgets.QMessageBox( self ) 243 | msgbox.setText( self.tr("Wii device disconnected") ) 244 | msgbox.setModal( True ) 245 | ret = msgbox.exec_() 246 | return 247 | self.batteryLevel.setValue(self.wii.battery()*100) 248 | 249 | 250 | def autoConnect(self): 251 | if self.isVisible(): 252 | self.timer.stop() 253 | self.connectWii() 254 | else: 255 | self.timer.start() 256 | 257 | 258 | 259 | def drawScreenGraphic(self): 260 | max_x = self.wiiScreen.geometry().width() 261 | max_y = self.wiiScreen.geometry().height() 262 | self.scene = qt.QGraphicsScene() 263 | self.scene.setSceneRect(0,0,max_x,max_y) 264 | quad = QtGui.QPolygonF() 265 | for p in self.wii.calibrationPoints: 266 | x = max_x * p[0]/Wiimote.MAX_X 267 | y = max_y * (1-old_div(float(p[1]),Wiimote.MAX_Y)) 268 | quad.append(qt.QPointF(x,y)) 269 | self.scene.addPolygon(quad) 270 | self.wiiScreen.setScene(self.scene) 271 | self.wiiScreen.show() 272 | 273 | 274 | def clearScreenGraphic(self): 275 | if self.wiiScreen.scene(): 276 | self.scene.clear() 277 | 278 | 279 | def center(self): 280 | screen = QtWidgets.QDesktopWidget().screenGeometry() 281 | size = self.geometry() 282 | self.move(old_div((screen.width()-size.width()),2), old_div((screen.height()-size.height()),2)) 283 | 284 | 285 | 286 | def updateButtons(self): 287 | if self.connected == False: 288 | self.ui.pushButtonConnect.setEnabled(1) 289 | self.ui.pushButtonCalibrate.setEnabled(0) 290 | self.ui.pushButtonActivate.setEnabled(0) 291 | self.ui.pushButtonLoadCal.setEnabled(0) 292 | #self.ui.frame_mouseControl.setEnabled(1) 293 | self.statusBar().showMessage("") 294 | return 295 | 296 | self.statusBar().showMessage(self.tr("Connected to ") + self.wii.addr) 297 | 298 | if self.calibrated == False: 299 | self.ui.pushButtonConnect.setEnabled(1) 300 | self.ui.pushButtonCalibrate.setEnabled(1) 301 | self.ui.pushButtonActivate.setEnabled(0) 302 | self.ui.pushButtonLoadCal.setEnabled(1) 303 | #self.ui.frame_mouseControl.setEnabled(1) 304 | return 305 | if self.active == False: 306 | self.ui.pushButtonConnect.setEnabled(1) 307 | self.ui.pushButtonCalibrate.setEnabled(1) 308 | self.ui.pushButtonActivate.setEnabled(1) 309 | self.ui.pushButtonLoadCal.setEnabled(1) 310 | #self.ui.frame_mouseControl.setEnabled(1) 311 | return 312 | else: 313 | self.ui.pushButtonConnect.setEnabled(0) 314 | self.ui.pushButtonCalibrate.setEnabled(1) 315 | self.ui.pushButtonLoadCal.setEnabled(0) 316 | self.ui.pushButtonActivate.setEnabled(1) 317 | #self.ui.frame_mouseControl.setEnabled(0) 318 | 319 | 320 | def disconnectDevice(self): 321 | if self.active: 322 | if self.cursor: 323 | self.cursor.finish() 324 | self.active = False 325 | 326 | if self.wii: 327 | self.wii.disable() 328 | self.wii.close() 329 | self.wii = None 330 | self.connected = False 331 | self.calibrated = False 332 | self.active = False 333 | self.pushButtonConnect.setText(self.tr("Connect")) 334 | self.updateButtons() 335 | self.ui.label_utilization.setText(self.tr("Utilization: 0%")) 336 | self.clearScreenGraphic() 337 | self.batteryLevel.setValue(0) 338 | self.confDialog.wii = None 339 | self.confDialog.checkButtons() 340 | return 341 | 342 | 343 | def makeBTNCallback(self): 344 | def func(): 345 | # Simulate click to calibrate button 346 | self.ui.pushButtonCalibrate.click() 347 | 348 | return func 349 | 350 | 351 | 352 | def connectWii(self): 353 | if self.connected: 354 | self.disconnectDevice() 355 | return 356 | 357 | self.wii = Wiimote() 358 | pBar = PBarDlg(self) 359 | pBar.setModal( True ) 360 | pBar.show() 361 | conf = Configuration() 362 | selectedMac = conf.getValueStr("selectedmac") 363 | pBar.reInit(selectedMac) 364 | pool = [] 365 | while 1: 366 | thread = self.wii.createConnectThread(selectedMac,pool) 367 | thread.start() 368 | 369 | while not thread.wait(30): 370 | QtWidgets.QApplication.processEvents() 371 | 372 | if pBar.cancelled == True: 373 | if self.wii.isConnected(): 374 | self.wii.close() 375 | 376 | self.wii = None 377 | pBar.close() 378 | return 379 | 380 | if selectedMac == '*' and len(pool) >= 1: 381 | if Configuration().getValueStr('nowaitdevices') == 'Yes': 382 | selectedMac = pool[0] 383 | else: 384 | pBar.inform(self.tr('Found ') + str(len(pool)) + self.tr(' Devices. Press to Choose')) 385 | 386 | if self.wii.isConnected(): 387 | self.connected = True 388 | self.calibrated = False 389 | self.active = False 390 | self.updateButtons() 391 | self.batteryLevel.setValue(self.wii.battery()*100) 392 | self.pushButtonConnect.setText(self.tr("Disconnect")) 393 | 394 | pBar.close() 395 | 396 | self.confDialog.wii = self.wii 397 | self.confDialog.checkButtons() 398 | 399 | self.wii.disable() 400 | self.wii.putCallbackBTN(self.makeBTNCallback()) 401 | self.wii.putCallbackIR(None) 402 | self.wii.enable() 403 | 404 | # Start calibration if configuration says so 405 | conf = Configuration() 406 | if conf.getValueStr("autocalibration") == "Yes": 407 | if conf.getValueStr("automatrix") == "Yes": 408 | self.calibrateWiiFromSettings() 409 | else: 410 | self.calibrateWiiScreen() 411 | return 412 | 413 | if self.wii.error: 414 | self.wii = None 415 | msgbox = QtWidgets.QMessageBox( self ) 416 | msgbox.setWindowTitle( self.tr('Error') ) 417 | msgbox.setText( self.tr("Error. Check your bluetooth driver") ) 418 | msgbox.setModal( True ) 419 | ret = msgbox.exec_() 420 | pBar.close() 421 | return 422 | 423 | if pBar.choice: 424 | if len(pool) == 1: 425 | selectedMac = str(pool[0]) 426 | pBar.reInit(selectedMac) 427 | else: 428 | item, ok = QtWidgets.QInputDialog.getItem(self, 429 | self.tr("Warning"), self.tr("Choose device"), pool, 0, False) 430 | if ok: 431 | selectedMac = str(item) 432 | pBar.reInit(selectedMac) 433 | else: 434 | pBar.close() 435 | return 436 | 437 | 438 | 439 | 440 | # doscreen: if doscreen is true, calibrate by manual pointing 441 | def calibrateWii(self,loadFromSettings=False): 442 | self.deactivateWii() 443 | self.ui.label_utilization.setText(self.tr("Utilization: 0%")) 444 | self.clearScreenGraphic() 445 | 446 | self.calibrated = False 447 | self.active = False 448 | try: 449 | self.wii.state = Wiimote.NONCALIBRATED 450 | if loadFromSettings: 451 | # If calibration matrix can't be loaded, calibrate manually 452 | if not self.loadCalibration(self.wii): 453 | doCalibration(self,self.wii) 454 | else: 455 | doCalibration(self,self.wii) 456 | 457 | 458 | if self.wii.state == Wiimote.CALIBRATED: 459 | self.calibrated = True 460 | self.active = False 461 | self.drawScreenGraphic() 462 | self.updateButtons() 463 | self.ui.label_utilization.setText(self.tr("Utilization: ") + "%d%%" % (100.0*self.wii.utilization)) 464 | self.saveCalibrationPars(self.wii) 465 | 466 | # Activate cursor after calibration (always) 467 | self.activateWii() 468 | return 469 | 470 | except CalibrationAbort: 471 | # Do nothing (user choice) 472 | pass 473 | 474 | except: 475 | print("Error during Calibration") 476 | traceback.print_exc(file=sys.stdout) 477 | self.updateButtons() 478 | msgbox = QtWidgets.QMessageBox( self ) 479 | msgbox.setText( self.tr("Error during Calibration") ) 480 | msgbox.setModal( True ) 481 | ret = msgbox.exec_() 482 | 483 | # Installs button callback (for calling calibration) 484 | self.wii.disable() 485 | self.wii.putCallbackBTN(self.makeBTNCallback()) 486 | self.wii.putCallbackIR(None) 487 | self.wii.enable() 488 | 489 | 490 | def calibrateWiiScreen(self): 491 | self.calibrateWii() 492 | 493 | 494 | def calibrateWiiFromSettings(self): 495 | self.calibrateWii(loadFromSettings=True) 496 | 497 | 498 | def saveCalibrationPars(self,wii): 499 | conf = Configuration() 500 | for i,p in enumerate(wii.screenPoints): 501 | conf.saveValue("screenPoint"+str(i)+"x",str(p[0])) 502 | conf.saveValue("screenPoint"+str(i)+"y",str(p[1])) 503 | 504 | for i,p in enumerate(wii.calibrationPoints): 505 | conf.saveValue("wiiPoint"+str(i)+"x",str(p[0])) 506 | conf.saveValue("wiiPoint"+str(i)+"y",str(p[1])) 507 | 508 | 509 | def loadCalibration(self,wii): 510 | try: 511 | conf = Configuration() 512 | pwii = [] 513 | pscr = [] 514 | for i in range(0,4): 515 | p = [] 516 | p.append(float(conf.getValueStr("screenPoint"+str(i)+"x"))) 517 | p.append(float(conf.getValueStr("screenPoint"+str(i)+"y"))) 518 | q = [] 519 | q.append(float(conf.getValueStr("wiiPoint"+str(i)+"x"))) 520 | q.append(float(conf.getValueStr("wiiPoint"+str(i)+"y"))) 521 | pwii.append(list(q)) 522 | pscr.append(list(p)) 523 | wii.calibrate(pscr,pwii) 524 | return True 525 | except: 526 | return False 527 | 528 | 529 | def deactivateWii(self): 530 | if self.active: 531 | self.cursor.finish() 532 | self.active = False 533 | self.pushButtonActivate.setText(self.tr("Activate")) 534 | self.updateButtons() 535 | 536 | 537 | def activateWii(self): 538 | if self.active: 539 | # Deactivate 540 | self.deactivateWii() 541 | else: 542 | # Activate 543 | self.cursor = FakeCursor(self.wii) 544 | if self.ui.moveOnlyCheck.isChecked(): 545 | self.cursor.noClicks = True 546 | 547 | # Installs button callback (for calling calibration) 548 | self.wii.disable() 549 | self.wii.putCallbackBTN(self.makeBTNCallback()) 550 | 551 | conf = Configuration() 552 | zones = [ conf.getValueStr(z) for z in ("zone1","zone2","zone3","zone4") ] 553 | self.cursor.setZones(zones) 554 | self.cursor.runThread() 555 | self.active = True 556 | self.pushButtonActivate.setText(self.tr("Deactivate")) 557 | self.updateButtons() 558 | 559 | 560 | # Exit callback 561 | def closeEvent(self,e): 562 | # Unity does not support qt systray anymore. 563 | # So, I'm putting the old code on hold 564 | 565 | #if self.mustquit: 566 | #self.disconnectDevice() 567 | #e.accept() 568 | #else: 569 | #msgbox = QtWidgets.QMessageBox(self) 570 | #msgbox.setText(self.tr("The application will remain active (systray).") + "\n" + \ 571 | #self.tr("To quit, use file->quit menu") ) 572 | #msgbox.setModal( True ) 573 | #ret = msgbox.exec_() 574 | #self.showHide() 575 | #e.ignore() 576 | 577 | # Instead, we simply ask if the user wants to really quit. 578 | 579 | msgbox = QtWidgets.QMessageBox(self) 580 | msgbox.setText(self.tr("Are you sure you want to exit?") ) 581 | msgbox.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) 582 | msgbox.setModal( True ) 583 | ret = msgbox.exec_() 584 | if ret == QtWidgets.QMessageBox.Ok: 585 | # Exit the application 586 | self.disconnectDevice() 587 | e.accept() 588 | else: 589 | e.ignore() 590 | 591 | 592 | def showHide(self): 593 | if self.isVisible(): 594 | self.hide() 595 | else: 596 | self.show() 597 | 598 | 599 | def mustQuit(self): 600 | self.mustquit = True 601 | self.close() 602 | 603 | 604 | 605 | 606 | 607 | class SysTrayIcon(object): 608 | def __init__(self, fname, mainWindow): 609 | self.mainWindow = mainWindow 610 | self.stray = QtWidgets.QSystemTrayIcon() 611 | self.stray.setIcon(QtGui.QIcon(fname)) 612 | 613 | self.stray.activated.connect(self.activate) 614 | 615 | def activate(self, reason): 616 | if reason == QtWidgets.QSystemTrayIcon.Trigger: 617 | self.mainWindow.showHide() 618 | 619 | def show(self): 620 | self.stray.show() 621 | 622 | 623 | 624 | def getTranslator(): 625 | trl = qt.QTranslator() 626 | loc = locale.getdefaultlocale()[0] 627 | if loc: 628 | code = loc.lower() 629 | if len(code) > 1: 630 | code = code[0:2] 631 | fname = "/usr/share/qt5/translations/pywhiteboard_" + code + ".qm" 632 | trl.load(fname) 633 | return trl 634 | 635 | 636 | 637 | 638 | # Checks that only one instance of python-whiteboard is running 639 | import fcntl 640 | fp = None 641 | def checkSingle(): 642 | lockfile = '/tmp/python-whiteboard.lock' 643 | global fp 644 | fp = open(lockfile, 'w') 645 | try: 646 | fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB) 647 | return True 648 | except IOError: 649 | return False 650 | 651 | 652 | 653 | 654 | 655 | def main(): 656 | app = QtWidgets.QApplication(sys.argv) 657 | t = getTranslator() 658 | app.installTranslator(t) 659 | mainWin = MainWindow() 660 | 661 | if checkSingle() == False: 662 | msgbox = QtWidgets.QMessageBox( mainWin ) 663 | msgbox.setText( app.tr("Application already running") ) 664 | msgbox.setModal( True ) 665 | ret = msgbox.exec_() 666 | sys.exit() 667 | 668 | stray = SysTrayIcon("icon.xpm", mainWin) 669 | stray.show() 670 | mainWin.show() 671 | app.exec_() 672 | -------------------------------------------------------------------------------- /stuff/about.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Dialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 467 10 | 387 11 | 12 | 13 | 14 | 15 | 467 16 | 387 17 | 18 | 19 | 20 | 21 | 467 22 | 387 23 | 24 | 25 | 26 | About python-whiteboard 27 | 28 | 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | Information 37 | 38 | 39 | 40 | 41 | 42 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 43 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 44 | p, li { white-space: pre-wrap; } 45 | </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> 46 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">python-whiteboard</span><span style=" font-family:'Sans'; font-size:10pt;"> (2009-2010) (c) Pere Negre Galmés</span></p> 47 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"></p> 48 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">You will find updates and information at the following address:</span></p> 49 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://wiki.github.com/pnegre/python-whiteboard/"><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://wiki.github.com/pnegre/python-whiteboard/</span></a></p> 50 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;"></p> 51 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">To download debian/ubuntu packages:</span></p> 52 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://github.com/pnegre/python-whiteboard/downloads"><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://github.com/pnegre/python-whiteboard/downloads</span></a></p> 53 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"></p> 54 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">To browse and download source code:</span></p> 55 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://github.com/pnegre/python-whiteboard"><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;">http://github.com/pnegre/python-whiteboard</span></a></p> 56 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt; text-decoration: underline; color:#0000ff;"></p> 57 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Special thanks to Pietro Pilolli for his continuous suggestions and ideas.</span></p></body></html> 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | Translations 66 | 67 | 68 | 69 | 70 | 71 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 72 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 73 | p, li { white-space: pre-wrap; } 74 | </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> 75 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Catalan Translation:</span></p> 76 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">* Pau Cabot</span></p> 77 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">* Pere Negre</span></p> 78 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Italian Translation:</span></p> 79 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">* Pietro Pilolli</span></p> 80 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">* Sergio Zanchetta</span></p> 81 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Spanish Translation:</span></p> 82 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">* Pere Negre</span></p> 83 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Chinese Translation:</span></p> 84 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">* Kentxchang Chang</span></p> 85 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">French Translation:</span></p> 86 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">* Georges Khaznadar</span></p> 87 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"></p> 88 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans'; font-size:10pt;"></p></body></html> 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | License 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 106 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 107 | p, li { white-space: pre-wrap; } 108 | </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> 109 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">GNU GENERAL PUBLIC LICENSE</span><span style=" font-family:'Sans'; font-size:10pt;"> </span></p> 110 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Version 2, June 1991 </span></p> 111 | <p style="-qt-paragraph-type:empty; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier'; font-size:10pt;"></p> 112 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">Copyright (C) 1989, 1991 Free Software Foundation, Inc. </span></p> 113 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA</span></p> 114 | <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Courier New,courier'; font-size:10pt;"></p> 115 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">Everyone is permitted to copy and distribute verbatim copies</span></p> 116 | <p style=" margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Courier New,courier'; font-size:10pt;">of this license document, but changing it is not allowed. </span></p> 117 | <p style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Preamble</span><span style=" font-family:'Sans'; font-size:10pt;"> </span></p> 118 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. </span></p> 119 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. </span></p> 120 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. </span></p> 121 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. </span></p> 122 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. </span></p> 123 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. </span></p> 124 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. </span></p> 125 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The precise terms and conditions for copying, distribution and modification follow. </span></p> 126 | <p style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION</span><span style=" font-family:'Sans'; font-size:10pt;"> </span></p> 127 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">0.</span><span style=" font-family:'Sans'; font-size:10pt;"> This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The &quot;Program&quot;, below, refers to any such program or work, and a &quot;work based on the Program&quot; means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term &quot;modification&quot;.) Each licensee is addressed as &quot;you&quot;. </span></p> 128 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. </span></p> 129 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">1.</span><span style=" font-family:'Sans'; font-size:10pt;"> You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. </span></p> 130 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. </span></p> 131 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">2.</span><span style=" font-family:'Sans'; font-size:10pt;"> You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: </span></p> 132 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:30px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">a)</span><span style=" font-family:'Sans'; font-size:10pt;"> You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. </span></p> 133 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:30px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">b)</span><span style=" font-family:'Sans'; font-size:10pt;"> You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. </span></p> 134 | <p style=" margin-top:0px; margin-bottom:8px; margin-left:30px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">c)</span><span style=" font-family:'Sans'; font-size:10pt;"> If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) </span></p> 135 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. </span></p> 136 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. </span></p> 137 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. </span></p> 138 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">3.</span><span style=" font-family:'Sans'; font-size:10pt;"> You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: </span></p> 139 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:30px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">a)</span><span style=" font-family:'Sans'; font-size:10pt;"> Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, </span></p> 140 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:30px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">b)</span><span style=" font-family:'Sans'; font-size:10pt;"> Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, </span></p> 141 | <p style=" margin-top:0px; margin-bottom:8px; margin-left:30px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">c)</span><span style=" font-family:'Sans'; font-size:10pt;"> Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) </span></p> 142 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. </span></p> 143 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. </span></p> 144 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">4.</span><span style=" font-family:'Sans'; font-size:10pt;"> You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. </span></p> 145 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">5.</span><span style=" font-family:'Sans'; font-size:10pt;"> You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. </span></p> 146 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">6.</span><span style=" font-family:'Sans'; font-size:10pt;"> Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. </span></p> 147 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">7.</span><span style=" font-family:'Sans'; font-size:10pt;"> If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. </span></p> 148 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. </span></p> 149 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. </span></p> 150 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. </span></p> 151 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">8.</span><span style=" font-family:'Sans'; font-size:10pt;"> If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. </span></p> 152 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">9.</span><span style=" font-family:'Sans'; font-size:10pt;"> The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. </span></p> 153 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and &quot;any later version&quot;, you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. </span></p> 154 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">10.</span><span style=" font-family:'Sans'; font-size:10pt;"> If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. </span></p> 155 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">NO WARRANTY</span><span style=" font-family:'Sans'; font-size:10pt;"> </span></p> 156 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">11.</span><span style=" font-family:'Sans'; font-size:10pt;"> BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM &quot;AS IS&quot; WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. </span></p> 157 | <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">12.</span><span style=" font-family:'Sans'; font-size:10pt;"> IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. </span></p> 158 | <p style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">END OF TERMS AND CONDITIONS</span><span style=" font-family:'Sans'; font-size:10pt;"> </span></p></body></html> 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | Qt::Horizontal 172 | 173 | 174 | 175 | 40 176 | 20 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | Ok 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | --------------------------------------------------------------------------------