Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1255 lines (1119 sloc)
56.4 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env pythone | |
# This is only needed for Python v2 but is harmless for Python v3. | |
#import sip | |
#sip.setapi('QVariant', 1) | |
import PyHook3, win32con, win32api, time, pythoncom, sys, threading, winsound, icons_rc, atexit | |
from collections import OrderedDict | |
from PyQt5.QtCore import pyqtSignal, Qt | |
from PyQt5.QtGui import QIcon | |
from PyQt5.QtWidgets import QVBoxLayout, QDialog, QWidget, QApplication, QSystemTrayIcon, QGroupBox, QRadioButton, \ | |
QMessageBox, QVBoxLayout, QHBoxLayout, QComboBox, QLabel, QLineEdit, QGridLayout, QCheckBox, QPushButton, QAction, \ | |
QMenu | |
from struct import pack, unpack | |
from enum import Enum | |
import json | |
import os | |
from presagectypes import Presage, PresageCallback | |
lastkeydowntime = -1 | |
presageconfig = os.path.join(os.path.dirname(os.path.realpath(__file__)), "res", "presage.xml") | |
presagedll = os.path.join(os.path.dirname(os.path.realpath(__file__)), "libpresage-1.dll") | |
keystrokes_state = {} | |
disabled = False | |
currentX = 0 | |
currentY = 0 | |
pressingKey = False | |
layoutmanager = None | |
codeslayoutview = None | |
typestate = None | |
hm = None | |
configfile = os.path.join(os.path.join(os.path.dirname(os.path.realpath(__file__)), "config.json")) | |
class TypeState (PresageCallback): | |
def __init__ (self): | |
self.text = "" | |
self.predictions = None | |
self.presage = Presage(self, config=presageconfig, dllfile=presagedll) | |
def past_stream (self): | |
return self.text | |
def future_stream (self): | |
return self.text | |
def pushchar (self, char): | |
self.text += char | |
self.predictions = None | |
def popchar (self): | |
self.text = self.text[:-1] | |
self.predictions = None | |
def getpredictions (self): | |
if self.predictions == None: | |
self.predictions = self.presage.predict() | |
return self.predictions | |
class LayoutManager (object): | |
def __init__ (self, fn, actions): | |
self.actions = actions | |
data_str = "" | |
with open(fn, "r") as f: | |
data = json.loads("".join(chunk for chunk in iter(f.read, ''))) | |
self.layouts = dict(map(lambda a:(a[0],self._layout_import(a[1])), data['layouts'].items())) | |
self.active = self.layouts.get(data['mainlayout'], None) | |
self.mainlayout = self.active | |
self.mainlayoutname = data['mainlayout'] | |
def _layout_import (self, layout): | |
for item in layout['items']: | |
actioninitdata = self.actions.__members__.get(item.get('action', None), None) | |
action = None | |
if actioninitdata is not None: | |
action = actioninitdata.value[0](item, *actioninitdata.value[1:]) | |
item['_action'] = action | |
layout['items'] = OrderedDict(map(lambda a: (a['code'], a), layout['items'])) | |
return layout | |
def set_active (self, layout): | |
self.active = layout | |
def PressKey(down, key): | |
global pressingKey | |
pressingKey = True | |
if myConfig['debug']: | |
print("presskey: ",key, "down" if down else "up") | |
# win32api.MAPVK_VK_TO_VSC = 0, one may need to pass this as second argument `win32api.MapVirtualKey(key, 0)` | |
win32api.keybd_event(key, win32api.MapVirtualKey(key, 0), (not down) * win32con.KEYEVENTF_KEYUP) | |
pressingKey = False | |
def TypeKey(key, keystroke_time=10): | |
PressKey(True, key) | |
PressKey(False, key) | |
if myConfig['debug']: | |
print("typekey, ", key) | |
def endCharacter(): | |
if myConfig['debug']: | |
print("End Character") | |
global currentCharacter, hm, repeaton, repeatkey, currentX, currentY | |
morse = "".join(map(str, currentCharacter)) | |
item = layoutmanager.active['items'].get(morse, None) | |
action = None if item is None else item['_action'] | |
if myConfig['debug']: | |
print("action: ", action) | |
if action is not None: | |
action.perform() | |
# save cursor position for after each action, MOUSEMODE has been removed!! | |
currentX, currentY = win32api.GetCursorPos() | |
hm.KeyDown = OnKeyboardEventDown | |
hm.KeyUp = OnKeyboardEventUp | |
currentCharacter = [] | |
codeslayoutview.reset() | |
def disableKeyUpDown(event): | |
if pressingKey: | |
updateKeyboardState(event) | |
onKeyboardEvent(event) | |
return True | |
return False | |
def OnKeyboardEventDown(event): | |
global lastkeydowntime, endCharacterTimer, disabled | |
#print "eventid: " + str(event.KeyID) | |
updateKeyboardState(event) | |
if (event.KeyID == 80 and getKeyStrokeState("CTRL")["down"] and getKeyStrokeState("SHIFT")['down']): | |
if (disabled == True): | |
disabled = False | |
return True | |
else: | |
disabled = True | |
return True | |
onKeyboardEvent(event) | |
if pressingKey: | |
return True | |
if (disabled): | |
TypeKey(event.KeyID) | |
return False | |
if myConfig['debug']: | |
print("Key down: ", event.Key, " ", event.KeyID, " ", str(event)) | |
print('MessageName:',event.MessageName) | |
print('Message:',event.Message) | |
print('Time:',event.Time) | |
print('Ascii:', event.Ascii, chr(event.Ascii)) | |
print('Key:', event.Key) | |
print('KeyID:', event.KeyID) | |
print('ScanCode:', event.ScanCode) | |
# if (myConfig['onekey']): | |
# if ((event.KeyID != myConfig['keyone']) or (lastkeydowntime != -1)): | |
# return False | |
# elif (((event.KeyID != myConfig['keyone']) and (event.KeyID != myConfig['keytwo'])) or (lastkeydowntime != -1)): | |
# return False | |
if lastkeydowntime != -1: | |
return False | |
keys = (myConfig['keyone'],) if myConfig['keylen'] == 1 else \ | |
(myConfig['keyone'], myConfig['keytwo']) if myConfig['keylen'] == 2 else \ | |
(myConfig['keyone'], myConfig['keytwo'], myConfig['keylen'] == 3) # threekey | |
tmp = tuple(map(lambda a:a[1], filter(lambda a:a[0].keywin32 == event.KeyID, zip(keys, range(len(keys)))))) | |
if len(tmp) == 0: | |
return True | |
keyidx = tmp[0] | |
try: | |
if endCharacterTimer is not None: | |
endCharacterTimer.cancel() | |
if keyidx == 2: # third key | |
endCharacter() | |
except NameError: | |
pass | |
lastkeydowntime = event.Time | |
hm.KeyDown = disableKeyUpDown | |
hm.KeyUp = OnKeyboardEventUp | |
return False | |
keyFeedbackTimer = None | |
def onKeyboardEvent (event): | |
global keyFeedbackTimer | |
if keyFeedbackTimer != None: | |
keyFeedbackTimer.cancel() | |
keyFeedbackTimer = threading.Timer(0.1, onKeyFeedback) | |
keyFeedbackTimer.start() | |
def onKeyFeedback (): | |
if codeslayoutview is not None: | |
codeslayoutview.feedbackSignal.emit() | |
def moveMouse(): | |
global currentX, currentY | |
print("movemouse: " + str(currentX) + " / " + str(currentY)) | |
if (win32api.SetCursorPos((currentX,currentY)) == True): | |
print(win32api.GetLastError()) | |
print("OK") | |
else: | |
print(win32api.GetLastError()) | |
print("NOT OK") | |
def leftClickMouse(): | |
global currentX, currentY | |
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,currentX,currentY,0,0) | |
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,currentX,currentY,0,0) | |
def rightClickMouse(): | |
global currentX, currentY | |
win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTDOWN,currentX,currentY,0,0) | |
win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTUP,currentX,currentY,0,0) | |
def middleClickMouse(): | |
global currentX, currentY | |
win32api.mouse_event(win32con.MOUSEEVENTF_MIDDLEDOWN,currentX,currentY,0,0) | |
win32api.mouse_event(win32con.MOUSEEVENTF_MIDDLEUP,currentX,currentY,0,0) | |
def leftMouseDown(): | |
global currentX, currentY | |
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,currentX,currentY,0,0) | |
def rightMouseDown(): | |
global currentX, currentY | |
win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTDOWN,currentX,currentY,0,0) | |
def releaseMouseDown(): | |
global currentX, currentY | |
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,currentX,currentY,0,0) | |
win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTUP,currentX,currentY,0,0) | |
def enum(name, **enums): | |
return Enum(name, enums) | |
vkeys_map = { | |
"RSHIFT": ["SHIFT"], | |
"LSHIFT": ["SHIFT"], | |
"RALT": ["ALT"], | |
"LALT": ["ALT"], | |
"RCTRL": ["CTRL"], | |
"LCTRL": ["CTRL"], | |
} | |
def getKeyStrokeState (name): | |
global keystrokes_state | |
state = keystrokes_state.get(name, None) | |
if state is None: | |
state = {"down":False} | |
keystroke = keystrokemap[name] | |
keystate = unpack("H", pack("h", win32api.GetKeyState(keystroke.keywin32)))[0] | |
state['down'] = (keystate & 1<<15) != 0 | |
if name == "CAPSLOCK": | |
state['locked'] = (keystate & 1) == 1 | |
keystrokes_state[name] = state | |
return state | |
def onRecheckKeyboardState (name): | |
global keystrokes_state | |
state = getKeyStrokeState(name) | |
keystroke = keystrokemap[name] | |
keystate = unpack("H", pack("h", win32api.GetKeyState(keystroke.keywin32)))[0] | |
changed = False | |
if ((keystate & 1<<15) != 0) != state['down']: | |
state['down'] = not state['down'] | |
changed = True | |
if name == "CAPSLOCK": | |
if ((keystate & 1) == 1) != state['locked']: | |
state['locked'] = not state['locked'] | |
changed = True | |
if changed: | |
onKeyboardEvent(None) | |
def updateKeyboardState (event): | |
keydown = True if event.Message in (win32con.WM_KEYDOWN, win32con.WM_SYSKEYDOWN) else False | |
ckeystrokes = list(filter(lambda a:a.keywin32 == event.KeyID, keystrokes)) | |
if len(ckeystrokes) > 0: | |
for skey in vkeys_map.get(ckeystrokes[0].name, []): | |
ckeystrokes.append(keystrokemap[skey]) | |
for akeystroke in ckeystrokes: | |
state = getKeyStrokeState(akeystroke.name) | |
state["down"] = keydown | |
if akeystroke.name == "CAPSLOCK" and keydown: | |
if event.Time - state.get('__lasttime', 0) > 300: | |
state["locked"] = not state.get("locked", False) | |
state['__lasttime'] = event.Time | |
keyFeedbackTimer = threading.Timer(0.5, onRecheckKeyboardState, [akeystroke.name]) | |
keyFeedbackTimer.start() | |
def OnKeyboardEventUp(event): | |
global lastkeydowntime, MyEvents, currentCharacter, endCharacterTimer, hm, disabled | |
updateKeyboardState(event) | |
onKeyboardEvent(event) | |
if pressingKey: | |
return True | |
if myConfig['off']: | |
return True | |
if (disabled): | |
return False | |
if myConfig['debug']: | |
print("Key up: ", event.Key) | |
#print 'MessageName:',event.MessageName | |
#print 'Time:',event.Time | |
#print 'Ascii:', event.Ascii, chr(event.Ascii) | |
#print 'Key:', event.Key | |
#print 'KeyID:', event.KeyID | |
#print 'ScanCode:', event.ScanCode | |
#print(str(lastkeydowntime)) | |
keys = (myConfig['keyone'],) if myConfig['keylen'] == 1 else \ | |
(myConfig['keyone'], myConfig['keytwo']) | |
tmp = tuple(map(lambda a:a[1], filter(lambda a:a[0].keywin32==event.KeyID, zip(keys, range(len(keys)))))) | |
if len(tmp) == 0: # is not the target key | |
return True | |
keyidx = tmp[0] | |
msSinceLastKeyDown = event.Time - lastkeydowntime | |
lastkeydowntime = -1 | |
if myConfig['keylen'] != 3: | |
endCharacterTimer = threading.Timer(float(myConfig['minLetterPause'])/1000.0, endCharacter) | |
#print str(float(myConfig['minLetterPause'])) | |
endCharacterTimer.start() | |
if (myConfig['keylen'] == 1): | |
if (msSinceLastKeyDown < float(myConfig['maxDitTime'])): | |
addDit() | |
if (myConfig['withsound']): | |
winsound.MessageBeep(myConfig['SoundDit']) | |
#print(currentCharacter) | |
else: | |
addDah() | |
if (myConfig['withsound']): | |
winsound.MessageBeep(myConfig['SoundDah']) | |
#print(currentCharacter) | |
if myConfig['debug']: | |
print("Duration of keypress: " + str(msSinceLastKeyDown)) | |
else: | |
if keyidx == 0: | |
addDit() | |
if (myConfig['withsound']): | |
winsound.MessageBeep(myConfig['SoundDit']) | |
elif keyidx == 1: | |
addDah() | |
if (myConfig['withsound']): | |
winsound.MessageBeep(myConfig['SoundDah']) | |
hm.KeyDown = OnKeyboardEventDown | |
hm.KeyUp = disableKeyUpDown | |
return False | |
def addDit(): | |
currentCharacter.append(MyEvents.DIT.value) | |
if (myConfig['withsound']): | |
#winsound.Beep(int(myConfig['SoundDitFrequency']), int(myConfig['SoundDitDuration'])) | |
winsound.MessageBeep(myConfig.get('SoundDit', -1)) | |
#winsound.Beep(440, 33) | |
#combos = getPossibleCombos(currentCharacter) | |
codeslayoutview.Dit() | |
def addDah(): | |
currentCharacter.append(MyEvents.DAH.value) | |
if (myConfig['withsound']): | |
#winsound.Beep(int(myConfig['SoundDitFrequency']), int(myConfig['SoundDitDuration'])) | |
winsound.MessageBeep(myConfig.get('SoundDah', -1)) | |
#winsound.Beep(440, 100) | |
#combos = getPossibleCombos(currentCharacter) | |
codeslayoutview.Dah() | |
def getPossibleCombos(currentCharacter): | |
x = "" | |
for i in currentCharacter: | |
x += str(i) | |
possibleactions = [] | |
for action in normalmapping: | |
if (len(action) >= len(x) and action[:len(x)] == x): | |
possibleactions.append(action) | |
print("possible: " + str(possibleactions)) | |
def newConfig (): | |
return {} | |
def saveConfig (configfile, config): | |
data = dict() | |
data.update(config) | |
for name in ['keyone', 'keytwo', 'keythree']: | |
if name in data: | |
data[name] = data[name].name | |
with open(configfile, "w") as f: | |
f.write(json.dumps(data, indent=2)) | |
def readConfig (configfile): | |
with open(configfile, "r") as f: | |
data = json.loads(f.read()) | |
for name in ['keyone', 'keytwo', 'keythree']: | |
if name in data: | |
data[name] = keystrokemap[data[name]] | |
return data | |
class Action (object): | |
def __init__ (self, item): | |
self.item = item | |
def getlabel (self): | |
return self.item['label'] if 'label' in self.item else "" | |
def perform (self): | |
pass | |
class ActionLegacy (Action): | |
def __init__ (self, item, key, label): | |
super(ActionLegacy, self).__init__(item) | |
self.key = key | |
self.label = label | |
def getlabel (self): | |
return self.label | |
def perform (self): | |
global repeaton, repeatkey | |
key = self.key | |
if (key == actions.MOUSEUP5.value[1]): | |
currentY += -5 | |
moveMouse() | |
elif (key == actions.MOUSEDOWN5.value[1]): | |
currentY += 5 | |
moveMouse() | |
elif (key == actions.MOUSELEFT5.value[1]): | |
currentX += -5 | |
moveMouse() | |
elif (key == actions.MOUSERIGHT5.value[1]): | |
currentX += 5 | |
moveMouse() | |
elif (key == actions.MOUSEUPLEFT5.value[1]): | |
currentX += -5 | |
currentY += -5 | |
moveMouse() | |
elif (key == actions.MOUSEUPRIGHT5.value[1]): | |
currentX += 5 | |
currentY += -5 | |
moveMouse() | |
elif (key == actions.MOUSEDOWNLEFT5.value[1]): | |
currentX += -5 | |
currentY += 5 | |
moveMouse() | |
elif (key == actions.MOUSEDOWNRIGHT5.value[1]): | |
currentX += 5 | |
currentY += 5 | |
moveMouse() | |
elif (key == actions.MOUSEUP40.value[1]): | |
currentY += -40 | |
moveMouse() | |
elif (key == actions.MOUSEDOWN40.value[1]): | |
currentY += 40 | |
moveMouse() | |
elif (key == actions.MOUSELEFT40.value[1]): | |
currentX += -40 | |
moveMouse() | |
elif (key == actions.MOUSERIGHT40.value[1]): | |
currentX += 40 | |
moveMouse() | |
elif (key == actions.MOUSEUPLEFT40.value[1]): | |
currentX += -40 | |
currentY += -40 | |
moveMouse() | |
elif (key == actions.MOUSEUPRIGHT40.value[1]): | |
currentX += 40 | |
currentY += -40 | |
moveMouse() | |
elif (key == actions.MOUSEDOWNLEFT40.value[1]): | |
currentX += -40 | |
currentY += 40 | |
moveMouse() | |
elif (key == actions.MOUSEDOWNRIGHT40.value[1]): | |
currentX += 40 | |
currentY += 40 | |
moveMouse() | |
elif (key == actions.MOUSEUP250.value[1]): | |
currentY += -250 | |
moveMouse() | |
elif (key == actions.MOUSEDOWN250.value[1]): | |
currentY += 250 | |
moveMouse() | |
elif (key == actions.MOUSELEFT250.value[1]): | |
currentX += -250 | |
moveMouse() | |
elif (key == actions.MOUSERIGHT250.value[1]): | |
currentX += 250 | |
moveMouse() | |
elif (key == actions.MOUSEUPLEFT250.value[1]): | |
currentX += -250 | |
currentY += -250 | |
moveMouse() | |
elif (key == actions.MOUSEUPRIGHT250.value[1]): | |
currentX += 250 | |
currentY += -250 | |
moveMouse() | |
elif (key == actions.MOUSEDOWNLEFT250.value[1]): | |
currentX += -250 | |
currentY += 250 | |
moveMouse() | |
elif (key == actions.MOUSEDOWNRIGHT250.value[1]): | |
currentX += 250 | |
currentY += 250 | |
moveMouse() | |
elif (key == actions.MOUSECLICKLEFT.value[1]): | |
leftClickMouse() | |
elif (key == actions.MOUSECLICKRIGHT.value[1]): | |
rightClickMouse() | |
elif (key == actions.MOUSECLKHLDLEFT.value[1]): | |
leftMouseDown() | |
elif (key == actions.MOUSECLKHLDRIGHT.value[1]): | |
rightMouseDown() | |
elif (key == actions.MOUSERELEASEHOLD.value[1]): | |
releaseMouseDown() | |
elif (key == actions.REPEATMODE.value[1]): | |
if (repeaton == True): | |
if myConfig['debug']: | |
print("repeat OFF") | |
repeaton = False | |
repeatkey = None | |
else: | |
if myConfig['debug']: | |
print("repeat ON") | |
repeaton = True | |
class KeyStroke (object): | |
def __init__ (self, name, label, keywin32, character): | |
self.name = name | |
self.label = label | |
self.keywin32 = keywin32 | |
self.character = character | |
class ActionKeyStroke (Action): | |
def __init__ (self, item, key, toggle_action=False): | |
super(ActionKeyStroke, self).__init__(item) | |
self.key = key | |
self.toggle_action = toggle_action | |
def getlabel (self): | |
return self.key.label | |
def perform (self): | |
key = self.key.keywin32 | |
if self.toggle_action: | |
isdown = getKeyStrokeState(self.key.name)["down"] | |
if isdown: | |
PressKey(False, key) | |
else: | |
PressKey(True, key) | |
else: | |
if (repeaton): | |
if (repeatkey == None): | |
repeatkey = key | |
else: | |
if myConfig['debug']: | |
print("repeat code: ", repeatkey, " + ", key) | |
PressKey(True, repeatkey) | |
TypeKey(key) | |
PressKey(False, repeatkey) | |
else: | |
if myConfig['debug']: | |
print("code found: ", key) | |
#win32api.VkKeyScan('1') | |
TypeKey(key) | |
if typestate != None: | |
if self.key.name == "BACKSPACE": | |
typestate.popchar() | |
elif self.key.character != None: | |
typestate.pushchar(self.key.character) | |
class ChangeLayoutAction (Action): | |
def perform (self): | |
global typestate | |
if 'target' not in self.item or self.item['target'] not in layoutmanager.layouts: | |
raise AssertionError("target({}) not found".format(self.item.get('target', ""))) | |
# special case for `typing` target | |
if self.item['target'] == 'typing': | |
typestate = TypeState() | |
else: | |
typestate = None | |
layoutmanager.set_active(layoutmanager.layouts[self.item['target']]) | |
codeslayoutview.changeLayoutSignal.emit() | |
class PredictionSelectLayoutAction (Action): | |
def getlabel (self): | |
if typestate != None: | |
target = self.item['target'] | |
predictions = typestate.getpredictions() | |
if target >= 0 and target < len(predictions): | |
return predictions[target] | |
return "" | |
def perform (self): | |
if typestate != None: | |
target = self.item['target'] | |
predictions = typestate.getpredictions() | |
if target >= 0 and target < len(predictions): | |
pred = predictions[target] | |
plen = len(pred) | |
print("PRED", predictions) | |
if plen > 0: | |
stripsuffix = "" | |
for i in range(plen): | |
idx = len(typestate.text) - i | |
if idx == 0 or (idx > 0 and typestate.text[idx - 1] in [" ", "\n", "\t", "\r"]): | |
stripsuffix = typestate.text[idx:] | |
break | |
newchars = pred[len(stripsuffix):] + " " | |
typestate.text = typestate.text[:len(typestate.text)-len(stripsuffix)] + newchars | |
for char in newchars: | |
keys = tuple(filter(lambda a: a.character == char, keystrokes)) | |
if len(keys) == 0: | |
break | |
if ord(keys[0].character) == 8 and len(typestate.text) == 0: # special case | |
continue | |
TypeKey(keys[0].keywin32) | |
def initActions(): | |
global actions, keystrokes, keystrokemap | |
keystrokesdata = list(map(lambda a: (KeyStroke(*a[:4]), a[4:]), ( | |
("A", "a", win32api.VkKeyScan("a"), "a"), ("B", "b", win32api.VkKeyScan("b"), "b"), ("C", "c", win32api.VkKeyScan("c"), "c"), ("D", "d", win32api.VkKeyScan("d"), "d"), | |
("E", "e", win32api.VkKeyScan("e"), "e"), ("F", "f", win32api.VkKeyScan("f"), "f"), ("G", "g", win32api.VkKeyScan("g"), "g"), ("H", "h", win32api.VkKeyScan("h"), "h"), | |
("I", "i", win32api.VkKeyScan("i"), "i"), ("J", "j", win32api.VkKeyScan("j"), "j"), ("K", "k", win32api.VkKeyScan("k"), "k"), ("L", "l", win32api.VkKeyScan("l"), "l"), | |
("M", "m", win32api.VkKeyScan("m"), "m"), ("N", "n", win32api.VkKeyScan("n"), "n"), ("O", "o", win32api.VkKeyScan("o"), "o"), ("P", "p", win32api.VkKeyScan("p"), "p"), | |
("Q", "q", win32api.VkKeyScan("q"), "q"), ("R", "r", win32api.VkKeyScan("r"), "r"), ("S", "s", win32api.VkKeyScan("s"), "s"), ("T", "t", win32api.VkKeyScan("t"), "t"), | |
("U", "u", win32api.VkKeyScan("u"), "u"), ("V", "v", win32api.VkKeyScan("v"), "v"), ("W", "w", win32api.VkKeyScan("w"), "w"), ("X", "x", win32api.VkKeyScan("x"), "x"), | |
("Y", "y", win32api.VkKeyScan("y"), "y"), ("Z", "z", win32api.VkKeyScan("z"), "z"), ("ONE", "1", win32api.VkKeyScan("1"), "1"), ("TWO", "2", win32api.VkKeyScan("2"), "2"), | |
("THREE", "3", win32api.VkKeyScan("3"), "3"), ("FOUR", "4", win32api.VkKeyScan("4"), "4"), ("FIVE", "5", win32api.VkKeyScan("5"), "5"), | |
("SIX", "6", win32api.VkKeyScan("6"), "6"), | |
("SEVEN", "7", win32api.VkKeyScan("7"), "7"), ("EIGHT", "8", win32api.VkKeyScan("8"), "8"), ("NINE", "9", win32api.VkKeyScan("9"), "9"), | |
("ZERO", "0", win32api.VkKeyScan("0"), "0"), ("DOT", ".", win32api.VkKeyScan("."), "."), ("COMMA", ",", win32api.VkKeyScan(","), ","), | |
("QUESTION", "?", win32api.VkKeyScan("?"), "?"), ("EXCLAMATION", "!", win32api.VkKeyScan("!"), "!"), ("COLON", ":", win32api.VkKeyScan(":"), ":"), | |
("SEMICOLON", ";", win32api.VkKeyScan(";"), ";"), ("AT", "@", win32api.VkKeyScan("@"), "@"), ("BASH", "#", win32api.VkKeyScan("#"), "#"), | |
("DOLLAR", "$", win32api.VkKeyScan("$"), "$"), ("PERCENT", "%", win32api.VkKeyScan("%"), "%"), ("AMPERSAND", "&", win32api.VkKeyScan("&"), "&"), | |
("STAR", "*", win32con.VK_MULTIPLY, "*"), ("PLUS", "+", win32con.VK_ADD, "+"), ("MINUS", "-", win32con.VK_SUBTRACT, "-"), | |
("EQUALS", "=", win32api.VkKeyScan("="), "="), ("FSLASH", "/", win32api.VkKeyScan("/"), "/"), ("BSLASH", "\\", win32api.VkKeyScan("\\"), "\\"), | |
("SINGLEQUOTE", "\'", win32api.VkKeyScan("\'"), "\'"), ("DOUBLEQUOTE", "\"", win32api.VkKeyScan("\""), "\""), ("OPENBRACKET", "(", win32api.VkKeyScan("("), "("), | |
("CLOSEBRACKET", ")", win32api.VkKeyScan(")"), ")"), ("LESSTHAN", "<", win32api.VkKeyScan("<"), "<"), ("MORETHAN", ">", win32api.VkKeyScan(">"), ">"), | |
("CIRCONFLEX", "^", win32api.VkKeyScan("^"), "^"), ("ENTER", "ENTER", win32con.VK_RETURN, "\n"), ("SPACE", "space", win32con.VK_SPACE, " "), | |
("BACKSPACE", "bckspc", win32con.VK_BACK, chr(8)), ("TAB", "tab", win32con.VK_TAB, "\t"), ("TABLEFT", "tab", win32con.VK_TAB, "\t"), | |
("UNDERSCORE", "_", win32api.VkKeyScan("_"), "_"), ("PAGEUP", "pageup", win32con.VK_PRIOR, None), ("PAGEDOWN", "pagedwn", win32con.VK_NEXT, None), | |
("LEFTARROW", "left", win32con.VK_LEFT, None), ("RIGHTARROW", "right", win32con.VK_RIGHT, "right"), | |
("UPARROW", "up", win32con.VK_UP, "up"), ("DOWNARROW", "down", win32con.VK_DOWN, "down"), ("ESCAPE", "esc", win32con.VK_ESCAPE, "esc"), ("HOME", "home", win32con.VK_HOME, "home"), | |
("END", "end", win32con.VK_END, None), ("INSERT", "insert", win32con.VK_INSERT, None), ("DELETE", "del", win32con.VK_DELETE, None), | |
("STARTMENU", "start", win32con.VK_MENU, None), ("SHIFT", "shift", win32con.VK_SHIFT, None, True), ("ALT", "alt", win32con.VK_MENU, None, True), | |
("CTRL", "ctrl", win32con.VK_CONTROL, None, True), ("WINDOWS", "win", win32con.VK_LWIN, None), ("APPKEY", "app", win32con.VK_LWIN, None), | |
("LCTRL", "left ctrl", win32con.VK_LCONTROL, None), ("RCTRL", "right ctrl", win32con.VK_RCONTROL, None), | |
("LSHIFT", "left shift", win32con.VK_LSHIFT, None), ("RSHIFT", "right shift", win32con.VK_RSHIFT, None), | |
("RALT", "right alt", win32con.VK_RMENU, None), ("LALT", "alt", win32con.VK_LMENU, None), | |
("CAPSLOCK", "caps", win32con.VK_CAPITAL, None), | |
("F1", "F1", win32con.VK_F1, None), ("F2", "F2", win32con.VK_F2, None), ("F3", "F3", win32con.VK_F3, None), ("F4", "F4", win32con.VK_F4, None), ("F5", "F5", win32con.VK_F5, None), | |
("F6", "F6", win32con.VK_F6, None), ("F7", "F7", win32con.VK_F7, None), ("F8", "F8", win32con.VK_F8, None), ("F9", "F9", win32con.VK_F9, None), ("F10", "F10", win32con.VK_F10, None), | |
("F11", "F11", win32con.VK_F11, None), ("F12", "F12", win32con.VK_F12, None)))) | |
keystrokes = list(map(lambda a: a[0], keystrokesdata)) | |
keystrokemap = dict(map(lambda a: (a.name, a), keystrokes)) | |
actionskwargs = dict(map(lambda a: (a[0], (ActionLegacy, a[1], a[2])), | |
(("REPEATMODE", 0, "repeat"), ("SOUND", 8, "snd"), ("CODESET", 9, "code"), | |
("MOUSERIGHT5", 2, "ms right 5"), | |
("MOUSEUP5", 3, "ms up 5"), ("MOUSECLICKLEFT", 4, "ms clkleft"), ("MOUSEDBLCLICKLEFT", 5, "ms dblclkleft"), | |
("MOUSECLKHLDLEFT", 6, "ms hldleft"), ("MOUSEUPLEFT5", 7, "ms leftup 5"), ("MOUSEDOWNLEFT5", 8, "ms leftdown 5"), | |
("MOUSERELEASEHOLD", 9, "ms release"), ("MOUSELEFT5", 0, "ms left 5"), ("MOUSEDOWN5", 1, "ms down 5"), ("MOUSECLICKRIGHT", 2, "ms clkright"), | |
("MOUSEDBLCLICKRIGHT", 3, "ms dblclkright"), ("MOUSECLKHLDRIGHT", 4, "ms hldright"), ("MOUSEUPRIGHT5", 5, "ms rightup 5"), | |
("MOUSEDOWNRIGHT5", 6, "ms rightdown 5"), ("NORMALMODE", 7, "normal mode"), ("MOUSEUP40", 8, "ms up 40"), ("MOUSEUP250", 9, "ms up 250"), | |
("MOUSEDOWN40", 0, "ms down 40"), ("MOUSEDOWN250", 1, "ms down 250"), ("MOUSELEFT40", 2, "ms left 40"), ("MOUSELEFT250", 3, "ms left 250"), | |
("MOUSERIGHT40", 4, "ms right 40"), ("MOUSERIGHT250", 5, "ms right 250"), ("MOUSEUPLEFT40", 6, "ms leftup 40"), | |
("MOUSEUPLEFT250", 7, "ms leftup 250"), ("MOUSEDOWNLEFT40", 8, "ms leftdown 40"), ("MOUSEDOWNLEFT250", 9, "ms leftdown 250"), | |
("MOUSEUPRIGHT40", 0, "ms rightup 40"), ("MOUSEUPRIGHT250", 1, "ms rightup 250"), ("MOUSEDOWNRIGHT40", 2, "ms rightdown 40"), | |
("MOUSEDOWNRIGHT250", 3, "ms rightdown 250")) | |
)) | |
actionskwargs.update(dict( | |
CHANGELAYOUT=(ChangeLayoutAction,), PREDICTION_SELECT=(PredictionSelectLayoutAction,) | |
)) | |
actionskwargs.update(dict(map(lambda a: (a[0].name, (ActionKeyStroke,) + a), keystrokesdata))) | |
actions = enum('Actions', **actionskwargs) | |
''' | |
normalmapping = OrderedDict([('12',actions.A), ('2111',actions.B), ('2121',actions.C), ('211',actions.D), ('1',actions.E), ('1121',actions.F), | |
('221',actions.G), ('1111',actions.H), ('11',actions.I), ('1222',actions.J), ('212',actions.K), ('1211',actions.L), | |
('22',actions.M), ('21',actions.N), ('222',actions.O), ('1221',actions.P), ('2212',actions.Q), ('121',actions.R), | |
('111',actions.S), ('2',actions.T), ('112',actions.U), ('1112',actions.V), ('122',actions.W), ('2112',actions.X), | |
('2122',actions.Y), ('2211', actions.Z), ('12222',actions.ONE), ('11222',actions.TWO), ('11122',actions.THREE), | |
('11112',actions.FOUR), ('11111',actions.FIVE), ('21111',actions.SIX), ('22111',actions.SEVEN), ('22211',actions.EIGHT), | |
('22221',actions.NINE), ('22222',actions.ZERO), ('121212',actions.DOT), ('221122',actions.COMMA), | |
('112211',actions.QUESTION), ('121122',actions.EXCLAMATION), ('212121',actions.COLON), ('11121',actions.SEMICOLON), | |
('12221',actions.AT), ('21222',actions.BASH), ('211121',actions.DOLLAR), ('122121',actions.PERCENT), | |
('21122',actions.AMPERSAND), ('12111',actions.STAR), ('12211',actions.PLUS), ('2221',actions.MINUS), | |
('12212',actions.EQUALS), ('22112',actions.FSLASH), ('211111',actions.BSLASH), ('121221',actions.SINGLEQUOTE), | |
('22122',actions.DOUBLEQUOTE), ('111221',actions.OPENBRACKET), ('211221',actions.CLOSEBRACKET), | |
('121112',actions.LESSTHAN), ('221121',actions.MORETHAN), ('212112',actions.CIRCONFLEX), ('1212',actions.ENTER), | |
('1122',actions.SPACE), ('2222',actions.BACKSPACE), ('21221',actions.TAB), ('221211',actions.TABLEFT), | |
('11221',actions.UNDERSCORE), ('222112',actions.PAGEUP), ('222121',actions.PAGEDOWN), ('222212',actions.LEFTARROW), | |
('222221',actions.RIGHTARROW), ('222211',actions.UPARROW), ('222222',actions.DOWNARROW), ('11211',actions.ESCAPE), | |
('111121',actions.HOME), ('21211',actions.END), ('12112',actions.INSERT), ('21121',actions.DELETE), | |
('221111',actions.STARTMENU), ('11212',actions.SHIFT), ('12122',actions.ALT), ('21212',actions.CTRL), | |
('112122',actions.WINDOWS), ('211122',actions.APPKEY), ('112121',actions.CAPSLOCK), ('22121',actions.MOUSEMODE), | |
('21112',actions.NUMBERMODE), ('121121',actions.REPEATMODE), ('121211',actions.SOUND), ('22212',actions.CODESET), | |
('112222',actions.F1), ('111222',actions.F2), ('111122',actions.F3), ('111112',actions.F4), ('111111',actions.F5), | |
('121111',actions.F6), ('122111',actions.F7), ('122211',actions.F8), ('122221',actions.F9), ('122222',actions.F10), | |
('212222',actions.F11), ('211222',actions.F12)]) | |
mousemapping = OrderedDict([('1', actions.MOUSERIGHT5), ('21', actions.MOUSERIGHT40), ('121', actions.MOUSERIGHT250), | |
('2', actions.MOUSELEFT5), ('22', actions.MOUSELEFT40), ('122', actions.MOUSELEFT250), | |
('11', actions.MOUSEUP5), ('111', actions.MOUSEUP40), ('211', actions.MOUSEUP250), | |
('12', actions.MOUSEDOWN5), ('112', actions.MOUSEDOWN40), ('212', actions.MOUSEDOWN250), | |
('221', actions.MOUSEUPRIGHT5), ('1121', actions.MOUSEUPRIGHT40), ('1221', actions.MOUSEUPRIGHT250), | |
('222', actions.MOUSEDOWNRIGHT5), ('1122', actions.MOUSEDOWNRIGHT40), ('1222', actions.MOUSEDOWNRIGHT250), | |
('1111', actions.MOUSEUPLEFT5), ('1211', actions.MOUSEUPLEFT40), ('2111', actions.MOUSEUPLEFT250), | |
('1112', actions.MOUSEDOWNLEFT5), ('1212', actions.MOUSEDOWNLEFT40), ('2112', actions.MOUSEDOWNLEFT250), | |
('2121',actions.MOUSECLICKLEFT), ('2122',actions.MOUSEDBLCLICKLEFT), | |
('2211',actions.MOUSECLKHLDLEFT), ('2212',actions.MOUSERELEASEHOLD), ('2221',actions.MOUSECLICKRIGHT), | |
('2222',actions.MOUSEDBLCLICKRIGHT), ('11111',actions.MOUSECLKHLDRIGHT), ('22121',actions.NORMALMODE)]) | |
numbermapping = OrderedDict([('1',actions.ONE), ('2',actions.TWO), ('12',actions.THREE), ('11',actions.FOUR), ('21',actions.FIVE), ('22',actions.SIX), | |
('122',actions.SEVEN), ('112',actions.EIGHT), ('111',actions.NINE), ('211',actions.ZERO), ('221',actions.PLUS), | |
('222',actions.MINUS), ('212',actions.FSLASH), ('121',actions.STAR), ('1212',actions.ENTER), ('121212',actions.DOT)]) | |
aitems = list(actions) | |
def aname (map, key): | |
return map[key].name | |
maps = {} | |
maps["main"] = { | |
"items": list("{{ \"code\": \"{}\", \"action\": \"{}\" }}".format(key, aname(normalmapping, key)) for key in normalmapping) | |
} | |
maps["mouse"] = { | |
"items": list("{{ \"code\": \"{}\", \"action\": \"{}\" }}".format(key, aname(mousemapping, key)) for key in mousemapping) | |
} | |
maps["number"] = { | |
"items": list("{{ \"code\": \"{}\", \"action\": \"{}\" }}".format(key, aname(numbermapping, key)) for key in numbermapping) | |
} | |
with open("codemaps.json", "w") as f: | |
f.write(json.dumps({ "maps": maps, "mainmap": "main" }, indent=2)) | |
''' | |
def Init(): | |
global MyEvents, currentCharacter, hm, repeaton, repeatkey, codeslayoutview, typestate | |
MyEvents = enum('DITDAH', DIT=1, DAH=2) | |
currentCharacter = [] | |
repeaton = False | |
repeatkey = None | |
layoutmanager.set_active(layoutmanager.mainlayout) # special case for `typing` target | |
if layoutmanager.mainlayoutname == 'typing': | |
typestate = TypeState() | |
else: | |
typestate = None | |
codeslayoutview = CodesLayoutViewWidget(layoutmanager.active) | |
def Go(): | |
global hm | |
hm = PyHook3.HookManager() | |
hm.KeyDown = OnKeyboardEventDown | |
hm.KeyUp = OnKeyboardEventUp | |
hm.HookKeyboard() | |
pythoncom.PumpMessages() | |
class Window(QDialog): | |
def __init__(self): | |
super(Window, self).__init__() | |
self.createIconGroupBox() | |
self.createActions() | |
self.createTrayIcon() | |
self.trayIcon.activated.connect(self.iconActivated) | |
self.GOButton.clicked.connect(self.goForIt) | |
self.SaveButton.clicked.connect(self.saveSettings) | |
self.withSound.clicked.connect(self.updateAudioProperties) | |
mainLayout = QVBoxLayout() | |
mainLayout.addWidget(self.iconGroupBox) | |
self.setLayout(mainLayout) | |
self.setIcon() | |
self.trayIcon.show() | |
self.setWindowTitle("MorseWriter V2.1") | |
self.resize(400, 300) | |
def updateAudioProperties(self): | |
if self.withSound.isChecked(): | |
self.iconComboBoxSoundDit.setEnabled(True) | |
self.iconComboBoxSoundDah.setEnabled(True) | |
else: | |
self.iconComboBoxSoundDit.setEnabled(False) | |
self.iconComboBoxSoundDah.setEnabled(False) | |
def start (self, config): | |
global myConfig | |
myConfig = dict() | |
myConfig.update(config) | |
myConfig['fontsizescale'] = int(config['fontsizescale']) / 100.0 | |
Init() | |
Go() | |
def collectConfig (self): | |
config = dict(); | |
config['keylen'] = 1 if self.keySelectionRadioOneKey.isChecked() else \ | |
2 if self.keySelectionRadioTwoKey.isChecked() else \ | |
3 if self.keySelectionRadioThreeKey.isChecked() else 1 | |
config['keyone'] = self.iconComboBoxKeyOne.itemData(self.iconComboBoxKeyOne.currentIndex()) | |
config['keytwo'] = self.iconComboBoxKeyTwo.itemData(self.iconComboBoxKeyTwo.currentIndex()) | |
config['keythree'] = self.iconComboBoxKeyThree.itemData(self.iconComboBoxKeyThree.currentIndex()) | |
config['maxDitTime'] = self.maxDitTimeEdit.text() | |
config['minLetterPause'] = self.minLetterPauseEdit.text() | |
config['withsound'] = self.withSound.isChecked() | |
config['SoundDit'] = self.iconComboBoxSoundDit.itemData(self.iconComboBoxSoundDit.currentIndex()) | |
config['SoundDah'] = self.iconComboBoxSoundDah.itemData(self.iconComboBoxSoundDah.currentIndex()) | |
config['debug'] = self.withDebug.isChecked() | |
config['off'] = False | |
config['fontsizescale'] = self.fontSizeScaleEdit.text() | |
config['upperchars'] = self.upperCharsCheck.isChecked() | |
config['autostart'] = self.autostartCheckbox.isChecked() | |
config['winxaxis'] = "left" if self.keyWinPosXLeftRadio.isChecked() else "right" | |
config['winyaxis'] = "top" if self.keyWinPosYTopRadio.isChecked() else "bottom" | |
config['winposx'] = self.keyWinPosXEdit.text() | |
config['winposy'] = self.keyWinPosYEdit.text() | |
return config | |
def goForIt(self): | |
global myConfig | |
if self.trayIcon.isVisible(): | |
QMessageBox.information(self, "MorseWriter", "The program will run in the system tray. To terminate the program, choose <b>Quit</b> in the context menu of the system tray entry.") | |
self.hide() | |
myConfig = self.collectConfig() | |
myConfig['fontsizescale'] = int(myConfig['fontsizescale']) / 100.0 | |
if myConfig['debug']: | |
print("Config: " + str(myConfig['keylen']) + " / " + str(myConfig['keyone']) + " / " + str(myConfig['keytwo']) + " / " + str(myConfig['keythree']) + " / " + str(myConfig['maxDitTime']) + " / " + str(myConfig['minLetterPause'])) | |
Init() | |
Go() | |
def closeEvent(self, event): | |
app.quit | |
sys.exit() | |
def setIcon(self): | |
icon = QIcon(':/morse-writer.ico') | |
self.trayIcon.setIcon(icon) | |
self.setWindowIcon(icon) | |
def iconActivated(self, reason): | |
#if reason in (QSystemTrayIcon.Trigger, QSystemTrayIcon.DoubleClick): | |
# self.iconComboBox.setCurrentIndex((self.iconComboBox.currentIndex() + 1) % self.iconComboBox.count()) | |
if reason == QSystemTrayIcon.MiddleClick: | |
self.showMessage() | |
def showMessage(self): | |
icon = QSystemTrayIcon.MessageIcon(self.typeComboBox.itemData(self.typeComboBox.currentIndex())) | |
self.trayIcon.showMessage(self.titleEdit.text(), self.bodyEdit.toPlainText(), icon, self.durationSpinBox.value() * 1000) | |
def mkKeyStrokeComboBox (self, items, currentkey, valuedict=None): | |
box = QComboBox() | |
for key, val in items: | |
box.addItem(key, valuedict[val] if valuedict is not None else val) | |
try: | |
values = list(map(lambda a:valuedict[a[1]] if valuedict is not None else a[1], items)) | |
box.setCurrentIndex(values.index(currentkey)) | |
except ValueError: | |
pass | |
return box | |
def createIconGroupBox(self): | |
self.iconGroupBox = QGroupBox("Input Settings") | |
self.keySelectionRadioOneKey = QRadioButton("One Key") | |
self.keySelectionRadioTwoKey = QRadioButton("Two Key") | |
self.keySelectionRadioThreeKey = QRadioButton("Three Key") | |
inputSettingsLayout = QVBoxLayout() | |
inputRadioGroup = QGroupBox("Number of keys") | |
inputRadioButtonsLayout = QHBoxLayout() | |
inputRadioButtonsLayout.addWidget(self.keySelectionRadioOneKey) | |
inputRadioButtonsLayout.addWidget(self.keySelectionRadioTwoKey) | |
inputRadioButtonsLayout.addWidget(self.keySelectionRadioThreeKey) | |
inputRadioGroup.setLayout(inputRadioButtonsLayout) | |
inputSettingsLayout.addWidget(inputRadioGroup) | |
inputKeyComboBoxesLayout = QHBoxLayout() | |
self.iconComboBoxKeyOne = self.mkKeyStrokeComboBox( | |
list(map(lambda a:(a,a),["SPACE", "ENTER", "ONE", "TWO", "Z", "X", "F8", "F9"])), | |
myConfig.get('keyone', None), keystrokemap | |
) | |
self.iconComboBoxKeyTwo = self.mkKeyStrokeComboBox( | |
list(map(lambda a:(a,a),["ENTER", "ONE", "TWO", "Z", "F8"])), | |
myConfig.get('keytwo', None), keystrokemap | |
) | |
self.iconComboBoxKeyThree = self.mkKeyStrokeComboBox( | |
[ ["Right Ctrl", "RCTRL"], ["Left Ctrl", "LCTRL"], ["Right Shift", "RSHIFT"], | |
["Left Shift", "LSHIFT"], ["Alt", "ALT"] ], | |
myConfig.get('keythree', None), keystrokemap | |
) | |
inputKeyComboBoxesLayout.addWidget(self.iconComboBoxKeyOne) | |
inputKeyComboBoxesLayout.addWidget(self.iconComboBoxKeyTwo) | |
inputKeyComboBoxesLayout.addWidget(self.iconComboBoxKeyThree) | |
inputSettingsLayout.addLayout(inputKeyComboBoxesLayout) | |
self.keySelectionRadioOneKey.toggled.connect(self.iconComboBoxKeyTwo.hide) | |
self.keySelectionRadioOneKey.toggled.connect(self.iconComboBoxKeyThree.hide) | |
self.keySelectionRadioTwoKey.toggled.connect(self.iconComboBoxKeyTwo.show) | |
self.keySelectionRadioTwoKey.toggled.connect(self.iconComboBoxKeyThree.hide) | |
self.keySelectionRadioThreeKey.toggled.connect(self.iconComboBoxKeyThree.show) | |
self.keySelectionRadioThreeKey.toggled.connect(self.iconComboBoxKeyTwo.show) | |
for index, name in [[1,'One'], [2,'Two'], [3,'Three']]: | |
getattr(self, 'keySelectionRadio%sKey'%(name)).setChecked(myConfig.get('keylen', 1) == index) | |
maxDitTimeLabel = QLabel("MaxDitTime (ms):") | |
self.maxDitTimeEdit = QLineEdit(myConfig.get("maxDitTime", "350")) | |
minLetterPauseLabel = QLabel("minLetterPause (ms):") | |
self.minLetterPauseEdit = QLineEdit(myConfig.get("minLetterPause", "1000")) | |
TimingsLayout = QGridLayout() | |
TimingsLayout.addWidget(maxDitTimeLabel, 0, 0) | |
TimingsLayout.addWidget(self.maxDitTimeEdit, 0, 1, 1, 4) | |
TimingsLayout.addWidget(minLetterPauseLabel, 1, 0) | |
TimingsLayout.addWidget(self.minLetterPauseEdit, 1, 1, 2, 4) | |
TimingsLayout.setRowStretch(4, 1) | |
inputSettingsLayout.addLayout(TimingsLayout) | |
self.withDebug = QCheckBox("Debug On") | |
self.withDebug.setChecked(myConfig.get("debug", False)) | |
inputSettingsLayout.addWidget(self.withDebug) | |
self.withSound = QCheckBox("Audible beeps") | |
self.withSound.setChecked(myConfig.get("withsound", True)) | |
inputSettingsLayout.addWidget(self.withSound) | |
fontSizeScaleLabel = QLabel("FontSize (%):") | |
self.fontSizeScaleEdit = QLineEdit(myConfig.get("fontsizescale", "100")) | |
viewSettingSec = QGridLayout() | |
viewSettingSec.addWidget(fontSizeScaleLabel, 0, 0) | |
viewSettingSec.addWidget(self.fontSizeScaleEdit, 0, 1, 1, 2) | |
self.upperCharsCheck = QCheckBox("Upper case chars") | |
self.upperCharsCheck.setChecked(myConfig.get("upperchars", True)) | |
viewSettingSec.addWidget(self.upperCharsCheck, 0, 3) | |
viewSettingSec.setRowStretch(4, 1) | |
inputSettingsLayout.addLayout(viewSettingSec) | |
self.iconComboBoxSoundDit = self.mkKeyStrokeComboBox([ | |
["MB_OK", winsound.MB_OK], | |
["MB_ICONQUESTION", winsound.MB_ICONQUESTION], | |
["MB_ICONHAND", winsound.MB_ICONHAND], | |
["MB_ICONEXCLAMATION", winsound.MB_ICONEXCLAMATION], | |
["MB_ICONASTERISK", winsound.MB_ICONASTERISK], | |
["DEFAULT", -1], | |
], myConfig.get('SoundDit', None)) | |
self.iconComboBoxSoundDah = self.mkKeyStrokeComboBox([ | |
["MB_OK", winsound.MB_OK], | |
["MB_ICONQUESTION", winsound.MB_ICONQUESTION], | |
["MB_ICONHAND", winsound.MB_ICONHAND], | |
["MB_ICONEXCLAMATION", winsound.MB_ICONEXCLAMATION], | |
["MB_ICONASTERISK", winsound.MB_ICONASTERISK], | |
["DEFAULT", -1], | |
], myConfig.get('SoundDah', None)) | |
DitSoundLabel = QLabel("Dit sound: ") | |
DahSoundLabel = QLabel("Dah sound: ") | |
SoundConfigLayout = QGridLayout() | |
SoundConfigLayout.addWidget(DitSoundLabel, 0, 0) | |
SoundConfigLayout.addWidget(self.iconComboBoxSoundDit, 0, 1, 1, 4) | |
SoundConfigLayout.addWidget(DahSoundLabel, 1, 0) | |
SoundConfigLayout.addWidget(self.iconComboBoxSoundDah, 1, 1, 1, 4) | |
self.autostartCheckbox = QCheckBox("Auto-Start") | |
self.autostartCheckbox.setChecked(myConfig.get("autostart", True)) | |
inputSettingsLayout.addWidget(self.autostartCheckbox) | |
inputSettingsLayout.addLayout(SoundConfigLayout) | |
inputRadioGroup = QGroupBox("Align window horizontally from") | |
posAxisLayout = QHBoxLayout() | |
self.keyWinPosXLeftRadio = QRadioButton("Left") | |
self.keyWinPosXRightRadio = QRadioButton("Right") | |
if myConfig.get("winxaxis", "left") == "left": | |
self.keyWinPosXLeftRadio.setChecked(True) | |
else: | |
self.keyWinPosXRightRadio.setChecked(True) | |
posAxisLayout.addWidget(self.keyWinPosXLeftRadio) | |
posAxisLayout.addWidget(self.keyWinPosXRightRadio) | |
self.keyWinPosXEdit = QLineEdit(myConfig.get("winposx", "10")) | |
inputRadioGroup.setLayout(posAxisLayout) | |
inputSettingsLayout.addWidget(inputRadioGroup) | |
inputSettingsLayout.addWidget(self.keyWinPosXEdit) | |
inputRadioGroup = QGroupBox("Align window vertically from") | |
posAxisLayout = QHBoxLayout() | |
self.keyWinPosYTopRadio = QRadioButton("Top") | |
self.keyWinPosYBottomRadio = QRadioButton("Bottom") | |
if myConfig.get("winyaxis", "top") == "top": | |
self.keyWinPosYTopRadio.setChecked(True) | |
else: | |
self.keyWinPosYBottomRadio.setChecked(True) | |
posAxisLayout.addWidget(self.keyWinPosYTopRadio) | |
posAxisLayout.addWidget(self.keyWinPosYBottomRadio) | |
self.keyWinPosYEdit = QLineEdit(myConfig.get("winposy", "10")) | |
inputRadioGroup.setLayout(posAxisLayout) | |
inputSettingsLayout.addWidget(inputRadioGroup) | |
inputSettingsLayout.addWidget(self.keyWinPosYEdit) | |
self.SaveButton = QPushButton("Save Settings") | |
self.GOButton = QPushButton("GO!") | |
buttonsSec = QHBoxLayout() | |
buttonsSec.addWidget(self.SaveButton) | |
buttonsSec.addWidget(self.GOButton) | |
inputSettingsLayout.addLayout(buttonsSec) | |
self.iconGroupBox.setLayout(inputSettingsLayout) | |
def saveSettings (self): | |
config = self.collectConfig() | |
data = dict() | |
data.update(config) | |
saveConfig(configfile, data) | |
def toggleOnOff(self): | |
global myConfig | |
if myConfig['off']: | |
myConfig['off'] = False | |
else: | |
myConfig['off'] = True | |
def stopIt (self): | |
global hm, codeslayoutview | |
if codeslayoutview is not None: | |
codeslayoutview.hide() | |
codeslayoutview = None | |
# detect if it is running | |
if hm is not None: | |
hm.UnhookKeyboard() | |
hm = None | |
pythoncom.PumpMessages() | |
def backToSettings (self): | |
self.showNormal() | |
self.stopIt() | |
def onOpenSettings (self): | |
self.backToSettings() | |
def createActions(self): | |
self.onOffAction = QAction("OnOff", self, triggered=self.toggleOnOff) | |
self.onOpenSettingsAction = QAction("Open Settings", self, triggered=self.onOpenSettings) | |
self.quitAction = QAction("Quit", self, triggered=sys.exit) | |
def createTrayIcon(self): | |
self.trayIconMenu = QMenu(self) | |
self.trayIconMenu.addAction(self.onOffAction) | |
self.trayIconMenu.addSeparator() | |
self.trayIconMenu.addAction(self.onOpenSettingsAction) | |
self.trayIconMenu.addSeparator() | |
self.trayIconMenu.addAction(self.quitAction) | |
self.trayIcon = QSystemTrayIcon(self) | |
self.trayIcon.setContextMenu(self.trayIconMenu) | |
class CodeRepresentation(QWidget): | |
def __init__(self, parent, code, item, c1): | |
super(CodeRepresentation, self).__init__(None) | |
vlayout = QVBoxLayout() | |
self.item = item | |
self.character = QLabel() | |
self.character.setGeometry(10, 10, 10, 10) | |
self.character.setContentsMargins(0, 0, 0, 0) | |
self.character.setAlignment(Qt.AlignTop) | |
self.codeline = QLabel() | |
self.codeline.setAlignment(Qt.AlignTop) | |
self.codeline.setContentsMargins(0, 0, 0, 0) | |
self.codeline.move(20, 30) | |
self.code = self.codetocode(code) | |
vlayout.setContentsMargins(5, 5, 5, 5) | |
vlayout.addWidget(self.character) | |
vlayout.addWidget(self.codeline) | |
vlayout.setAlignment(self.character, Qt.AlignCenter) | |
vlayout.setAlignment(self.codeline, Qt.AlignCenter) | |
self.setLayout(vlayout) | |
self.setContentsMargins(0, 0, 0, 0) | |
# self.show() | |
self.disabledchars = 0 | |
self.is_enabled = True | |
self.toggled = False | |
self.updateView() | |
def item_label (self): | |
return self.item['_action'].getlabel() if self.item['_action'] is not None else "" | |
def codetocode(self, code): | |
toReturn = code.replace('1', '.') | |
toReturn = toReturn.replace('2', '-') | |
return toReturn; | |
def enable(self): | |
self.is_enabled = True | |
self.updateView() | |
def disable(self): | |
self.is_enabled = False | |
self.updateView() | |
def updateView (self): | |
enabled = self.is_enabled | |
codeselectrange = self.disabledchars if enabled and self.disabledchars > 0 else 0 | |
self.character.setDisabled(not enabled) | |
self.codeline.setDisabled(not enabled) | |
charfontsize = int(3.0 * myConfig['fontsizescale']) | |
codefontsize = int(5.0 * myConfig['fontsizescale']) | |
toggled = self.toggled | |
self.character.setText("<font style='background-color:{bgcolor};color:{color};font-weight:bold;' size='{fontsize}'>{text}</font>" | |
.format(color='blue' if enabled else 'lightgrey', | |
text=(self.item_label().upper() if myConfig['upperchars'] else self.item_label()), | |
fontsize=charfontsize, bgcolor="yellow" if toggled else "none")) | |
self.codeline.setText("<font size='{fontsize}'><font color='green'>{selecttext}</font><font color='{color}'>{text}</font></font>" | |
.format(text=self.code[codeselectrange:], selecttext=self.code[:codeselectrange], | |
color='red' if enabled else 'lightgrey', fontsize=codefontsize)) | |
def enabled(self): | |
return self.character.isEnabled() | |
def reset(self): | |
self.enable() | |
self.disabledchars = -1 | |
self.tickDitDah() | |
def Dit(self): | |
if (self.enabled()): | |
if ((self.disabledchars < len(self.code)) and self.code[self.disabledchars] == '.'): | |
self.tickDitDah() | |
else: | |
self.disable() | |
def Dah(self): | |
if (self.enabled()): | |
if ((self.disabledchars < len(self.code)) and self.code[self.disabledchars] == '-'): | |
self.tickDitDah() | |
else: | |
self.disable() | |
def tickDitDah(self): | |
self.disabledchars += 1 | |
if (self.disabledchars > len(self.code)): | |
self.is_enabled = False | |
self.updateView() | |
class CodesLayoutViewWidget(QWidget): | |
feedbackSignal = pyqtSignal() | |
changeLayoutSignal = pyqtSignal() | |
hideSignal = pyqtSignal() | |
showSignal = pyqtSignal() | |
def changeLayout (self): | |
global codeslayoutview | |
self.hide() | |
if layoutmanager.active is not None: | |
codeslayoutview = CodesLayoutViewWidget(layoutmanager.active) | |
else: | |
codeslayoutview = None | |
def onFeedback (self): | |
for keyname in ("ALT", "SHIFT", "CTRL"): | |
coderep = self.keystroke_crs_map.get(keyname, None) | |
if coderep is not None: | |
coderep.toggled = getKeyStrokeState(keyname)['down'] | |
coderep.updateView() | |
coderep = self.keystroke_crs_map.get("CAPSLOCK", None) | |
if coderep is not None: | |
key = coderep.item['_action'].key | |
#state = unpack("H", pack("h", win32api.GetAsyncKeyState(key.keywin32)))[0] | |
coderep.toggled = getKeyStrokeState("CAPSLOCK")["locked"]# (state & 1) == 1 | |
coderep.updateView() | |
def __init__(self, layout): | |
super(CodesLayoutViewWidget, self).__init__() | |
self.feedbackSignal.connect(self.onFeedback) | |
self.changeLayoutSignal.connect(self.changeLayout) | |
self.hideSignal.connect(self.hide) | |
self.showSignal.connect(self.show) | |
self.setWindowFlags(Qt.WindowStaysOnTopHint) | |
self.vlayout = QVBoxLayout() | |
hlayout = QHBoxLayout() | |
hlayout.setContentsMargins(0, 0, 0, 0) | |
self.vlayout.addLayout(hlayout) | |
self.keystroke_crs_map = {} | |
self.crs = {} | |
x = 0 | |
perrow = layout['column_len'] | |
for code in layout['items']: | |
x += 1 | |
item = layout['items'][code] | |
if item.get('emptyspace', False) == False: | |
coderep = CodeRepresentation(None, code, item, "Green") | |
if isinstance(item['_action'], ActionKeyStroke): | |
self.keystroke_crs_map[item['_action'].key.name] = coderep | |
#self.setStyleSheet("background: %s " % "green") | |
self.crs[code] = coderep | |
hlayout.addWidget(coderep) | |
if (x > perrow): | |
x = 0 | |
hlayout = QHBoxLayout() | |
hlayout.setContentsMargins(0, 0, 0, 0) | |
self.vlayout.addLayout(hlayout) | |
self.setLayout(self.vlayout) | |
self.vlayout.setContentsMargins(0, 0, 0, 0) | |
self.show() | |
self.onFeedback() | |
ssize = app.desktop().screenGeometry() | |
size = self.frameSize() | |
x = int(myConfig['winposx']) if myConfig['winxaxis'] == 'left' else\ | |
ssize.width() - size.width() - int(myConfig['winposx']) | |
y = int(myConfig['winposy']) if myConfig['winyaxis'] == 'top' else\ | |
ssize.height() - size.height() - int(myConfig['winposy']) | |
self.move(x, y) | |
def Dit(self): | |
for item in self.crs.values(): | |
item.Dit() | |
def Dah(self): | |
for item in self.crs.values(): | |
item.Dah() | |
def reset(self): | |
for item in self.crs.values(): | |
item.reset() | |
def closeEvent(self, event): | |
window.backToSettings() | |
if __name__ == '__main__': | |
global layoutmanage, window, myConfig | |
import sys | |
initActions() | |
try: | |
myConfig = readConfig(configfile) | |
except FileNotFoundError: | |
myConfig = newConfig() | |
layoutmanager = LayoutManager(os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts.json"), actions) | |
if layoutmanager.active is None: | |
raise AssertionError("layouts.json has no mainlayout") | |
app = QApplication(sys.argv) | |
if not QSystemTrayIcon.isSystemTrayAvailable(): | |
QMessageBox.critical(None, "MorseWriter", "I couldn't detect any system tray on this system.") | |
sys.exit(1) | |
QApplication.setQuitOnLastWindowClosed(False) | |
window = Window() | |
#code = CodeRepresentation(window, "A", "233232") | |
#code.disable() | |
#code.tickDitDah() | |
#code.tickDitDah() | |
if myConfig.get("autostart", False): | |
window.start(myConfig) | |
else: | |
window.show() | |
sys.exit(app.exec_()) |