Source code for dispass.gui
'''Module that houses all GUI related objects and interactions'''
# Copyright (c) 2012-2016 Tom Willemse <tom@ryuslash.org>
# Copyright (c) 2011-2018 Benjamin Althues <benjamin@babab.nl>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
from tkinter import (
Button,
Checkbutton,
Entry,
Frame,
IntVar,
Label,
Spinbox,
StringVar,
Tk,
DISABLED,
END,
NORMAL,
N, E, S, W,
)
import sys
import tkinter.messagebox as tkMessageBox
import tkinter.ttk as ttk
import dispass.algos as algos
from dispass.dispass import versionStr as dispass_version
versionStr = 'g{}'.format(dispass_version)
[docs]class GUI(Frame):
'''GUI object with tkinter mainloop'''
font = "Verdana"
'''Default font (Verdana)'''
fontsize = 10
'''Default fontsize (10 pt.)'''
def __init__(self, settings, filehandler):
'''Initialize GUI object, create the widgets and start mainloop
Try to import Tkinter and tkMessageBox. If that fails, show a help
message with quick instructions on installing Tkinter.
'''
self.settings = settings
self.labelspecs = {l[0]: l[1:] for l in filehandler.labelfile}
Frame.__init__(self, Tk(className='dispass'))
self.lengthVar = IntVar()
self.lengthVar.set(self.settings.passphrase_length)
self.sequenceVar = IntVar()
self.sequenceVar.set(self.settings.sequence_number)
self.master.title(versionStr)
self.grid()
self.createWidgets()
[docs] def getFont(self, sizediff=0):
'''Get `font` and `fontsize`, optionally differ from default `fontsize`
:Parameters:
- `sizediff`: The difference in pt. from the default `fontsize`
:Return:
- Tuple of `(font, fontsize)` to be used when creating widgets
'''
return (self.font, self.fontsize + sizediff)
[docs] def warn(self, message, warning_type='soft', box_title=''):
'''Prototype for warning user
* soft warnings display a message in the passwordout field
* hard warnings do the same and also display a messagebox
:Parameters:
- `message`: The message string for warning the user
- `warning_type`: Either 'soft' (default value) or 'hard'
- `box_title`: Optional title for tkMessageBox on hard warnings
'''
if warning_type == 'soft' or warning_type == 'hard':
self.result.config(fg="black", readonlybackground="red")
self.passwordout.set('- ' + message + ' -')
if warning_type == 'hard':
self.passwordin1.delete(0, END)
self.passwordin2.delete(0, END)
tkMessageBox.showwarning(box_title, message)
# GUI # Event actions
[docs] def validateAndShow(self):
'''Check user input
Warn when user input is insufficient or wrong. Create digest and
display the generated password if user input is OK.
'''
label = self.label.get()
passwordin1 = self.passwordin1.get()
passwordin2 = self.passwordin2.get()
isnew = self.isnew.get()
algorithm = self.algorithm.get()
if len(label) == 0:
self.warn('No password generated, label field is empty')
return
elif len(passwordin1) == 0:
self.warn('No password generated, password field is empty')
return
elif len(passwordin1) < 8:
self.warn('Password must contain at least 8 characters', 'hard',
box_title='Password is too short')
return
elif isnew and passwordin1 != passwordin2:
self.warn('Passwords are not identical, please try again', 'hard',
box_title='Password mismatch')
return
elif algorithm not in algos.algorithms:
self.warn('Unknown algorithm: {}'.format(algorithm),
box_title='Unknown algorithm')
return
# All checks passed, create digest
if algorithm == 'dispass1':
algo = algos.Dispass1
elif algorithm == 'dispass2':
algo = algos.Dispass2
else:
self.warn('Algorithm not implemented in GUI: {}'.format(algorithm),
box_title='Unimplemented algorithm')
return
h = algo.digest(label, passwordin1, length=self.lengthVar.get(),
seqno=self.sequenceVar.get())
self.result.config(fg="black", readonlybackground="green")
self.passwordout.set(h)
self.clearInput()
self.result.focus_set()
self.result.select_range(0, END)
[docs] def toggleCheck(self):
'''Toggle checking of input password'''
if self.isnew.get() == 0:
# Disable double check (default)
self.passwordin2.delete(0, END)
self.passwordin2.config(state=DISABLED)
else:
# Password is new, allow for double checking passwordin
self.passwordin2.config(state=NORMAL)
[docs] def clearInput(self):
'''Clear all input fields'''
self.lengthVar.set(self.settings.passphrase_length)
self.sequenceVar.set(self.settings.sequence_number)
self.label.delete(0, END)
self.passwordin1.delete(0, END)
self.passwordin2.delete(0, END)
self.algorithm.set(self.settings.algorithm)
[docs] def clearOutput(self):
'''Clear all output fields'''
self.passwordout.set('- No password generated -')
self.result.config(fg="black", readonlybackground="gray")
[docs] def clearIO(self):
'''Clear all input and output fields'''
self.clearInput()
self.clearOutput()
[docs] def reset(self):
'''Clear all input and output and focus label entry'''
self.clearIO()
self.label.focus_set()
[docs] def labelFocusOut(self, event):
'''Set values of input fields according to the selected label.'''
labelspec = self.labelspecs.get(self.label.get())
if labelspec:
self.lengthVar.set(labelspec[0])
self.algorithm.set(labelspec[1])
self.sequenceVar.set(labelspec[2])
[docs] def labelSelected(self, event):
'''Change focus to password field.'''
self.passwordin1.focus_set()
[docs] def filterLabels(self):
'''Filter labels according to what was typed into the entry.'''
value = self.label.get()
names = list(self.labelspecs.copy().keys())
self.label.configure(values=names)
if value:
self.label.configure(values=[s for s in names
if s.startswith(value)])
# GUI # Create Widgets
[docs] def createWidgets(self):
'''Create and align widgets'''
top = self.winfo_toplevel()
top.rowconfigure(0, weight=1)
top.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.passwordout = StringVar()
self.passwordout.set('- No password generated -')
self.isnew = IntVar()
ttitle = Label(self, text=versionStr, font=self.getFont(4))
wisnew = Checkbutton(self, height=2, font=self.getFont(),
text=('This is a new password, that I have not '
'used before'),
variable=self.isnew, command=self.toggleCheck)
tlabel = Label(self, text='Label', font=self.getFont(2))
tpasswordin1 = Label(self, text='Password', font=self.getFont(2))
tpasswordin2 = Label(self, text='Password (again)',
font=self.getFont(2))
tlength = Label(self, text='Length', font=self.getFont(2))
talgorithm = Label(self, text='Algorithm', font=self.getFont(2))
tsequence = Label(self, text='Sequence #', font=self.getFont(2))
self.label = ttk.Combobox(self, width=27, font=self.getFont(),
postcommand=self.filterLabels)
self.passwordin1 = Entry(self, width=27, font=self.getFont(), show="*")
self.passwordin2 = Entry(self, width=27, font=self.getFont(), show="*",
state=DISABLED)
length = Spinbox(self, width=3, font=self.getFont, from_=9,
to=171, textvariable=self.lengthVar)
self.algorithm = ttk.Combobox(self, width=27, font=self.getFont(),
values=algos.algorithms)
sequence = Spinbox(self, width=3, font=self.getFont, from_=1,
to=sys.maxsize, textvariable=self.sequenceVar)
genbutton = Button(self, text="Generate password",
font=self.getFont(), command=self.validateAndShow,
default="active")
clrbutton = Button(self, text="Clear fields", font=self.getFont(),
command=self.clearIO)
self.result = Entry(self, font=self.getFont(4),
textvariable=self.passwordout, state="readonly",
fg="black", readonlybackground="gray")
# Keybindings
self.passwordin1.bind('<Return>', lambda e: genbutton.invoke())
self.passwordin2.bind('<Return>', lambda e: genbutton.invoke())
length.bind('<Return>', lambda e: genbutton.invoke())
self.algorithm.bind('<Return>', lambda e: genbutton.invoke())
sequence.bind('<Return>', lambda e: genbutton.invoke())
self.master.bind('<Control-q>', lambda e: self.quit())
self.master.bind('<Escape>', lambda e: self.reset())
self.label.bind('<<ComboboxSelected>>', self.labelSelected)
self.label.bind('<FocusOut>', self.labelFocusOut)
# Layout widgets in a grid
ttitle.grid(row=0, column=0, sticky=N + S + E + W, columnspan=2)
wisnew.grid(row=1, column=0, sticky=N + S + E + W, columnspan=2)
tlabel.grid(row=2, column=0, sticky=N + S + W)
self.label.grid(row=2, column=1, sticky=N + S + E + W)
tpasswordin1.grid(row=3, column=0, sticky=N + S + W)
self.passwordin1.grid(row=3, column=1, sticky=N + S + E + W)
tpasswordin2.grid(row=4, column=0, sticky=N + S + W)
self.passwordin2.grid(row=4, column=1, sticky=N + S + E + W)
tlength.grid(row=5, column=0, sticky=N + S + W)
length.grid(row=5, column=1, sticky=N + S + E + W)
talgorithm.grid(row=6, column=0, sticky=N + S + W)
self.algorithm.grid(row=6, column=1, sticky=N + S + E + W)
tsequence.grid(row=7, column=0, sticky=N + S + W)
sequence.grid(row=7, column=1, sticky=N + S + E + W)
clrbutton.grid(row=8, column=0, sticky=N + S + E + W, columnspan=2)
genbutton.grid(row=9, column=0, sticky=N + S + E + W, columnspan=2)
self.result.grid(row=10, column=0, sticky=N + S + E + W, columnspan=2)
# Initial values
self.algorithm.set(self.settings.algorithm)
# Initially, set focus on self.label
self.label.focus_set()