i'm on fire, a lot of updates

This commit is contained in:
Bilal Elmoussaoui 2016-05-30 05:49:20 +02:00
parent e0abfa1a16
commit 79150f2221
15 changed files with 862 additions and 608 deletions

1
.gitignore vendored
View file

@ -8,6 +8,7 @@ __pycache__/
# Distribution / packaging # Distribution / packaging
.Python .Python
.idea/
env/ env/
build/ build/
develop-eggs/ develop-eggs/

View file

@ -8,18 +8,21 @@ from TwoFactorAuth.widgets.settings import SettingsWindow
from TwoFactorAuth.models.settings import SettingsReader from TwoFactorAuth.models.settings import SettingsReader
import logging import logging
import signal import signal
from gettext import gettext as _
class Application(Gtk.Application): class Application(Gtk.Application):
win = None win = None
alive = True alive = True
locked = False locked = False
menu = Gio.Menu()
auth = Authenticator()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
for key in kwargs: for key in kwargs:
setattr(self, key, kwargs[key]) setattr(self, key, kwargs[key])
Gtk.Application.__init__(self, Gtk.Application.__init__(self,
application_id='org.gnome.twofactorauth', application_id='org.gnome.TwoFactorAuth',
flags=Gio.ApplicationFlags.FLAGS_NONE) flags=Gio.ApplicationFlags.FLAGS_NONE)
GLib.set_application_name("TwoFactorAuth") GLib.set_application_name("TwoFactorAuth")
GLib.set_prgname(self.package) GLib.set_prgname(self.package)
@ -41,36 +44,68 @@ class Application(Gtk.Application):
def do_startup(self): def do_startup(self):
Gtk.Application.do_startup(self) Gtk.Application.do_startup(self)
if self.locked:
self.menu.append(_("Unlock the Application"), "app.lock")
else:
self.menu.append(_("Lock the Application"), "app.lock")
self.menu.append(_("Settings"), "app.settings")
if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
self.menu.append(_("Shortcuts"), "app.shortcuts")
self.menu.append(_("About"), "app.about")
self.menu.append(_("Quit"), "app.quit")
self.set_app_menu(self.menu)
action = Gio.SimpleAction.new("settings", None) action = Gio.SimpleAction.new("settings", None)
action.connect("activate", self.on_settings) action.connect("activate", self.on_settings)
self.add_action(action) self.add_action(action)
action = Gio.SimpleAction.new("shortcuts", None) if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
action.connect("activate", self.on_shortcuts) action = Gio.SimpleAction.new("shortcuts", None)
self.add_action(action) action.connect("activate", self.on_shortcuts)
self.add_action(action)
action = Gio.SimpleAction.new("about", None) action = Gio.SimpleAction.new("about", None)
action.connect("activate", self.on_about) action.connect("activate", self.on_about)
self.add_action(action) self.add_action(action)
action = Gio.SimpleAction.new("lock", None)
action.connect("activate", self.on_toggle_lock)
self.add_action(action)
action = Gio.SimpleAction.new("quit", None) action = Gio.SimpleAction.new("quit", None)
action.connect("activate", self.on_quit) action.connect("activate", self.on_quit)
self.add_action(action) self.add_action(action)
builder = Gtk.Builder() self.toggle_settings_menu()
builder.add_from_file(self.pkgdatadir + "/data/menu.ui")
self.app_menu = builder.get_object("app-menu")
logging.debug("Adding gnome shell menu") logging.debug("Adding gnome shell menu")
self.set_app_menu(self.app_menu)
def do_activate(self, *args): def do_activate(self, *args):
self.auth = Authenticator()
self.win = Window(self) self.win = Window(self)
self.win.show_all() self.win.show_all()
self.add_window(self.win) self.add_window(self.win)
def toggle_settings_menu(self):
if self.locked:
self.menu.remove(1)
else:
self.menu.insert(1, _("Settings"), "app.settings")
def on_toggle_lock(self, *args):
if not self.locked:
self.locked = not self.locked
self.toggle_app_lock_menu()
self.toggle_settings_menu()
self.win.refresh_window()
def toggle_app_lock_menu(self):
if self.locked:
label = _("Unlock the Application")
else:
label = _("Lock the Application")
self.menu.remove(0)
self.menu.insert(0, label, "app.lock")
def on_shortcuts(self, *args): def on_shortcuts(self, *args):
self.win.show_shortcuts() self.win.show_shortcuts()
@ -78,8 +113,10 @@ class Application(Gtk.Application):
self.win.show_about() self.win.show_about()
def on_settings(self, *args): def on_settings(self, *args):
if not self.locked: """
SettingsWindow(self.win) Shows settings window
"""
SettingsWindow(self.win)
def on_quit(self, *args): def on_quit(self, *args):
""" """

View file

@ -9,6 +9,7 @@ class Authenticator:
def __init__(self): def __init__(self):
home = path.expanduser("~") home = path.expanduser("~")
database_file = home + '/.config/TwoFactorAuth/database.db' database_file = home + '/.config/TwoFactorAuth/database.db'
# Create missing folders
if not (path.isfile(database_file) and path.exists(database_file)): if not (path.isfile(database_file) and path.exists(database_file)):
dirs = database_file.split("/") dirs = database_file.split("/")
i = 0 i = 0
@ -18,57 +19,81 @@ class Authenticator:
makedirs(directory) makedirs(directory)
logging.debug("Creating directory %s " % directory) logging.debug("Creating directory %s " % directory)
i += 1 i += 1
# create database file
mknod(database_file) mknod(database_file)
logging.debug("Creating database file %s " % database_file) logging.debug("Creating database file %s " % database_file)
# Connect to database
self.conn = sqlite3.connect(database_file) self.conn = sqlite3.connect(database_file)
if not self.is_table_exists(): if not self.is_table_exists():
logging.debug( logging.debug("SQL: Table 'applciations' does not exists, creating it now...")
"Table 'applciations' does not exists, creating it now...")
self.create_table() self.create_table()
logging.debug("Table 'applications' created successfully") logging.debug("SQL: Table 'applications' created successfully")
def add_application(self, name, secret_code, image): def add_application(self, name, secret_code, image):
"""
Add an application to applications table
:param name: (str) Application name
:param secret_code: (str) ASCII Secret code
:param image: image path or icon name
:return:
"""
t = (name, secret_code, image,) t = (name, secret_code, image,)
query = "INSERT INTO applications (name, secret_code, image) VALUES (?, ?, ?)" query = "INSERT INTO applications (name, secret_code, image) VALUES (?, ?, ?)"
try: try:
self.conn.execute(query, t) self.conn.execute(query, t)
self.conn.commit() self.conn.commit()
except Exception as e: except Exception as e:
logging.error("Couldn't add a new application : %s ", str(e)) logging.error("SQL: Couldn't add a new application : %s ", str(e))
def remove_by_id(self, id): def remove_by_uid(self, uid):
query = "DELETE FROM applications WHERE id=?" """
Remove an application by uid
:param uid: (int) application uid
:return:
"""
query = "DELETE FROM applications WHERE uid=?"
try: try:
self.conn.execute(query, (id,)) self.conn.execute(query, (uid,))
self.conn.commit() self.conn.commit()
except Exception as e: except Exception as e:
logging.error( logging.error("SQL: Couldn't remove application by uid : %s with error : %s" % (uid, str(e)))
"Couldn't remove application by id : %s with error : %s" % (id, str(e)))
def count(self): def count(self):
"""
Count number of applications
:return: (int) count
"""
c = self.conn.cursor() c = self.conn.cursor()
query = "SELECT COUNT(id) AS count FROM applications" query = "SELECT COUNT(uid) AS count FROM applications"
try: try:
data = c.execute(query) data = c.execute(query)
return data.fetchone()[0] return data.fetchone()[0]
except Exception as e: except Exception as e:
logging.error("Couldn't count applications list : %s " % str(e)) logging.error("SQL: Couldn't count applications list : %s " % str(e))
return None return None
def fetch_apps(self): def fetch_apps(self):
"""
Fetch list of applications
:return: (tuple) list of applications
"""
c = self.conn.cursor() c = self.conn.cursor()
query = "SELECT * FROM applications" query = "SELECT * FROM applications"
try: try:
data = c.execute(query) data = c.execute(query)
return data.fetchall() return data.fetchall()
except Exception as e: except Exception as e:
logging.error(query) logging.error("SQL: Couldn't fetch applications list %s" % str(e))
logging.error("Couldn't fetch applications list")
logging.error(str(e))
return None return None
@staticmethod @staticmethod
def get_auth_icon(image, pkgdatadir): def get_auth_icon(image, pkgdatadir):
"""
Generate a GdkPixbuf image
:param image: icon name or image path
:param pkgdatadir: default installation dir of apps
:return: GdkPixbux Image
"""
directory = pkgdatadir + "/data/logos/" directory = pkgdatadir + "/data/logos/"
theme = Gtk.IconTheme.get_default() theme = Gtk.IconTheme.get_default()
if path.isfile(directory + image) and path.exists(directory + image): if path.isfile(directory + image) and path.exists(directory + image):
@ -79,24 +104,31 @@ class Authenticator:
icon = theme.load_icon(path.splitext(image)[0], 48, 0) icon = theme.load_icon(path.splitext(image)[0], 48, 0)
else: else:
icon = theme.load_icon("image-missing", 48, 0) icon = theme.load_icon("image-missing", 48, 0)
if icon.get_width() != 48 or icon.get_height() != 48: if icon.get_wuidth() != 48 or icon.get_height() != 48:
icon = icon.scale_simple(48, 48, icon = icon.scale_simple(48, 48,
GdkPixbuf.InterpType.BILINEAR) GdkPixbuf.InterpType.BILINEAR)
return icon return icon
def get_latest_id(self): def get_latest_uid(self):
"""
Get the latest uid on applications table
:return: (int) latest uid
"""
c = self.conn.cursor() c = self.conn.cursor()
query = "SELECT id FROM applications ORDER BY id DESC LIMIT 1;" query = "SELECT uid FROM applications ORDER BY uid DESC LIMIT 1;"
try: try:
data = c.execute(query) data = c.execute(query)
return data.fetchone()[0] return data.fetchone()[0]
except Exception as e: except Exception as e:
logging.error("Couldn't fetch the latest id %s" % str(e)) logging.error("SQL: Couldn't fetch the latest uid %s" % str(e))
return None return None
def create_table(self): def create_table(self):
"""
Create applications table
"""
query = '''CREATE TABLE "applications" ( query = '''CREATE TABLE "applications" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE , "uid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE ,
"name" VARCHAR NOT NULL , "name" VARCHAR NOT NULL ,
"secret_code" VARCHAR NOT NULL UNIQUE , "secret_code" VARCHAR NOT NULL UNIQUE ,
"image" TEXT NOT NULL "image" TEXT NOT NULL
@ -105,11 +137,14 @@ class Authenticator:
self.conn.execute(query) self.conn.execute(query)
self.conn.commit() self.conn.commit()
except Exception as e: except Exception as e:
logging.error( logging.error( "SQL: imossibile to create table 'applications' %s " % str(e))
"SQL : imossibile to create table 'applications' %s " % str(err))
def is_table_exists(self): def is_table_exists(self):
query = "SELECT id from applications LIMIT 1" """
Check if applications table exists
:return: (bool)
"""
query = "SELECT uid from applications LIMIT 1"
c = self.conn.cursor() c = self.conn.cursor()
try: try:
data = c.execute(query) data = c.execute(query)

View file

@ -7,12 +7,19 @@ class SettingsReader:
def __init__(self): def __init__(self):
try: try:
# Check if the gsettings path exists
self.source = Gio.SettingsSchemaSource.get_default() self.source = Gio.SettingsSchemaSource.get_default()
self.source.lookup(self.path, True) self.source.lookup(self.path, True)
except Exception as e: except Exception as e:
logging.critical("Couldn't load gsettings source %s " % str(e)) logging.critical("Couldn't load gsettings source %s " % str(e))
def read(self, key, path): def read(self, key, path):
"""
Read a 'key' from org.gnome.TwoFactorAuth.'path'
:param key: (str) key to read
:param path: login/user
:return: value
"""
gsettings = Gio.Settings.new(self.path + "." + path) gsettings = Gio.Settings.new(self.path + "." + path)
value = gsettings.get_value(key) value = gsettings.get_value(key)
value_type = value.get_type_string() value_type = value.get_type_string()
@ -25,6 +32,13 @@ class SettingsReader:
return value return value
def update(self, key, value, path): def update(self, key, value, path):
"""
Update 'key' value to 'value' from org.gnome.TwoFactorAuth.'path'
:param key: (str) key to read
:param value: updated value
:param path: login/user
:return: value
"""
gsettings = Gio.Settings.new(self.path + "." + path) gsettings = Gio.Settings.new(self.path + "." + path)
if type(value) is int: if type(value) is int:
gsettings.set_int(key, value) gsettings.set_int(key, value)

View file

@ -11,28 +11,23 @@ from gettext import gettext as _
class AddAuthenticator(Gtk.Window): class AddAuthenticator(Gtk.Window):
selected_image = None selected_image = None
hb = Gtk.HeaderBar()
popover = Gtk.PopoverMenu.new()
logo_image = Gtk.Image(xalign=0)
secret_code = Gtk.Entry()
name_entry = Gtk.Entry()
def __init__(self, window): def __init__(self, window):
self.parent = window self.parent = window
self.generate_window() self.generate_window()
self.generate_compenents() self.generate_components()
self.generate_headerbar() self.generate_headerbar()
self.show_all() self.show_all()
def update_logo(self, image):
# TODO : add the possiblity to use external icons or icon names
directory = self.parent.app.pkgdatadir + "/data/logos/"
self.selected_image = image
auth_icon = Authenticator.get_auth_icon(
image, self.parent.app.pkgdatadir)
img_box = self.get_children()[0].get_children()[0].get_children()
img_box[0].get_children()[0].clear()
img_box[0].get_children()[0].set_from_pixbuf(auth_icon)
def generate_window(self): def generate_window(self):
Gtk.Window.__init__(self, title=_("Add a new application"), modal=True, Gtk.Window.__init__(self, title=_("Add a new application"),
destroy_with_parent=True) modal=True, destroy_with_parent=True)
self.connect("delete-event", lambda x, y: self.destroy()) self.connect("delete-event", self.close_window)
self.resize(300, 100) self.resize(300, 100)
self.set_border_width(12) self.set_border_width(12)
self.set_size_request(300, 100) self.set_size_request(300, 100)
@ -41,11 +36,27 @@ class AddAuthenticator(Gtk.Window):
self.set_transient_for(self.parent) self.set_transient_for(self.parent)
self.connect("key_press_event", self.on_key_press) self.connect("key_press_event", self.on_key_press)
def on_key_press(self, key, keyevent): def on_key_press(self, key, key_event):
if Gdk.keyval_name(keyevent.keyval) == "Escape": """
Keyboard Listener handler
"""
if Gdk.keyval_name(key_event.keyval) == "Escape":
self.destroy() self.destroy()
def select_logo(self, eventbox, event_button): def update_logo(self, image):
"""
Update image logo
"""
directory = self.parent.app.pkgdatadir + "/data/logos/"
self.selected_image = image
auth_icon = Authenticator.get_auth_icon(image, self.parent.app.pkgdatadir)
self.logo_image.clear()
self.logo_image.set_from_pixbuf(auth_icon)
def select_logo(self, event_box, event_button):
"""
Application logo selection, right & left mouse click event handling
"""
# Right click handling # Right click handling
if event_button.button == 3: if event_button.button == 3:
if self.popover.get_visible(): if self.popover.get_visible():
@ -56,55 +67,54 @@ class AddAuthenticator(Gtk.Window):
AuthenticatorLogoChooser(self) AuthenticatorLogoChooser(self)
def add_application(self, *args): def add_application(self, *args):
entries_box = self.get_children()[0].get_children()[1].get_children() """
name_entry = entries_box[0].get_children()[1].get_text() Add a new application to the database
secret_entry = entries_box[1].get_children()[1].get_text() """
name_entry = self.name_entry.get_text()
secret_entry = self.secret_code.get_text()
image_entry = self.selected_image if self.selected_image else "image-missing" image_entry = self.selected_image if self.selected_image else "image-missing"
try: try:
self.parent.app.auth.add_application(name_entry, secret_entry, self.parent.app.auth.add_application(name_entry, secret_entry,
image_entry) image_entry)
id = self.parent.app.auth.get_latest_id() id = self.parent.app.auth.get_latest_id()
self.parent.update_list(id, name_entry, secret_entry, image_entry) self.parent.append(id, name_entry, secret_entry, image_entry)
self.parent.refresh_window() self.parent.refresh_window()
self.close_window() self.close_window()
except Exception as e: except Exception as e:
logging.error("Error in adding a new application") logging.error("Error in adding a new application")
logging.error(str(e)) logging.error(str(e))
def generate_compenents(self): def generate_components(self):
mainbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) """
Generate window components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
logo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) logo_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
hbox_title = Gtk.Box( hbox_title = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
title_label = Gtk.Label() title_label = Gtk.Label()
title_label.set_text(_("Application name : ")) title_label.set_text(_("Application Name"))
title_entry = Gtk.Entry()
hbox_title.pack_start(title_label, False, True, 0)
hbox_title.pack_end(title_entry, False, True, 0)
hbox_two_factor = Gtk.Box( hbox_title.pack_start(title_label, False, True, 0)
orientation=Gtk.Orientation.HORIZONTAL, spacing=18) hbox_title.pack_end(self.name_entry, False, True, 0)
hbox_two_factor = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
two_factor_label = Gtk.Label() two_factor_label = Gtk.Label()
two_factor_label.set_text(_("Two-factor secret : ")) two_factor_label.set_text(_("Secret Code"))
two_factor_entry = Gtk.Entry() self.secret_code.connect("changed", self.validate_ascii_code)
two_factor_entry.connect("changed", self.validate_ascii_code)
hbox_two_factor.pack_start(two_factor_label, False, True, 0) hbox_two_factor.pack_start(two_factor_label, False, True, 0)
hbox_two_factor.pack_end(two_factor_entry, False, True, 0) hbox_two_factor.pack_end(self.secret_code, False, True, 0)
logo_event = Gtk.EventBox() logo_event = Gtk.EventBox()
auth_icon = Authenticator.get_auth_icon("image-missing", auth_icon = Authenticator.get_auth_icon("image-missing", self.parent.app.pkgdatadir)
self.parent.app.pkgdatadir) self.logo_image.set_from_pixbuf(auth_icon)
logo_image = Gtk.Image(xalign=0) self.logo_image.get_style_context().add_class("application-logo-add")
logo_image.set_from_pixbuf(auth_icon) logo_event.add(self.logo_image)
logo_image.get_style_context().add_class("application-logo-add")
logo_event.add(logo_image)
logo_event.connect("button-press-event", self.select_logo) logo_event.connect("button-press-event", self.select_logo)
logo_box.pack_start(logo_event, False, False, 6) logo_box.pack_start(logo_event, False, False, 6)
self.popover = Gtk.PopoverMenu.new()
self.popover.get_style_context().add_class("choose-popover") self.popover.get_style_context().add_class("choose-popover")
self.popover.set_relative_to(logo_image) self.popover.set_relative_to(self.logo_image)
pbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) pbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.popover.add(pbox) self.popover.add(pbox)
@ -126,30 +136,36 @@ class AddAuthenticator(Gtk.Window):
vbox.add(hbox_title) vbox.add(hbox_title)
vbox.add(hbox_two_factor) vbox.add(hbox_two_factor)
mainbox.pack_start(logo_box, False, True, 6) main_box.pack_start(logo_box, False, True, 6)
mainbox.pack_start(vbox, False, True, 6) main_box.pack_start(vbox, False, True, 6)
self.add(mainbox) self.add(main_box)
def validate_ascii_code(self, entry): def validate_ascii_code(self, entry):
"""
Validate if the typed secret code is a valid ascii one
"""
ascii_code = entry.get_text().strip() ascii_code = entry.get_text().strip()
is_valid = Code.is_valid(ascii_code) is_valid = Code.is_valid(ascii_code)
self.hb.get_children()[1].get_children()[0].set_sensitive(is_valid) self.apply_button.set_sensitive(is_valid)
if not is_valid and len(ascii_code) != 0: if not is_valid and len(ascii_code) != 0:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
"dialog-error-symbolic")
else: else:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, None)
"")
def on_provided_click(self, *args): def on_provided_click(self, *args):
"""
Select an icon from provided ones
"""
AuthenticatorLogoChooser(self) AuthenticatorLogoChooser(self)
def on_file_clicked(self, *args): def on_file_clicked(self, *args):
""""
Select an icon by filename
"""
dialog = Gtk.FileChooserDialog(_("Please choose a file"), self, dialog = Gtk.FileChooserDialog(_("Please choose a file"), self,
Gtk.FileChooserAction.OPEN, Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
self.add_filters(dialog) self.add_filters(dialog)
response = dialog.run() response = dialog.run()
@ -158,9 +174,16 @@ class AddAuthenticator(Gtk.Window):
dialog.destroy() dialog.destroy()
def on_icon_clicked(self, *args): def on_icon_clicked(self, *args):
"""
Shows icon finder window
select icon by icon name
"""
IconFinderWindow(self) IconFinderWindow(self)
def add_filters(self, dialog): def add_filters(self, dialog):
"""
Add file filters to GtkFileChooser
"""
filter_png = Gtk.FileFilter() filter_png = Gtk.FileFilter()
filter_png.set_name("PNG") filter_png.set_name("PNG")
filter_png.add_mime_type("image/png") filter_png.add_mime_type("image/png")
@ -171,9 +194,10 @@ class AddAuthenticator(Gtk.Window):
filter_svg.add_mime_type("image/svg+xml") filter_svg.add_mime_type("image/svg+xml")
dialog.add_filter(filter_svg) dialog.add_filter(filter_svg)
def generate_headerbar(self): def generate_header_bar(self):
self.hb = Gtk.HeaderBar() """
Generate the header bar box
"""
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
@ -182,15 +206,18 @@ class AddAuthenticator(Gtk.Window):
cancel_button.get_style_context().add_class("destructive-action") cancel_button.get_style_context().add_class("destructive-action")
left_box.add(cancel_button) left_box.add(cancel_button)
apply_button = Gtk.Button.new_with_label(_("Add")) self.apply_button = Gtk.Button.new_with_label(_("Add"))
apply_button.get_style_context().add_class("suggested-action") self.apply_button.get_style_context().add_class("suggested-action")
apply_button.connect("clicked", self.add_application) self.apply_button.connect("clicked", self.add_application)
apply_button.set_sensitive(False) self.apply_button.set_sensitive(False)
right_box.add(apply_button) right_box.add(self.apply_button)
self.hb.pack_start(left_box) self.hb.pack_start(left_box)
self.hb.pack_end(right_box) self.hb.pack_end(right_box)
self.set_titlebar(self.hb) self.set_titlebar(self.hb)
def close_window(self, *args): def close_window(self, *args):
"""
Close the window
"""
self.destroy() self.destroy()

View file

@ -21,9 +21,8 @@ class AuthenticatorLogoChooser(Gtk.Window):
self.show_all() self.show_all()
def generate_window(self): def generate_window(self):
Gtk.Window.__init__(self, modal=True, Gtk.Window.__init__(self, modal=True, destroy_with_parent=True)
destroy_with_parent=True) self.connect("delete-event", self.close_window)
self.connect("delete-event", lambda x, y: self.destroy())
self.resize(350, 400) self.resize(350, 400)
self.set_size_request(350, 400) self.set_size_request(350, 400)
self.set_position(Gtk.WindowPosition.CENTER) self.set_position(Gtk.WindowPosition.CENTER)

View file

@ -8,19 +8,23 @@ from TwoFactorAuth.models.settings import SettingsReader
class PasswordWindow(Gtk.Window): class PasswordWindow(Gtk.Window):
hb = Gtk.HeaderBar()
apply_button = Gtk.Button.new_with_label(_("Modifier"))
new_entry = Gtk.Entry()
new2_entry = Gtk.Entry()
old_entry = Gtk.Entry()
def __init__(self, window): def __init__(self, window):
self.parent = window self.parent = window
self.cfg = SettingsReader() self.cfg = SettingsReader()
self.generate_window() self.generate_window()
self.generate_compenents() self.generate_components()
self.generate_headerbar() self.generate_header_bar()
self.show_all() self.show_all()
def generate_window(self): def generate_window(self):
Gtk.Window.__init__(self, modal=True, Gtk.Window.__init__(self, modal=True, destroy_with_parent=True)
destroy_with_parent=True) self.connect("delete-event", self.close_window)
self.connect("delete-event", lambda x, y: self.destroy())
self.resize(300, 100) self.resize(300, 100)
self.set_border_width(12) self.set_border_width(12)
self.set_size_request(300, 100) self.set_size_request(300, 100)
@ -29,60 +33,70 @@ class PasswordWindow(Gtk.Window):
self.set_transient_for(self.parent) self.set_transient_for(self.parent)
self.connect("key_press_event", self.on_key_press) self.connect("key_press_event", self.on_key_press)
def on_key_press(self, key, keyevent): def on_key_press(self, key, key_event):
if Gdk.keyval_name(keyevent.keyval) == "Escape": """
Keyboard listener handler
"""
if Gdk.keyval_name(key_event.keyval) == "Escape":
self.destroy() self.destroy()
def generate_compenents(self): def generate_components(self):
mainbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) """
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) Generate window components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
if len(self.cfg.read("password", "login")) != 0: if len(self.cfg.read("password", "login")) != 0:
hbox_old = Gtk.Box( box_old = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18) orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
old_label = Gtk.Label() old_label = Gtk.Label()
old_label.set_text(_("Old password : ")) old_label.set_text(_("Old password"))
self.old_entry = Gtk.Entry()
self.old_entry.connect("changed", self.on_type_password) self.old_entry.connect("changed", self.on_type_password)
self.old_entry.set_visibility(False) self.old_entry.set_visibility(False)
hbox_old.pack_start(old_label, False, True, 0) box_old.pack_start(old_label, False, True, 0)
hbox_old.pack_end(self.old_entry, False, True, 0) box_old.pack_end(self.old_entry, False, True, 0)
vbox.add(hbox_old) box.add(box_old)
hbox_new = Gtk.Box( box_new = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18) orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
new_label = Gtk.Label() new_label = Gtk.Label()
new_label.set_text(_("New password : ")) new_label.set_text(_("New password"))
self.new_entry = Gtk.Entry()
self.new_entry.connect("changed", self.on_type_password) self.new_entry.connect("changed", self.on_type_password)
self.new_entry.set_visibility(False) self.new_entry.set_visibility(False)
hbox_new.pack_start(new_label, False, True, 0) box_new.pack_start(new_label, False, True, 0)
hbox_new.pack_end(self.new_entry, False, True, 0) box_new.pack_end(self.new_entry, False, True, 0)
hbox_new2 = Gtk.Box( box_new2 = Gtk.Box(
orientation=Gtk.Orientation.HORIZONTAL, spacing=18) orientation=Gtk.Orientation.HORIZONTAL, spacing=18)
new2_label = Gtk.Label() new2_label = Gtk.Label()
new2_label.set_text(_("Repeat new password : ")) new2_label.set_text(_("Repeat new password"))
self.new2_entry = Gtk.Entry()
self.new2_entry.connect("changed", self.on_type_password) self.new2_entry.connect("changed", self.on_type_password)
self.new2_entry.set_visibility(False) self.new2_entry.set_visibility(False)
hbox_new2.pack_start(new2_label, False, True, 0) box_new2.pack_start(new2_label, False, True, 0)
hbox_new2.pack_end(self.new2_entry, False, True, 0) box_new2.pack_end(self.new2_entry, False, True, 0)
vbox.add(hbox_new) box.add(box_new)
vbox.add(hbox_new2) box.add(box_new2)
mainbox.pack_start(vbox, False, True, 6) main_box.pack_start(box, False, True, 6)
self.add(mainbox) self.add(main_box)
def update_password(self, *args): def update_password(self, *args):
"""
Update user password
"""
password = md5(self.new_entry.get_text().encode("utf-8")).hexdigest() password = md5(self.new_entry.get_text().encode("utf-8")).hexdigest()
self.cfg.update("password", password, "login") self.cfg.update("password", password, "login")
logging.debug("Password changed successfully")
self.destroy() self.destroy()
def on_type_password(self, entry): def on_type_password(self, entry):
"""
Validate the old & new password
"""
pwd = self.cfg.read("password", "login") pwd = self.cfg.read("password", "login")
are_diff = False
old_is_ok = True old_is_ok = True
if self.new_entry.get_text() != self.new2_entry.get_text(): if self.new_entry.get_text() != self.new2_entry.get_text():
self.new_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, self.new_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
@ -113,9 +127,10 @@ class PasswordWindow(Gtk.Window):
Gtk.EntryIconPosition.SECONDARY, "") Gtk.EntryIconPosition.SECONDARY, "")
self.apply_button.set_sensitive(not are_diff and old_is_ok) self.apply_button.set_sensitive(not are_diff and old_is_ok)
def generate_headerbar(self): def generate_header_bar(self):
self.hb = Gtk.HeaderBar() """
Generate header bar box
"""
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
@ -124,7 +139,6 @@ class PasswordWindow(Gtk.Window):
cancel_button.get_style_context().add_class("destructive-action") cancel_button.get_style_context().add_class("destructive-action")
left_box.add(cancel_button) left_box.add(cancel_button)
self.apply_button = Gtk.Button.new_with_label(_("Modifier"))
self.apply_button.get_style_context().add_class("suggested-action") self.apply_button.get_style_context().add_class("suggested-action")
self.apply_button.connect("clicked", self.update_password) self.apply_button.connect("clicked", self.update_password)
self.apply_button.set_sensitive(False) self.apply_button.set_sensitive(False)
@ -135,4 +149,7 @@ class PasswordWindow(Gtk.Window):
self.set_titlebar(self.hb) self.set_titlebar(self.hb)
def close_window(self, *args): def close_window(self, *args):
"""
Close the window
"""
self.destroy() self.destroy()

View file

@ -5,6 +5,7 @@ import logging
class ConfirmationMessage(Gtk.Window): class ConfirmationMessage(Gtk.Window):
result = None
def __init__(self, parent, message): def __init__(self, parent, message):
try: try:
@ -18,6 +19,9 @@ class ConfirmationMessage(Gtk.Window):
logging.error(str(e)) logging.error(str(e))
def show(self): def show(self):
"""
Show confirmation dialog
"""
try: try:
self.result = self.dialog.run() self.result = self.dialog.run()
self.dialog.hide() self.dialog.hide()
@ -26,7 +30,13 @@ class ConfirmationMessage(Gtk.Window):
return None return None
def get_confirmation(self): def get_confirmation(self):
"""
get confirmation
"""
return self.result == Gtk.ResponseType.OK return self.result == Gtk.ResponseType.OK
def destroy(self): def destroy(self):
"""
Destroy the message
"""
self.dialog.destroy() self.dialog.destroy()

View file

@ -1,27 +1,27 @@
from gi import require_version from gi import require_version
require_version("Gtk", "3.0") require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk from gi.repository import Gtk, Gdk
import logging
from TwoFactorAuth.widgets.authenticator_logo import AuthenticatorLogoChooser
from TwoFactorAuth.models.code import Code
from TwoFactorAuth.models.authenticator import Authenticator from TwoFactorAuth.models.authenticator import Authenticator
from gettext import gettext as _ from gettext import gettext as _
class IconFinderWindow(Gtk.Window): class IconFinderWindow(Gtk.Window):
selected_image = None selected_image = None
hb = Gtk.HeaderBar()
apply_button = Gtk.Button.new_with_label(_("Select"))
logo_image = Gtk.Image(xalign=0)
icon_entry = Gtk.Entry()
def __init__(self, window): def __init__(self, window):
self.parent = window self.parent = window
self.generate_window() self.generate_window()
self.generate_compenents() self.generate_components()
self.generate_headerbar() self.generate_header_bar()
self.show_all() self.show_all()
def generate_window(self): def generate_window(self):
Gtk.Window.__init__(self, modal=True, Gtk.Window.__init__(self, modal=True, destroy_with_parent=True)
destroy_with_parent=True) self.connect("delete-event", self.close_window)
self.connect("delete-event", lambda x, y: self.destroy())
self.resize(200, 100) self.resize(200, 100)
self.set_border_width(12) self.set_border_width(12)
self.set_size_request(200, 100) self.set_size_request(200, 100)
@ -29,29 +29,33 @@ class IconFinderWindow(Gtk.Window):
self.set_resizable(False) self.set_resizable(False)
self.set_transient_for(self.parent) self.set_transient_for(self.parent)
def generate_compenents(self): def generate_components(self):
mainbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) """
Generate all the components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
logo_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) logo_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
auth_icon = Authenticator.get_auth_icon("image-missing", auth_icon = Authenticator.get_auth_icon("image-missing",
self.parent.parent.app.pkgdatadir) self.parent.parent.app.pkgdatadir)
logo_image = Gtk.Image(xalign=0) self.logo_image.set_from_pixbuf(auth_icon)
logo_image.set_from_pixbuf(auth_icon) self.logo_image.get_style_context().add_class("application-logo-add")
logo_image.get_style_context().add_class("application-logo-add") logo_box.pack_start(self.logo_image, False, False, 0)
logo_box.pack_start(logo_image, False, False, 0)
box_entry = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box_entry = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
icon_entry = Gtk.Entry() self.icon_entry.set_margin_top(10)
icon_entry.set_margin_top(10) self.icon_entry.connect("changed", self.update_icon)
icon_entry.connect("changed", self.update_icon) box_entry.pack_start(self.icon_entry, False, False, 0)
box_entry.pack_start(icon_entry, False, False, 0)
mainbox.pack_start(logo_box, False, True, 6) main_box.pack_start(logo_box, False, True, 6)
mainbox.pack_end(box_entry, False, True, 6) main_box.pack_end(box_entry, False, True, 6)
self.add(mainbox) self.add(main_box)
def update_icon(self, entry): def update_icon(self, entry):
"""
Update icon image on changed event
"""
icon_name = entry.get_text() icon_name = entry.get_text()
theme = Gtk.IconTheme.get_default() theme = Gtk.IconTheme.get_default()
apply_button = self.hb.get_children()[1].get_children()[0] apply_button = self.hb.get_children()[1].get_children()[0]
@ -61,18 +65,20 @@ class IconFinderWindow(Gtk.Window):
else: else:
icon = theme.load_icon("image-missing", 48, 0) icon = theme.load_icon("image-missing", 48, 0)
apply_button.set_sensitive(False) apply_button.set_sensitive(False)
icon_image = self.get_children()[0].get_children()[0].get_children()[0] self.logo_image.clear()
icon_image.clear() self.logo_image.set_from_pixbuf(icon)
icon_image.set_from_pixbuf(icon)
def update_logo(self, *args): def update_logo(self, *args):
img_entry = self.get_children()[0].get_children()[1].get_children()[0] """
self.parent.update_logo(img_entry.get_text().strip()) Update application logo and close the window
"""
self.parent.update_logo(self.icon_entry.get_text().strip())
self.close_window() self.close_window()
def generate_headerbar(self): def generate_header_bar(self):
self.hb = Gtk.HeaderBar() """
Generate header bar box
"""
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
@ -81,15 +87,17 @@ class IconFinderWindow(Gtk.Window):
cancel_button.get_style_context().add_class("destructive-action") cancel_button.get_style_context().add_class("destructive-action")
left_box.add(cancel_button) left_box.add(cancel_button)
apply_button = Gtk.Button.new_with_label(_("Select")) self.apply_button.get_style_context().add_class("suggested-action")
apply_button.get_style_context().add_class("suggested-action") self.apply_button.connect("clicked", self.update_logo)
apply_button.connect("clicked", self.update_logo) self.apply_button.set_sensitive(False)
apply_button.set_sensitive(False) right_box.add(self.apply_button)
right_box.add(apply_button)
self.hb.pack_start(left_box) self.hb.pack_start(left_box)
self.hb.pack_end(right_box) self.hb.pack_end(right_box)
self.set_titlebar(self.hb) self.set_titlebar(self.hb)
def close_window(self, *args): def close_window(self, *args):
"""
Close the window
"""
self.destroy() self.destroy()

View file

@ -1,11 +1,11 @@
from gi import require_version from gi import require_version
require_version("Gtk", "3.0") require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gio, Gdk, GObject from gi.repository import Gtk, GObject
from TwoFactorAuth.models.code import Code from TwoFactorAuth.models.code import Code
from TwoFactorAuth.models.authenticator import Authenticator from TwoFactorAuth.models.authenticator import Authenticator
from TwoFactorAuth.models.settings import SettingsReader from TwoFactorAuth.models.settings import SettingsReader
from threading import Thread from threading import Thread
import time from time import sleep
import logging import logging
from math import pi from math import pi
from gettext import gettext as _ from gettext import gettext as _
@ -18,8 +18,13 @@ class ListBoxRow(Thread):
code = None code = None
code_generated = True code_generated = True
row = Gtk.ListBoxRow()
drawing_area = Gtk.DrawingArea()
code_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
def __init__(self, parent, id, name, secret_code, logo): def __init__(self, parent, id, name, secret_code, logo):
Thread.__init__(self) Thread.__init__(self)
# Read default values
cfg = SettingsReader() cfg = SettingsReader()
self.counter_max = cfg.read("refresh-time", "preferences") self.counter_max = cfg.read("refresh-time", "preferences")
self.counter = self.counter_max self.counter = self.counter_max
@ -33,16 +38,60 @@ class ListBoxRow(Thread):
self.start() self.start()
GObject.timeout_add_seconds(1, self.refresh_listbox) GObject.timeout_add_seconds(1, self.refresh_listbox)
def toggle_code_box(self, *args): @staticmethod
code_box = self.row.get_children()[0].get_children()[1] def get_id(row):
is_visible = code_box.get_visible() """
code_box.set_visible(not is_visible) Get the application id
code_box.set_no_show_all(is_visible) :param row: ListBoxRow
code_box.show_all() :return: (int): row id
"""
label_id = row.get_children()[0].get_children()[2]
label_id = int(label_id.get_text())
return label_id
def copy_code(self, eventbox, box): @staticmethod
def get_label(row):
"""
Get the application label
:param row: ListBoxRow
:return: (str): application label
"""
label = row.get_children()[0].get_children()[0].get_children()[2].get_children()[0].get_text()
return label.strip()
@staticmethod
def get_checkbox(row):
"""
Get ListBowRow's checkbox
:param row: ListBoxRow
:return: (Gtk.Checkbox)
"""
return row.get_children()[0].get_children()[0].get_children()[0]
@staticmethod
def get_code_box(row):
"""
Get code's box
:param row: ListBoxRow
:return: (Gtk.Box)
"""
return row.get_children()[0].get_children()[1]
def toggle_code_box(self, *args):
"""
Toggle code box
"""
is_visible = self.code_box.get_visible()
self.code_box.set_visible(not is_visible)
self.code_box.set_no_show_all(is_visible)
self.code_box.show_all()
def copy_code(self, event_box, box):
"""
Copy code shows the code box for a while (10s by default)
"""
self.timer = 0 self.timer = 0
self.parent.copy_code(eventbox) self.parent.copy_code(event_box)
code_box = self.row.get_children()[0].get_children()[1] code_box = self.row.get_children()[0].get_children()[1]
code_box.set_visible(True) code_box.set_visible(True)
code_box.set_no_show_all(False) code_box.set_no_show_all(False)
@ -50,6 +99,9 @@ class ListBoxRow(Thread):
GObject.timeout_add_seconds(1, self.update_timer) GObject.timeout_add_seconds(1, self.update_timer)
def update_timer(self, *args): def update_timer(self, *args):
"""
Update timer
"""
self.timer += 1 self.timer += 1
if self.timer > 10: if self.timer > 10:
code_box = self.row.get_children()[0].get_children()[1] code_box = self.row.get_children()[0].get_children()[1]
@ -58,36 +110,36 @@ class ListBoxRow(Thread):
return self.timer <= 10 return self.timer <= 10
def create_row(self): def create_row(self):
self.row = Gtk.ListBoxRow() """
Create ListBoxRow
"""
self.row.get_style_context().add_class("application-list-row") self.row.get_style_context().add_class("application-list-row")
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) h_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
pass_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.code_box.set_visible(False)
pass_box.set_visible(False) box.pack_start(h_box, True, True, 6)
vbox.pack_start(hbox, True, True, 6) box.pack_start(self.code_box, True, True, 6)
vbox.pack_start(pass_box, True, True, 6)
# ID # ID
label_id = Gtk.Label() label_id = Gtk.Label()
label_id.set_text(str(self.id)) label_id.set_text(str(self.id))
label_id.set_visible(False) label_id.set_visible(False)
label_id.set_no_show_all(True) label_id.set_no_show_all(True)
vbox.pack_end(label_id, False, False, 0) box.pack_end(label_id, False, False, 0)
# Checkbox # Checkbox
checkbox = Gtk.CheckButton() checkbox = Gtk.CheckButton()
checkbox.set_visible(False) checkbox.set_visible(False)
checkbox.set_no_show_all(True) checkbox.set_no_show_all(True)
checkbox.connect("toggled", self.parent.select_application) checkbox.connect("toggled", self.parent.select_application)
hbox.pack_start(checkbox, False, True, 6) h_box.pack_start(checkbox, False, True, 6)
# Application logo # Application logo
auth_icon = Authenticator.get_auth_icon(self.logo, auth_icon = Authenticator.get_auth_icon(self.logo, self.parent.app.pkgdatadir)
self.parent.app.pkgdatadir)
auth_logo = Gtk.Image(xalign=0) auth_logo = Gtk.Image(xalign=0)
auth_logo.set_from_pixbuf(auth_icon) auth_logo.set_from_pixbuf(auth_icon)
hbox.pack_start(auth_logo, False, True, 6) h_box.pack_start(auth_logo, False, True, 6)
# Application name # Application name
name_event = Gtk.EventBox() name_event = Gtk.EventBox()
@ -96,40 +148,36 @@ class ListBoxRow(Thread):
application_name.set_text(self.name) application_name.set_text(self.name)
name_event.connect("button-press-event", self.toggle_code_box) name_event.connect("button-press-event", self.toggle_code_box)
name_event.add(application_name) name_event.add(application_name)
hbox.pack_start(name_event, True, True, 6) h_box.pack_start(name_event, True, True, 6)
# Copy button # Copy button
copy_event = Gtk.EventBox() copy_event = Gtk.EventBox()
copy_button = Gtk.Image(xalign=0) copy_button = Gtk.Image(xalign=0)
copy_button.set_from_icon_name("edit-copy-symbolic", copy_button.set_from_icon_name("edit-copy-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
Gtk.IconSize.SMALL_TOOLBAR)
copy_button.set_tooltip_text(_("Copy the generated code..")) copy_button.set_tooltip_text(_("Copy the generated code.."))
copy_event.connect("button-press-event", self.copy_code) copy_event.connect("button-press-event", self.copy_code)
copy_event.add(copy_button) copy_event.add(copy_button)
hbox.pack_end(copy_event, False, True, 6) h_box.pack_end(copy_event, False, True, 6)
# Remove button # Remove button
remove_event = Gtk.EventBox() remove_event = Gtk.EventBox()
remove_button = Gtk.Image(xalign=0) remove_button = Gtk.Image(xalign=0)
remove_button.set_from_icon_name("user-trash-symbolic", remove_button.set_from_icon_name("user-trash-symbolic", Gtk.IconSize.SMALL_TOOLBAR)
Gtk.IconSize.SMALL_TOOLBAR)
remove_button.set_tooltip_text(_("Remove the source..")) remove_button.set_tooltip_text(_("Remove the source.."))
remove_event.add(remove_button) remove_event.add(remove_button)
remove_event.connect("button-press-event", remove_event.connect("button-press-event", self.parent.remove_application)
self.parent.remove_application) h_box.pack_end(remove_event, False, True, 6)
hbox.pack_end(remove_event, False, True, 6)
self.darea = Gtk.DrawingArea() self.drawing_area.set_size_request(24, 24)
self.darea.set_size_request(24, 24)
code_label = Gtk.Label(xalign=0) code_label = Gtk.Label(xalign=0)
code_label.get_style_context().add_class("application-secret-code") code_label.get_style_context().add_class("application-secret-code")
# TODO : show the real secret code # TODO : show the real secret code
self.update_code(code_label) self.update_code(code_label)
pass_box.set_no_show_all(True) self.code_box.set_no_show_all(True)
pass_box.pack_end(self.darea, False, True, 6) self.code_box.pack_end(self.drawing_area, False, True, 6)
pass_box.pack_start(code_label, False, True, 6) self.code_box.pack_start(code_label, False, True, 6)
self.row.add(vbox) self.row.add(box)
def get_counter(self): def get_counter(self):
return self.counter return self.counter
@ -142,16 +190,16 @@ class ListBoxRow(Thread):
self.regenerate_code() self.regenerate_code()
if self.timer != 0: if self.timer != 0:
self.timer = 0 self.timer = 0
self.darea.connect("draw", self.expose) self.drawing_area.connect("draw", self.expose)
self.row.changed() self.row.changed()
time.sleep(1) sleep(1)
def get_listrow(self): def get_list_row(self):
return self.row return self.row
def refresh_listbox(self): def refresh_listbox(self):
self.parent.listbox.hide() self.parent.list_box.hide()
self.parent.listbox.show_all() self.parent.list_box.show_all()
return self.code_generated return self.code_generated
def regenerate_code(self): def regenerate_code(self):
@ -163,7 +211,7 @@ class ListBoxRow(Thread):
def update_code(self, label): def update_code(self, label):
try: try:
code = self.code.get_secret_code() code = self.code.get_secret_code()
if code != None: if code:
label.set_text(code) label.set_text(code)
else: else:
raise TypeError raise TypeError
@ -172,13 +220,13 @@ class ListBoxRow(Thread):
label.set_text(_("Couldn't generate the secret code")) label.set_text(_("Couldn't generate the secret code"))
self.code_generated = False self.code_generated = False
def expose(self, darea, cairo): def expose(self, drawing_area, cairo):
try: try:
if self.code_generated: if self.code_generated:
cairo.arc(12, 12, 12, 0, (self.counter * cairo.arc(12, 12, 12, 0, (self.counter * 2 * pi / self.counter_max))
2 * pi / self.counter_max))
cairo.set_source_rgba(0, 0, 0, 0.4) cairo.set_source_rgba(0, 0, 0, 0.4)
cairo.fill_preserve() cairo.fill_preserve()
# TODO : get colors from default theme
if self.counter < self.counter_max / 2: if self.counter < self.counter_max / 2:
cairo.set_source_rgb(0, 0, 0) cairo.set_source_rgb(0, 0, 0)
else: else:

View file

@ -1,36 +1,39 @@
from gi import require_version from gi import require_version
require_version("Gtk", "3.0") require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gio, Gdk, GObject from gi.repository import Gtk
import logging
from TwoFactorAuth.models.settings import SettingsReader from TwoFactorAuth.models.settings import SettingsReader
from TwoFactorAuth.widgets.change_password import PasswordWindow from TwoFactorAuth.widgets.change_password import PasswordWindow
from gettext import gettext as _ from gettext import gettext as _
from hashlib import md5
class SettingsWindow(Gtk.Window): class SettingsWindow(Gtk.Window):
notebook = Gtk.Notebook()
time_spin_button = Gtk.SpinButton()
enable_switch = Gtk.Switch()
password_button = Gtk.Button()
def __init__(self, parent): def __init__(self, parent):
self.parent = parent self.parent = parent
self.cfg = SettingsReader() self.cfg = SettingsReader()
self.generate_window() self.generate_window()
self.generate_compenents() self.generate_components()
self.show_all() self.show_all()
def generate_window(self): def generate_window(self):
Gtk.Window.__init__(self, title=_("Settings"), modal=True, Gtk.Window.__init__(self, title=_("Settings"), modal=True,
destroy_with_parent=True) destroy_with_parent=True)
self.connect("delete-event", lambda x, y: self.destroy()) self.connect("delete-event", self.close_window)
self.resize(300, 300) self.resize(300, 300)
self.set_size_request(300, 300) self.set_size_request(300, 300)
self.set_resizable(False) self.set_resizable(False)
self.set_position(Gtk.WindowPosition.CENTER) self.set_position(Gtk.WindowPosition.CENTER)
self.set_transient_for(self.parent) self.set_transient_for(self.parent)
def generate_compenents(self): def generate_components(self):
self.notebook = Gtk.Notebook() """
Generate all the components
"""
self.add(self.notebook) self.add(self.notebook)
user_settings = self.generate_user_settings() user_settings = self.generate_user_settings()
user_settings.set_border_width(10) user_settings.set_border_width(10)
self.notebook.append_page(user_settings, Gtk.Label(_('Preferences'))) self.notebook.append_page(user_settings, Gtk.Label(_('Preferences')))
@ -40,34 +43,40 @@ class SettingsWindow(Gtk.Window):
self.notebook.append_page(login_settings, Gtk.Label(_('Account'))) self.notebook.append_page(login_settings, Gtk.Label(_('Account')))
def generate_login_settings(self): def generate_login_settings(self):
mainbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) """
Create a box with login settings components
:return (Gtk.Box): Box contains all the components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
enable_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) enable_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
enable_label = Gtk.Label(_("Enable password: ")) enable_label = Gtk.Label(_("Enable password: "))
enable_switch = Gtk.Switch() self.enable_switch.set_active(bool(self.cfg.read("state", "login")))
enable_switch.set_active(bool(self.cfg.read("state", "login"))) self.enable_switch.connect("notify::active", self.on_switch_activated)
enable_switch.connect("notify::active", self.on_switch_activated)
enable_box.pack_start(enable_label, False, True, 0) enable_box.pack_start(enable_label, False, True, 0)
enable_box.pack_end(enable_switch, False, True, 0) enable_box.pack_end(self.enable_switch, False, True, 0)
password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
password_label = Gtk.Label(_("Password: ")) password_label = Gtk.Label(_("Password: "))
password_button = Gtk.Button() self.password_button.get_style_context().add_class("flat")
password_button.get_style_context().add_class("flat") self.password_button.get_style_context().add_class("text-button")
password_button.get_style_context().add_class("text-button") self.password_button.set_label("******")
password_button.set_label("******") self.password_button.connect("clicked", self.new_password_window)
password_button.connect("clicked", self.new_password_window)
password_box.pack_start(password_label, False, True, 0) password_box.pack_start(password_label, False, True, 0)
password_box.pack_end(password_button, False, True, 0) password_box.pack_end(self.password_button, False, True, 0)
mainbox.pack_start(enable_box, False, True, 6) main_box.pack_start(enable_box, False, True, 6)
mainbox.pack_start(password_box, False, True, 6) main_box.pack_start(password_box, False, True, 6)
return mainbox return main_box
def generate_user_settings(self): def generate_user_settings(self):
mainbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) """
Create a box with user settings components
:return (Gtk.Box): Box contains all the components
"""
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
time_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) time_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
time_label = Gtk.Label(_("Password generation time (s): ")) time_label = Gtk.Label(_("Password generation time (s): "))
@ -75,31 +84,39 @@ class SettingsWindow(Gtk.Window):
if default_value < 30 or default_value > 120: if default_value < 30 or default_value > 120:
default_value = 30 default_value = 30
adjustment = Gtk.Adjustment(default_value, 10, 120, 1, 10, 0) adjustment = Gtk.Adjustment(default_value, 10, 120, 1, 10, 0)
time_spinbutton = Gtk.SpinButton() self.time_spin_button.connect("value-changed", self.on_time_changed)
time_spinbutton.connect("value-changed", self.on_time_changed) self.time_spin_button.set_adjustment(adjustment)
time_spinbutton.set_adjustment(adjustment) self.time_spin_button.set_value(default_value)
time_spinbutton.set_value(default_value)
time_box.pack_start(time_label, False, True, 0) time_box.pack_start(time_label, False, True, 0)
time_box.pack_end(time_spinbutton, False, True, 0) time_box.pack_end(self.time_spin_button, False, True, 0)
mainbox.pack_start(time_box, False, True, 6) main_box.pack_start(time_box, False, True, 6)
return mainbox return main_box
def new_password_window(self, *args): def new_password_window(self, *args):
"""
Show a new password window
"""
PasswordWindow(self) PasswordWindow(self)
def on_time_changed(self, spinbutton): def on_time_changed(self, spin_button):
self.cfg.update("refresh-time", spinbutton.get_value_as_int(), """
Update time tog generate a new secret code
"""
self.cfg.update("refresh-time", spin_button.get_value_as_int(),
"preferences") "preferences")
""" def on_password_changed(self, entry): def on_switch_activated(self, switch, *args):
password = md5(entry.get_text().encode('utf-8')).hexdigest() """
self.cfg.update("password", password, "login") Update password state : enabled/disabled
""" """
self.password_button.set_sensitive(switch.get_active())
def on_switch_activated(self, switch, gparam):
password_box = switch.get_parent().get_parent().get_children()[1]
password_entry = password_box.get_children()[1]
password_entry.set_sensitive(switch.get_active())
self.cfg.update("state", switch.get_active(), "login") self.cfg.update("state", switch.get_active(), "login")
def close_window(self, *args):
"""
Close the window
"""
self.destroy()

View file

@ -1,31 +1,50 @@
from gi import require_version from gi import require_version
require_version("Gtk", "3.0") require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gio, Gdk, GObject from gi.repository import Gtk, Gio, Gdk
from TwoFactorAuth.widgets.add_authenticator import AddAuthenticator from TwoFactorAuth.widgets.add_authenticator import AddAuthenticator
from TwoFactorAuth.widgets.confirmation import ConfirmationMessage from TwoFactorAuth.widgets.confirmation import ConfirmationMessage
from TwoFactorAuth.widgets.listrow import ListBoxRow from TwoFactorAuth.widgets.listrow import ListBoxRow
from threading import Thread
import logging import logging
from hashlib import md5 from hashlib import md5
from gettext import gettext as _ from gettext import gettext as _
class Window(Gtk.ApplicationWindow): class Window(Gtk.ApplicationWindow):
app = None app = None
selected_app_idx = None selected_app_idx = None
selected_count = 0 selected_count = 0
hb = Gtk.HeaderBar()
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
search_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
login_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
no_apps_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
apps_list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
apps_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
list_box = Gtk.ListBox()
search_button = Gtk.ToggleButton()
add_button = Gtk.Button()
remove_button = Gtk.Button()
cancel_button = Gtk.Button()
select_button = Gtk.Button()
password_entry = Gtk.Entry()
search_entry = Gtk.Entry()
def __init__(self, application): def __init__(self, application):
self.app = application self.app = application
self.generate_window() self.generate_window()
self.generate_headerbar() self.generate_header_bar()
self.generate_searchbar() self.generate_search_bar()
self.generate_applications_list() self.generate_applications_list()
if self.app.locked: self.generate_no_apps_box()
self.generate_login_form() self.generate_login_form()
self.get_children()[0].get_children()[0].set_visible(False) self.refresh_window()
def generate_window(self, *args): def generate_window(self, *args):
"""
Generate application window (Gtk.Window)
"""
Gtk.ApplicationWindow.__init__(self, Gtk.WindowType.TOPLEVEL, Gtk.ApplicationWindow.__init__(self, Gtk.WindowType.TOPLEVEL,
application=self.app) application=self.app)
self.set_position(Gtk.WindowPosition.CENTER) self.set_position(Gtk.WindowPosition.CENTER)
@ -35,319 +54,332 @@ class Window(Gtk.ApplicationWindow):
self.set_resizable(False) self.set_resizable(False)
self.connect("key_press_event", self.on_key_press) self.connect("key_press_event", self.on_key_press)
self.connect("delete-event", lambda x, y: self.app.on_quit()) self.connect("delete-event", lambda x, y: self.app.on_quit())
self.add(Gtk.Box(orientation=Gtk.Orientation.VERTICAL)) self.add(self.main_box)
def on_key_press(self, app, keyevent): def on_key_press(self, app, key_event):
keypressed = Gdk.keyval_name(keyevent.keyval).lower() """
Keyboard Listener handling
"""
keypress = Gdk.keyval_name(key_event.keyval).lower()
if not self.app.locked: if not self.app.locked:
CONTROL_MASK = Gdk.ModifierType.CONTROL_MASK control_mask = Gdk.ModifierType.CONTROL_MASK
search_box = self.get_children()[0].get_children()[
0].get_children()[0]
count = self.app.auth.count() count = self.app.auth.count()
if keypressed == "c": if keypress == "c":
if keyevent.state == CONTROL_MASK: if key_event.state == control_mask:
self.copy_code() self.copy_code()
elif keypressed == "f": elif keypress == "f":
if keyevent.state == CONTROL_MASK: if key_event.state == control_mask:
self.toggle_searchobox() self.toggle_search_box()
elif keypressed == "s": elif keypress == "s":
if keyevent.state == CONTROL_MASK: if key_event.state == control_mask:
self.toggle_select() self.toggle_select()
elif keypressed == "n": elif keypress == "n":
if keyevent.state == CONTROL_MASK: if key_event.state == control_mask:
self.add_application() self.add_application()
elif keypressed == "delete" and not search_box.get_visible(): elif keypress == "delete" and not self.search_box.get_visible():
self.remove_application() self.remove_application()
elif keypressed == "return": elif keypress == "return":
if count > 0: if count > 0:
if self.listbox.get_selected_row(): if self.list_box.get_selected_row():
index = self.listbox.get_selected_row().get_index() index = self.list_box.get_selected_row().get_index()
else: else:
index = 0 index = 0
listrow_box = self.listbox.get_row_at_index(index) ListBoxRow.toggle_code_box(self.list_box.get_row_at_index(index))
listbox = listrow_box.get_children()[0] elif keypress == "backspace":
code_box = listbox.get_children()[1] if len(self.search_entry.get_text()) == 0:
is_visible = code_box.get_no_show_all() self.hide_searchbar()
code_box.set_no_show_all(not is_visible)
code_box.set_visible(is_visible)
code_box.show_all()
elif keypressed == "backspace":
search_box = self.get_children()[0].get_children()[
0].get_children()[0]
search_entry = search_box.get_children()[0]
if len(search_entry.get_text()) == 0:
search_box.set_visible(False)
self.listbox.set_filter_func(lambda x, y, z: True, None,
False)
else: else:
if keypressed == "return": if keypress == "return":
self.on_login_clicked() self.on_unlock_clicked()
def filter_applications(self, entry): def filter_applications(self, entry):
data = entry.get_text() data = entry.get_text()
if len(data) != 0: if len(data) != 0:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic")
"edit-clear-symbolic")
else: else:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, None)
None) self.list_box.set_filter_func(self.filter_func, data, False)
self.listbox.set_filter_func(self.filter_func, data, False)
def generate_searchbar(self): def hide_search_bar(self):
hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) """
search_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) Hide search bar
search_entry = Gtk.Entry() """
search_box.set_margin_left(60) self.search_box.set_visible(False)
search_entry.set_width_chars(21) self.list_box.set_filter_func(lambda x, y, z: True, None, False)
search_entry.connect("changed", self.filter_applications)
search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,
"system-search-symbolic")
search_box.pack_start(search_entry, False, True, 0) def generate_search_bar(self):
hbox.pack_start(search_box, False, True, 6) """
self.get_children()[0].pack_start(hbox, True, True, 0) Generate search bar box and entry
search_box.set_no_show_all(True) """
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
box.set_margin_left(60)
self.search_entry.set_width_chars(22)
self.search_entry.connect("changed", self.filter_applications)
self.search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, "system-search-symbolic")
box.pack_start(self.search_entry, False, True, 0)
self.search_box.pack_start(box, False, True, 6)
self.search_box.set_no_show_all(True)
self.apps_box.pack_start(self.search_box, False, True, 0)
self.main_box.pack_start(self.apps_box, True, True, 0)
def remove_selected(self, *args): def remove_selected(self, *args):
message = _("Do you really want to remove this application?") """
Remove selected applications
"""
message = _("Do you really want to remove selected applications?")
confirmation = ConfirmationMessage(self, message) confirmation = ConfirmationMessage(self, message)
confirmation.show() confirmation.show()
if confirmation.get_confirmation(): if confirmation.get_confirmation():
for row in self.listbox.get_children(): for row in self.list_box.get_children():
checkbox = self.get_checkbox_from_row(row) checkbox = ListBoxRow.get_checkbox(row)
if checkbox.get_active(): if checkbox.get_active():
label_id = row.get_children()[0].get_children()[2] label_id = ListBoxRow.get_id(row)
label_id = int(label_id.get_text())
self.app.auth.remove_by_id(label_id) self.app.auth.remove_by_id(label_id)
self.listbox.remove(row) self.list_box.remove(row)
self.listbox.unselect_all() self.list_box.unselect_all()
confirmation.destroy() confirmation.destroy()
self.refresh_window() self.refresh_window()
def generate_login_form(self): def generate_login_form(self):
mainbox = self.get_children()[0] """
Generate login form
login_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) """
password_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) password_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
password_entry = Gtk.Entry()
password_entry.set_visibility(False)
password_entry.set_placeholder_text(_("Enter your password"))
password_box.pack_start(password_entry, False, False, 6)
login_button = Gtk.Button() self.password_entry.set_visibility(False)
login_button.set_label(_("Login")) self.password_entry.set_placeholder_text(_("Enter your password"))
login_button.connect("clicked", self.on_login_clicked) password_box.pack_start(self.password_entry, False, False, 6)
password_box.pack_start(login_button, False, False, 6)
login_box.pack_start(password_box, True, False, 6)
mainbox.pack_start(login_box, True, False, 0) unlock_button = Gtk.Button()
mainbox.get_children()[0].set_no_show_all(self.app.locked) unlock_button.set_label(_("Unlock"))
mainbox.get_children()[1].set_no_show_all(self.app.locked) unlock_button.connect("clicked", self.on_unlock_clicked)
mainbox.get_children()[2].set_no_show_all(not self.app.locked)
self.hide_headerbar()
def on_login_clicked(self, *args): password_box.pack_start(unlock_button, False, False, 6)
login_box = self.get_children()[0].get_children()[2] self.login_box.pack_start(password_box, True, False, 6)
entry = login_box.get_children()[0].get_children()[0]
password = md5(entry.get_text().encode("utf-8")).hexdigest() self.main_box.pack_start(self.login_box, True, False, 0)
def on_unlock_clicked(self, *args):
"""
Password check and unlock
"""
password = self.password_entry.get_text()
password = md5(password.encode("utf-8")).hexdigest()
if password == self.app.cfg.read("password", "login"): if password == self.app.cfg.read("password", "login"):
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "") self.password_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, None)
self.toggle_app_lock() self.toggle_app_lock()
self.app.locked = False
self.app.toggle_settings_menu()
self.password_entry.set_text("")
self.app.toggle_app_lock_menu()
else: else:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, self.password_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
"dialog-error-symbolic")
def toggle_app_lock(self): def toggle_app_lock(self):
"""
Lock/unlock the application
"""
self.app.locked = not self.app.locked self.app.locked = not self.app.locked
mainbox = self.get_children()[0] self.app.toggle_app_lock_menu()
mainbox.get_children()[2].set_visible(self.app.locked)
mainbox.get_children()[2].set_no_show_all(not self.app.locked)
self.refresh_window() self.refresh_window()
def hide_headerbar(self): def toggle_boxes(self, apps_box, no_apps_box, login_box):
hb = self.get_children()[1] """
hb.get_children()[0].get_children()[0].set_no_show_all(True) Change the status of all the boxes in one time
hb.get_children()[0].get_children()[1].set_no_show_all(True) :param apps_box: bool
hb.get_children()[1].get_children()[0].set_no_show_all(True) :param no_apps_box: bool
hb.get_children()[1].get_children()[1].set_no_show_all(True) :param login_box: bool
hb.get_children()[1].get_children()[2].set_no_show_all(True) :return:
"""
self.login_box.set_visible(login_box)
self.login_box.set_no_show_all(not login_box)
self.apps_box.set_visible(apps_box)
self.apps_box.set_no_show_all(not apps_box)
self.no_apps_box.set_visible(no_apps_box)
self.no_apps_box.set_no_show_all(not no_apps_box)
def generate_headerbar(self): def hide_header_bar(self):
hb = Gtk.HeaderBar() """
hb.set_show_close_button(True) Hide all buttons on the header bar
"""
self.toggle_hb_buttons(False, False, False, False, False)
def generate_header_bar(self):
"""
Generate a header bar box
"""
self.hb.set_show_close_button(True)
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
Gtk.StyleContext.add_class(left_box.get_style_context(), "linked")
Gtk.StyleContext.add_class(right_box.get_style_context(), "linked")
self.remove_button = Gtk.Button()
remove_icon = Gio.ThemedIcon(name="user-trash-symbolic") remove_icon = Gio.ThemedIcon(name="user-trash-symbolic")
remove_image = Gtk.Image.new_from_gicon(remove_icon, remove_image = Gtk.Image.new_from_gicon(remove_icon, Gtk.IconSize.BUTTON)
Gtk.IconSize.BUTTON) self.remove_button.set_tooltip_text(_("Remove selected two factor auth sources"))
self.remove_button.set_tooltip_text(_("Remove selected two factor auth "
"sources"))
self.remove_button.set_sensitive(False)
self.remove_button.set_image(remove_image) self.remove_button.set_image(remove_image)
self.remove_button.set_sensitive(False)
self.remove_button.set_no_show_all(True) self.remove_button.set_no_show_all(True)
self.remove_button.connect("clicked", self.remove_selected) self.remove_button.connect("clicked", self.remove_selected)
left_box.add(self.remove_button)
add_button = Gtk.Button()
add_icon = Gio.ThemedIcon(name="list-add-symbolic") add_icon = Gio.ThemedIcon(name="list-add-symbolic")
add_image = Gtk.Image.new_from_gicon(add_icon, add_image = Gtk.Image.new_from_gicon(add_icon, Gtk.IconSize.BUTTON)
Gtk.IconSize.BUTTON) self.add_button.set_tooltip_text(_("Add a new application"))
add_button.set_tooltip_text(_("Add a new application")) self.add_button.set_image(add_image)
add_button.set_image(add_image) self.add_button.connect("clicked", self.add_application)
add_button.connect("clicked", self.add_application)
left_box.add(add_button) left_box.add(self.remove_button)
left_box.add(self.add_button)
select_button = Gtk.Button()
select_icon = Gio.ThemedIcon(name="object-select-symbolic") select_icon = Gio.ThemedIcon(name="object-select-symbolic")
select_image = Gtk.Image.new_from_gicon(select_icon, select_image = Gtk.Image.new_from_gicon(select_icon, Gtk.IconSize.BUTTON)
Gtk.IconSize.BUTTON) self.select_button.set_tooltip_text(_("Select mode"))
select_button.set_tooltip_text(_("Select mode")) self.select_button.set_image(select_image)
select_button.set_image(select_image) self.select_button.connect("clicked", self.toggle_select)
select_button.connect("clicked", self.toggle_select) self.select_button.set_no_show_all(not self.app.auth.count() > 0)
select_button.set_no_show_all(not self.app.auth.count() > 0)
search_button = Gtk.ToggleButton()
search_icon = Gio.ThemedIcon(name="system-search-symbolic") search_icon = Gio.ThemedIcon(name="system-search-symbolic")
search_image = Gtk.Image.new_from_gicon(search_icon, search_image = Gtk.Image.new_from_gicon(search_icon, Gtk.IconSize.BUTTON)
Gtk.IconSize.BUTTON) self.search_button.set_tooltip_text(_("Search"))
search_button.set_tooltip_text(_("Search")) self.search_button.set_image(search_image)
search_button.set_image(search_image) self.search_button.connect("clicked", self.toggle_search_box)
search_button.connect("clicked", self.toggle_searchobox) self.search_button.set_no_show_all(not self.app.auth.count() > 0)
search_button.set_no_show_all(not self.app.auth.count() > 0)
cancel_buton = Gtk.Button() self.cancel_button.set_label(_("Cancel"))
cancel_buton.set_label(_("Cancel")) self.cancel_button.connect("clicked", self.toggle_select)
cancel_buton.connect("clicked", self.toggle_select) self.cancel_button.set_no_show_all(True)
cancel_buton.set_no_show_all(True)
right_box.add(search_button) right_box.add(self.search_button)
right_box.add(select_button) right_box.add(self.select_button)
right_box.add(cancel_buton) right_box.add(self.cancel_button)
hb.pack_start(left_box) self.hb.pack_start(left_box)
hb.pack_end(right_box) self.hb.pack_end(right_box)
self.set_titlebar(hb) self.set_titlebar(self.hb)
def add_application(self, *args): def add_application(self, *args):
"""
Create add application window
"""
AddAuthenticator(self) AddAuthenticator(self)
def toggle_searchobox(self, *args): def toggle_search_box(self, *args):
"""
Toggle search box, only if there's an application
"""
if self.app.auth.count() > 0: if self.app.auth.count() > 0:
search_box = self.get_children()[0].get_children()[ is_visible = self.search_box.get_no_show_all()
0].get_children()[0]
is_visible = search_box.get_no_show_all() self.search_box.set_no_show_all(not is_visible)
self.search_box.set_visible(is_visible)
self.search_box.show_all()
headerbar = self.get_children()[1]
search_button = headerbar.get_children()[1].get_children()[0]
search_box.set_no_show_all(not is_visible)
search_box.set_visible(is_visible)
search_box.show_all()
if is_visible: if is_visible:
search_button.get_style_context().add_class("toggle") self.search_button.get_style_context().add_class("toggle")
search_box.get_children()[0].grab_focus_without_selecting() self.search_entry.grab_focus_without_selecting()
else: else:
search_button.get_style_context().remove_class("toggle") self.search_button.get_style_context().remove_class("toggle")
self.listbox.set_filter_func(lambda x, y, z: True, None, False) self.list_box.set_filter_func(lambda x, y, z: True, None, False)
def toggle_select(self, *args): def toggle_select(self, *args):
i = 0 """
button_visible = self.remove_button.get_visible() Toggle select mode
headerbar = self.get_children()[1] """
headerbar.set_show_close_button(button_visible) is_visible = self.remove_button.get_visible()
headerbar.get_children()[0].get_children()[
1].set_visible(button_visible)
headerbar.get_children()[1].get_children()[
1].set_visible(button_visible)
headerbar.get_children()[1].get_children()[
2].set_visible(not button_visible)
self.remove_button.set_visible(not button_visible)
self.remove_button.set_no_show_all(button_visible)
if not button_visible: self.remove_button.set_visible(not is_visible)
self.listbox.set_selection_mode(Gtk.SelectionMode.MULTIPLE) self.remove_button.set_no_show_all(is_visible)
headerbar.get_style_context().add_class("selection-mode")
self.hb.set_show_close_button(is_visible)
self.cancel_button.set_visible(not is_visible)
self.remove_button.set_visible(not is_visible)
self.add_button.set_visible(is_visible)
self.select_button.set_visible(is_visible)
if not is_visible:
self.list_box.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
self.hb.get_style_context().add_class("selection-mode")
else: else:
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE) self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
headerbar.get_style_context().remove_class("selection-mode") self.hb.get_style_context().remove_class("selection-mode")
if self.selected_app_idx: if self.selected_app_idx:
index = self.selected_app_idx index = self.selected_app_idx
else: else:
index = 0 index = 0
listrow_box = self.listbox.get_row_at_index(index) list_row_box = self.list_box.get_row_at_index(index)
self.listbox.select_row(listrow_box) self.list_box.select_row(list_row_box)
for row in self.listbox.get_children(): for row in self.list_box.get_children():
checkbox = self.get_checkbox_from_row(row) checkbox = ListBoxRow.get_checkbox(row)
visible = checkbox.get_visible() visible = checkbox.get_visible()
selected = checkbox.get_active() selected = checkbox.get_active()
if not button_visible: if not is_visible:
self.select_application(checkbox) self.select_application(checkbox)
checkbox.set_visible(not visible) checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible) checkbox.set_no_show_all(visible)
def select_application(self, checkbutton): def select_application(self, checkbutton):
"""
Select an application in the application ListBox
:param checkbutton:
"""
is_active = checkbutton.get_active() is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
listbox_row = checkbutton.get_parent().get_parent().get_parent() listbox_row = checkbutton.get_parent().get_parent().get_parent()
if is_active: if is_active:
self.listbox.select_row(listbox_row) self.list_box.select_row(listbox_row)
if is_visible: if is_visible:
self.selected_count += 1 self.selected_count += 1
else: else:
self.listbox.unselect_row(listbox_row) self.list_box.unselect_row(listbox_row)
if is_visible: if is_visible:
self.selected_count -= 1 self.selected_count -= 1
self.remove_button.set_sensitive(self.selected_count > 0) self.remove_button.set_sensitive(self.selected_count > 0)
def filter_func(self, row, data, notify_destroy): def filter_func(self, row, data, notify_destroy):
app_label = row.get_children()[0].get_children()[0].get_children() """
Filter function, used to check if the entered data exists on the application ListBox
"""
app_label = ListBoxRow.get_label(row)
data = data.strip() data = data.strip()
if len(data) > 0: if len(data) > 0:
return data in app_label[2].get_children()[0].get_text().lower() return data in app_label
else: else:
return True return True
def get_checkbox_from_row(self, row): def select_row(self, list_box, listbox_row):
if row: """
return row.get_children()[0].get_children()[0].get_children()[0] Select row @override the clicked event by default for ListBoxRow
else: """
return None
def select_row(self, listbox, listbox_row):
index = listbox_row.get_index() index = listbox_row.get_index()
button_visible = self.remove_button.get_visible() button_visible = self.remove_button.get_visible()
checkbox = self.get_checkbox_from_row(listbox_row) checkbox = ListBoxRow.get_checkbox(listbox_row)
if button_visible: if button_visible:
checkbox.set_active(not checkbox.get_active()) checkbox.set_active(not checkbox.get_active())
else: else:
if self.selected_app_idx: if self.selected_app_idx:
listrow_box = self.listbox.get_row_at_index( self.list_box.unselect_row(self.list_box.get_row_at_index(self.selected_app_idx))
self.selected_app_idx)
self.listbox.unselect_row(listbox_row)
self.selected_app_idx = index self.selected_app_idx = index
listrow_box = self.listbox.get_row_at_index(index) self.list_box.select_row(self.list_box.get_row_at_index(index))
self.listbox.select_row(listbox_row)
# TODO : show a nice message when no application is added
def generate_applications_list(self): def generate_applications_list(self):
list_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) """
Generate an application ListBox inside of a ScrolledWindow
"""
count = self.app.auth.count() count = self.app.auth.count()
# Create a ScrolledWindow for installed applications # Create a ScrolledWindow for installed applications
self.listbox = Gtk.ListBox() self.list_box.get_style_context().add_class("applications-list")
self.listbox.get_style_context().add_class("applications-list") self.list_box.set_adjustment()
self.listbox.set_adjustment() self.list_box.connect("row_activated", self.select_row)
self.listbox.connect("row_activated", self.select_row) self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE) self.apps_list_box.pack_start(self.list_box, True, True, 0)
list_box.pack_start(self.listbox, True, True, 0)
scrolled_win = Gtk.ScrolledWindow() scrolled_win = Gtk.ScrolledWindow()
scrolled_win.add_with_viewport(list_box) scrolled_win.add_with_viewport(self.apps_list_box)
self.get_children()[0].get_children()[0].pack_start( self.apps_box.pack_start(scrolled_win, True, True, 0)
scrolled_win, True, True, 0)
apps = self.app.auth.fetch_apps() apps = self.app.auth.fetch_apps()
i = 0 i = 0
@ -355,39 +387,46 @@ class Window(Gtk.ApplicationWindow):
while i < count: while i < count:
row = ListBoxRow(self, apps[i][0], apps[i][1], apps[i][2], row = ListBoxRow(self, apps[i][0], apps[i][1], apps[i][2],
apps[i][3]) apps[i][3])
self.listbox.add(row.get_listrow()) self.list_box.add(row.get_list_row())
i += 1 i += 1
nolist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) def generate_no_apps_box(self):
vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) """
Generate a box with no applications message
"""
logo_image = Gtk.Image() logo_image = Gtk.Image()
logo_image.set_from_icon_name("dialog-information-symbolic", logo_image.set_from_icon_name("dialog-information-symbolic",
Gtk.IconSize.DIALOG) Gtk.IconSize.DIALOG)
vbox.pack_start(logo_image, False, False, 6)
no_proivders_label = Gtk.Label() no_apps_label = Gtk.Label()
no_proivders_label.set_text(_("There's no application at the moment")) no_apps_label.set_text(_("There's no application at the moment"))
vbox.pack_start(no_proivders_label, False, False, 6)
nolist_box.pack_start(vbox, True, True, 0) self.no_apps_box.pack_start(logo_image, False, False, 6)
self.get_children()[0].pack_start(nolist_box, True, True, 0) self.no_apps_box.pack_start(no_apps_label, False, False, 6)
self.get_children()[0].get_children()[0].set_no_show_all(count == 0) self.main_box.pack_start(self.no_apps_box, True, False, 0)
self.get_children()[0].get_children()[
1].set_no_show_all(not count == 0)
def update_list(self, id, name, secret_code, image): def append(self, uid, name, secret_code, image):
row = ListBoxRow(self, id, name, secret_code, image) """
self.listbox.add(row.get_listrow()) Add an element to the ListBox
self.listbox.show_all() :param uid: application id
:param name: application name
:param secret_code: application secret code
:param image: application image path or icon name
"""
row = ListBoxRow(self, uid, name, secret_code, image)
self.list_box.add(row.get_list_row())
self.list_box.show_all()
def copy_code(self, *args): def copy_code(self, *args):
"""
Copy the secret code to clipboard
"""
if len(args) > 0: if len(args) > 0:
# if the code is called by clicking on copy button, select the right ListBowRow
row = args[0].get_parent().get_parent().get_parent() row = args[0].get_parent().get_parent().get_parent()
self.listbox.select_row(row) self.list_box.select_row(row)
selected_row = self.listbox.get_selected_row() selected_row = self.list_box.get_selected_row()
label = selected_row.get_children()[0].get_children()[1].get_children() code = ListBoxRow.get_label(selected_row)
code = label[0].get_text()
try: try:
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.clear() clipboard.clear()
@ -397,56 +436,70 @@ class Window(Gtk.ApplicationWindow):
logging.error(str(e)) logging.error(str(e))
def refresh_window(self): def refresh_window(self):
mainbox = self.get_children()[0] """
Refresh windows components
"""
count = self.app.auth.count() count = self.app.auth.count()
headerbar = self.get_children()[1] is_locked = self.app.locked
if count == 0:
mainbox.get_children()[0].set_visible(False) if is_locked:
mainbox.get_children()[1].set_visible(True) self.toggle_boxes(False, False, True)
mainbox.get_children()[1].set_no_show_all(False) self.hide_header_bar()
mainbox.get_children()[1].show_all()
headerbar.get_children()[0].get_children()[1].set_visible(True)
headerbar.get_children()[1].get_children()[1].set_visible(False)
headerbar.get_children()[1].get_children()[2].set_visible(False)
headerbar.set_show_close_button(True)
headerbar.get_style_context().remove_class("selection-mode")
else: else:
self.get_children()[0].get_children()[0].set_no_show_all(False) if count == 0:
self.get_children()[0].get_children()[0].set_visible(True) self.toggle_boxes(False, True, False)
self.get_children()[0].get_children()[0].show_all() self.toggle_hb_buttons(False, True, False, False, False)
headerbar.get_children()[0].get_children()[0].set_visible(False) else:
headerbar.get_children()[1].get_children()[0].set_visible(True) self.toggle_boxes(True, False, False)
headerbar.get_children()[1].get_children()[1].set_visible(True) self.toggle_hb_buttons(False, True, True, True, False)
mainbox.get_children()[1].set_visible(False)
headerbar.get_children()[0].get_children()[1].set_no_show_all(False) self.main_box.show_all()
headerbar.get_children()[0].get_children()[1].set_visible(True) self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
headerbar = self.get_children()[1] def toggle_hb_buttons(self, remove, add, search, select, cancel):
left_box = headerbar.get_children()[0] """
right_box = headerbar.get_children()[1] Toggle header bar buttons visibilty
right_box.get_children()[0].set_visible(count > 0) :param remove: (bool)
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE) :param add: (bool)
left_box.get_children()[0].set_visible(False) :param search: (bool)
:param select: (bool)
:param cancel: (bool)
"""
self.add_button.set_visible(add)
self.add_button.set_no_show_all(not add)
self.remove_button.set_visible(remove)
self.remove_button.set_no_show_all(not remove)
self.cancel_button.set_visible(cancel)
self.cancel_button.set_no_show_all(not cancel)
self.select_button.set_visible(select)
self.select_button.set_no_show_all(not select)
self.search_button.set_visible(search)
self.search_button.set_no_show_all(not search)
def remove_application(self, *args): def remove_application(self, *args):
"""
Remove an application
"""
if len(args) > 0: if len(args) > 0:
row = args[0].get_parent().get_parent().get_parent() row = args[0].get_parent().get_parent().get_parent()
self.listbox.select_row(row) self.list_box.select_row(row)
message = _("Do you really want to remove the application?") message = _("Do you really want to remove the application?")
confirmation = ConfirmationMessage(self, message) confirmation = ConfirmationMessage(self, message)
confirmation.show() confirmation.show()
if confirmation.get_confirmation(): if confirmation.get_confirmation():
if self.listbox.get_selected_row(): if self.list_box.get_selected_row():
selected_row = self.listbox.get_selected_row() selected_row = self.list_box.get_selected_row()
self.listbox.remove(selected_row) self.list_box.remove(selected_row)
label_id = selected_row.get_children()[0].get_children()[2] app_id = ListBoxRow.get_id(selected_row)
self.app.auth.remove_by_id(int(label_id.get_text())) self.app.auth.remove_by_id(app_id)
confirmation.destroy() confirmation.destroy()
self.refresh_window() self.refresh_window()
def show_about(self, *args): def show_about(self, *args):
"""
Shows about dialog
"""
builder = Gtk.Builder() builder = Gtk.Builder()
builder.add_from_file(self.app.pkgdatadir + "/data/about.ui") builder.add_from_file(self.app.pkgdatadir + "/data/about.ui")
@ -456,9 +509,13 @@ class Window(Gtk.ApplicationWindow):
dialog.destroy() dialog.destroy()
def show_shortcuts(self, *args): def show_shortcuts(self, *args):
builder = Gtk.Builder() """
builder.add_from_file(self.app.pkgdatadir + "/data/shortcuts.ui") Shows keyboard shortcuts
"""
if Gtk.get_major_version() >= 3 and Gtk.get_minor_version() >= 20:
builder = Gtk.Builder()
builder.add_from_file(self.app.pkgdatadir + "/data/shortcuts.ui")
shortcuts = builder.get_object("shortcuts") shortcuts = builder.get_object("shortcuts")
shortcuts.set_transient_for(self) shortcuts.set_transient_for(self)
shortcuts.show() shortcuts.show()

View file

@ -6,7 +6,6 @@ desktop_DATA = twofactorauth.desktop
uidir = $(pkgdatadir)/data uidir = $(pkgdatadir)/data
ui_DATA = about.ui \ ui_DATA = about.ui \
shortcuts.ui \ shortcuts.ui \
menu.ui \
style.css style.css
appdatadir = $(pkgdatadir)/appdata appdatadir = $(pkgdatadir)/appdata

View file

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<menu id="app-menu">
<section>
<item>
<attribute name="action">app.settings</attribute>
<attribute name="label" translatable="yes">Settings</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">app.shortcuts</attribute>
<attribute name="label" translatable="yes">Keyboard shortcuts</attribute>
</item>
</section>
<section>
<item>
<attribute name="action">app.about</attribute>
<attribute name="label" translatable="yes">About</attribute>
</item>
<item>
<attribute name="action">app.quit</attribute>
<attribute name="label" translatable="yes">Quit</attribute>
<attribute name="accel">&lt;Primary&gt;q</attribute>
</item>
</section>
</menu>
</interface>

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-05-29 22:16+0200\n" "POT-Creation-Date: 2016-05-30 05:45+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,7 +17,8 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: data/menu.ui:7 TwoFactorAuth/widgets/settings.py:21 #: data/menu.ui:7 TwoFactorAuth/widgets/settings.py:23
#: TwoFactorAuth/application.py:52 TwoFactorAuth/application.py:92
msgid "Settings" msgid "Settings"
msgstr "" msgstr ""
@ -25,11 +26,11 @@ msgstr ""
msgid "Keyboard shortcuts" msgid "Keyboard shortcuts"
msgstr "" msgstr ""
#: data/menu.ui:19 #: data/menu.ui:19 TwoFactorAuth/application.py:55
msgid "About" msgid "About"
msgstr "" msgstr ""
#: data/menu.ui:23 #: data/menu.ui:23 TwoFactorAuth/application.py:56
msgid "Quit" msgid "Quit"
msgstr "" msgstr ""
@ -84,131 +85,143 @@ msgid ""
"later</a> for details." "later</a> for details."
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:111 #: TwoFactorAuth/widgets/window.py:131
msgid "Do you really want to remove this application?" msgid "Do you really want to remove selected applications?"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:133 #: TwoFactorAuth/widgets/window.py:152
msgid "Enter your password" msgid "Enter your password"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:137 #: TwoFactorAuth/widgets/window.py:156
msgid "Login" msgid "Unlock"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:186 #: TwoFactorAuth/widgets/window.py:220
msgid "Remove selected two factor auth sources" msgid "Remove selected two factor auth sources"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:198 #: TwoFactorAuth/widgets/window.py:228
#: TwoFactorAuth/widgets/add_authenticator.py:33 #: TwoFactorAuth/widgets/add_authenticator.py:28
msgid "Add a new application" msgid "Add a new application"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:207 #: TwoFactorAuth/widgets/window.py:237
msgid "Select mode" msgid "Select mode"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:216 #: TwoFactorAuth/widgets/window.py:244
msgid "Search" msgid "Search"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:222 #: TwoFactorAuth/widgets/window.py:249
#: TwoFactorAuth/widgets/change_password.py:122 #: TwoFactorAuth/widgets/change_password.py:137
#: TwoFactorAuth/widgets/icon_finder.py:79 #: TwoFactorAuth/widgets/icon_finder.py:85
#: TwoFactorAuth/widgets/authenticator_logo.py:140 #: TwoFactorAuth/widgets/authenticator_logo.py:139
#: TwoFactorAuth/widgets/add_authenticator.py:180 #: TwoFactorAuth/widgets/add_authenticator.py:204
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:364 #: TwoFactorAuth/widgets/window.py:402
msgid "There's no application at the moment" msgid "There's no application at the moment"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/window.py:431 #: TwoFactorAuth/widgets/window.py:487
msgid "Do you really want to remove the application?" msgid "Do you really want to remove the application?"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/change_password.py:44 #: TwoFactorAuth/widgets/change_password.py:12
msgid "Old password : "
msgstr ""
#: TwoFactorAuth/widgets/change_password.py:55
msgid "New password : "
msgstr ""
#: TwoFactorAuth/widgets/change_password.py:65
msgid "Repeat new password : "
msgstr ""
#: TwoFactorAuth/widgets/change_password.py:127
msgid "Modifier" msgid "Modifier"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/icon_finder.py:84 #: TwoFactorAuth/widgets/change_password.py:54
msgid "Old password"
msgstr ""
#: TwoFactorAuth/widgets/change_password.py:64
msgid "New password"
msgstr ""
#: TwoFactorAuth/widgets/change_password.py:74
msgid "Repeat new password"
msgstr ""
#: TwoFactorAuth/widgets/icon_finder.py:11
msgid "Select" msgid "Select"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/authenticator_logo.py:145 #: TwoFactorAuth/widgets/authenticator_logo.py:144
msgid "Choose" msgid "Choose"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/settings.py:36 #: TwoFactorAuth/widgets/settings.py:39
msgid "Preferences" msgid "Preferences"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/settings.py:40 #: TwoFactorAuth/widgets/settings.py:43
msgid "Account" msgid "Account"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/settings.py:46 #: TwoFactorAuth/widgets/settings.py:53
msgid "Enable password: " msgid "Enable password: "
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/settings.py:55 #: TwoFactorAuth/widgets/settings.py:61
msgid "Password: " msgid "Password: "
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/settings.py:73 #: TwoFactorAuth/widgets/settings.py:82
msgid "Password generation time (s): " msgid "Password generation time (s): "
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/listrow.py:105 #: TwoFactorAuth/widgets/listrow.py:156
msgid "Copy the generated code.." msgid "Copy the generated code.."
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/listrow.py:115 #: TwoFactorAuth/widgets/listrow.py:165
msgid "Remove the source.." msgid "Remove the source.."
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/listrow.py:172 #: TwoFactorAuth/widgets/listrow.py:220
msgid "Couldn't generate the secret code" msgid "Couldn't generate the secret code"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:81 #: TwoFactorAuth/widgets/add_authenticator.py:96
msgid "Application name : " msgid "Application Name"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:89 #: TwoFactorAuth/widgets/add_authenticator.py:103
msgid "Two-factor secret : " msgid "Secret Code"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:113
msgid "Select from provided icons"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:118
msgid "Select a file"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:123 #: TwoFactorAuth/widgets/add_authenticator.py:123
msgid "Select from provided icons"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:128
msgid "Select a file"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:133
msgid "Select an icon name" msgid "Select an icon name"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:148 #: TwoFactorAuth/widgets/add_authenticator.py:165
msgid "Please choose a file" msgid "Please choose a file"
msgstr "" msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:185 #: TwoFactorAuth/widgets/add_authenticator.py:209
msgid "Add" msgid "Add"
msgstr "" msgstr ""
#: TwoFactorAuth/application.py:48 TwoFactorAuth/application.py:103
msgid "Unlock the Application"
msgstr ""
#: TwoFactorAuth/application.py:50 TwoFactorAuth/application.py:105
msgid "Lock the Application"
msgstr ""
#: TwoFactorAuth/application.py:54
msgid "Shortcuts"
msgstr ""