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
.Python
.idea/
env/
build/
develop-eggs/

View file

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

View file

@ -9,6 +9,7 @@ class Authenticator:
def __init__(self):
home = path.expanduser("~")
database_file = home + '/.config/TwoFactorAuth/database.db'
# Create missing folders
if not (path.isfile(database_file) and path.exists(database_file)):
dirs = database_file.split("/")
i = 0
@ -18,57 +19,81 @@ class Authenticator:
makedirs(directory)
logging.debug("Creating directory %s " % directory)
i += 1
# create database file
mknod(database_file)
logging.debug("Creating database file %s " % database_file)
# Connect to database
self.conn = sqlite3.connect(database_file)
if not self.is_table_exists():
logging.debug(
"Table 'applciations' does not exists, creating it now...")
logging.debug("SQL: Table 'applciations' does not exists, creating it now...")
self.create_table()
logging.debug("Table 'applications' created successfully")
logging.debug("SQL: Table 'applications' created successfully")
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,)
query = "INSERT INTO applications (name, secret_code, image) VALUES (?, ?, ?)"
try:
self.conn.execute(query, t)
self.conn.commit()
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):
query = "DELETE FROM applications WHERE id=?"
def remove_by_uid(self, uid):
"""
Remove an application by uid
:param uid: (int) application uid
:return:
"""
query = "DELETE FROM applications WHERE uid=?"
try:
self.conn.execute(query, (id,))
self.conn.execute(query, (uid,))
self.conn.commit()
except Exception as e:
logging.error(
"Couldn't remove application by id : %s with error : %s" % (id, str(e)))
logging.error("SQL: Couldn't remove application by uid : %s with error : %s" % (uid, str(e)))
def count(self):
"""
Count number of applications
:return: (int) count
"""
c = self.conn.cursor()
query = "SELECT COUNT(id) AS count FROM applications"
query = "SELECT COUNT(uid) AS count FROM applications"
try:
data = c.execute(query)
return data.fetchone()[0]
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
def fetch_apps(self):
"""
Fetch list of applications
:return: (tuple) list of applications
"""
c = self.conn.cursor()
query = "SELECT * FROM applications"
try:
data = c.execute(query)
return data.fetchall()
except Exception as e:
logging.error(query)
logging.error("Couldn't fetch applications list")
logging.error(str(e))
logging.error("SQL: Couldn't fetch applications list %s" % str(e))
return None
@staticmethod
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/"
theme = Gtk.IconTheme.get_default()
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)
else:
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,
GdkPixbuf.InterpType.BILINEAR)
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()
query = "SELECT id FROM applications ORDER BY id DESC LIMIT 1;"
query = "SELECT uid FROM applications ORDER BY uid DESC LIMIT 1;"
try:
data = c.execute(query)
return data.fetchone()[0]
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
def create_table(self):
"""
Create applications table
"""
query = '''CREATE TABLE "applications" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE ,
"uid" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE ,
"name" VARCHAR NOT NULL ,
"secret_code" VARCHAR NOT NULL UNIQUE ,
"image" TEXT NOT NULL
@ -105,11 +137,14 @@ class Authenticator:
self.conn.execute(query)
self.conn.commit()
except Exception as e:
logging.error(
"SQL : imossibile to create table 'applications' %s " % str(err))
logging.error( "SQL: imossibile to create table 'applications' %s " % str(e))
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()
try:
data = c.execute(query)

View file

@ -7,12 +7,19 @@ class SettingsReader:
def __init__(self):
try:
# Check if the gsettings path exists
self.source = Gio.SettingsSchemaSource.get_default()
self.source.lookup(self.path, True)
except Exception as e:
logging.critical("Couldn't load gsettings source %s " % str(e))
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)
value = gsettings.get_value(key)
value_type = value.get_type_string()
@ -25,6 +32,13 @@ class SettingsReader:
return value
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)
if type(value) is int:
gsettings.set_int(key, value)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,36 +1,39 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gio, Gdk, GObject
import logging
from gi.repository import Gtk
from TwoFactorAuth.models.settings import SettingsReader
from TwoFactorAuth.widgets.change_password import PasswordWindow
from gettext import gettext as _
from hashlib import md5
class SettingsWindow(Gtk.Window):
notebook = Gtk.Notebook()
time_spin_button = Gtk.SpinButton()
enable_switch = Gtk.Switch()
password_button = Gtk.Button()
def __init__(self, parent):
self.parent = parent
self.cfg = SettingsReader()
self.generate_window()
self.generate_compenents()
self.generate_components()
self.show_all()
def generate_window(self):
Gtk.Window.__init__(self, title=_("Settings"), 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, 300)
self.set_size_request(300, 300)
self.set_resizable(False)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_transient_for(self.parent)
def generate_compenents(self):
self.notebook = Gtk.Notebook()
def generate_components(self):
"""
Generate all the components
"""
self.add(self.notebook)
user_settings = self.generate_user_settings()
user_settings.set_border_width(10)
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')))
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_label = Gtk.Label(_("Enable password: "))
enable_switch = Gtk.Switch()
enable_switch.set_active(bool(self.cfg.read("state", "login")))
enable_switch.connect("notify::active", self.on_switch_activated)
self.enable_switch.set_active(bool(self.cfg.read("state", "login")))
self.enable_switch.connect("notify::active", self.on_switch_activated)
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_label = Gtk.Label(_("Password: "))
password_button = Gtk.Button()
password_button.get_style_context().add_class("flat")
password_button.get_style_context().add_class("text-button")
password_button.set_label("******")
password_button.connect("clicked", self.new_password_window)
self.password_button.get_style_context().add_class("flat")
self.password_button.get_style_context().add_class("text-button")
self.password_button.set_label("******")
self.password_button.connect("clicked", self.new_password_window)
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)
mainbox.pack_start(password_box, False, True, 6)
return mainbox
main_box.pack_start(enable_box, False, True, 6)
main_box.pack_start(password_box, False, True, 6)
return main_box
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_label = Gtk.Label(_("Password generation time (s): "))
@ -75,31 +84,39 @@ class SettingsWindow(Gtk.Window):
if default_value < 30 or default_value > 120:
default_value = 30
adjustment = Gtk.Adjustment(default_value, 10, 120, 1, 10, 0)
time_spinbutton = Gtk.SpinButton()
time_spinbutton.connect("value-changed", self.on_time_changed)
time_spinbutton.set_adjustment(adjustment)
time_spinbutton.set_value(default_value)
self.time_spin_button.connect("value-changed", self.on_time_changed)
self.time_spin_button.set_adjustment(adjustment)
self.time_spin_button.set_value(default_value)
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)
return mainbox
main_box.pack_start(time_box, False, True, 6)
return main_box
def new_password_window(self, *args):
"""
Show a new password window
"""
PasswordWindow(self)
def on_time_changed(self, spinbutton):
self.cfg.update("refresh-time", spinbutton.get_value_as_int(),
def on_time_changed(self, spin_button):
"""
Update time tog generate a new secret code
"""
self.cfg.update("refresh-time", spin_button.get_value_as_int(),
"preferences")
""" def on_password_changed(self, entry):
password = md5(entry.get_text().encode('utf-8')).hexdigest()
self.cfg.update("password", password, "login")
"""
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())
def on_switch_activated(self, switch, *args):
"""
Update password state : enabled/disabled
"""
self.password_button.set_sensitive(switch.get_active())
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
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.confirmation import ConfirmationMessage
from TwoFactorAuth.widgets.listrow import ListBoxRow
from threading import Thread
import logging
from hashlib import md5
from gettext import gettext as _
class Window(Gtk.ApplicationWindow):
app = None
selected_app_idx = None
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):
self.app = application
self.generate_window()
self.generate_headerbar()
self.generate_searchbar()
self.generate_header_bar()
self.generate_search_bar()
self.generate_applications_list()
if self.app.locked:
self.generate_login_form()
self.get_children()[0].get_children()[0].set_visible(False)
self.generate_no_apps_box()
self.generate_login_form()
self.refresh_window()
def generate_window(self, *args):
"""
Generate application window (Gtk.Window)
"""
Gtk.ApplicationWindow.__init__(self, Gtk.WindowType.TOPLEVEL,
application=self.app)
self.set_position(Gtk.WindowPosition.CENTER)
@ -35,319 +54,332 @@ class Window(Gtk.ApplicationWindow):
self.set_resizable(False)
self.connect("key_press_event", self.on_key_press)
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):
keypressed = Gdk.keyval_name(keyevent.keyval).lower()
def on_key_press(self, app, key_event):
"""
Keyboard Listener handling
"""
keypress = Gdk.keyval_name(key_event.keyval).lower()
if not self.app.locked:
CONTROL_MASK = Gdk.ModifierType.CONTROL_MASK
search_box = self.get_children()[0].get_children()[
0].get_children()[0]
control_mask = Gdk.ModifierType.CONTROL_MASK
count = self.app.auth.count()
if keypressed == "c":
if keyevent.state == CONTROL_MASK:
if keypress == "c":
if key_event.state == control_mask:
self.copy_code()
elif keypressed == "f":
if keyevent.state == CONTROL_MASK:
self.toggle_searchobox()
elif keypressed == "s":
if keyevent.state == CONTROL_MASK:
elif keypress == "f":
if key_event.state == control_mask:
self.toggle_search_box()
elif keypress == "s":
if key_event.state == control_mask:
self.toggle_select()
elif keypressed == "n":
if keyevent.state == CONTROL_MASK:
elif keypress == "n":
if key_event.state == control_mask:
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()
elif keypressed == "return":
elif keypress == "return":
if count > 0:
if self.listbox.get_selected_row():
index = self.listbox.get_selected_row().get_index()
if self.list_box.get_selected_row():
index = self.list_box.get_selected_row().get_index()
else:
index = 0
listrow_box = self.listbox.get_row_at_index(index)
listbox = listrow_box.get_children()[0]
code_box = listbox.get_children()[1]
is_visible = code_box.get_no_show_all()
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)
ListBoxRow.toggle_code_box(self.list_box.get_row_at_index(index))
elif keypress == "backspace":
if len(self.search_entry.get_text()) == 0:
self.hide_searchbar()
else:
if keypressed == "return":
self.on_login_clicked()
if keypress == "return":
self.on_unlock_clicked()
def filter_applications(self, entry):
data = entry.get_text()
if len(data) != 0:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"edit-clear-symbolic")
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic")
else:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
None)
self.listbox.set_filter_func(self.filter_func, data, False)
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, None)
self.list_box.set_filter_func(self.filter_func, data, False)
def generate_searchbar(self):
hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
search_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
search_entry = Gtk.Entry()
search_box.set_margin_left(60)
search_entry.set_width_chars(21)
search_entry.connect("changed", self.filter_applications)
search_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY,
"system-search-symbolic")
def hide_search_bar(self):
"""
Hide search bar
"""
self.search_box.set_visible(False)
self.list_box.set_filter_func(lambda x, y, z: True, None, False)
search_box.pack_start(search_entry, False, True, 0)
hbox.pack_start(search_box, False, True, 6)
self.get_children()[0].pack_start(hbox, True, True, 0)
search_box.set_no_show_all(True)
def generate_search_bar(self):
"""
Generate search bar box and entry
"""
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):
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.show()
if confirmation.get_confirmation():
for row in self.listbox.get_children():
checkbox = self.get_checkbox_from_row(row)
for row in self.list_box.get_children():
checkbox = ListBoxRow.get_checkbox(row)
if checkbox.get_active():
label_id = row.get_children()[0].get_children()[2]
label_id = int(label_id.get_text())
label_id = ListBoxRow.get_id(row)
self.app.auth.remove_by_id(label_id)
self.listbox.remove(row)
self.listbox.unselect_all()
self.list_box.remove(row)
self.list_box.unselect_all()
confirmation.destroy()
self.refresh_window()
def generate_login_form(self):
mainbox = self.get_children()[0]
login_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
"""
Generate login form
"""
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()
login_button.set_label(_("Login"))
login_button.connect("clicked", self.on_login_clicked)
password_box.pack_start(login_button, False, False, 6)
login_box.pack_start(password_box, True, False, 6)
self.password_entry.set_visibility(False)
self.password_entry.set_placeholder_text(_("Enter your password"))
password_box.pack_start(self.password_entry, False, False, 6)
mainbox.pack_start(login_box, True, False, 0)
mainbox.get_children()[0].set_no_show_all(self.app.locked)
mainbox.get_children()[1].set_no_show_all(self.app.locked)
mainbox.get_children()[2].set_no_show_all(not self.app.locked)
self.hide_headerbar()
unlock_button = Gtk.Button()
unlock_button.set_label(_("Unlock"))
unlock_button.connect("clicked", self.on_unlock_clicked)
def on_login_clicked(self, *args):
login_box = self.get_children()[0].get_children()[2]
entry = login_box.get_children()[0].get_children()[0]
password = md5(entry.get_text().encode("utf-8")).hexdigest()
password_box.pack_start(unlock_button, False, False, 6)
self.login_box.pack_start(password_box, True, False, 6)
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"):
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.app.locked = False
self.app.toggle_settings_menu()
self.password_entry.set_text("")
self.app.toggle_app_lock_menu()
else:
entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
"dialog-error-symbolic")
self.password_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
def toggle_app_lock(self):
"""
Lock/unlock the application
"""
self.app.locked = not self.app.locked
mainbox = self.get_children()[0]
mainbox.get_children()[2].set_visible(self.app.locked)
mainbox.get_children()[2].set_no_show_all(not self.app.locked)
self.app.toggle_app_lock_menu()
self.refresh_window()
def hide_headerbar(self):
hb = self.get_children()[1]
hb.get_children()[0].get_children()[0].set_no_show_all(True)
hb.get_children()[0].get_children()[1].set_no_show_all(True)
hb.get_children()[1].get_children()[0].set_no_show_all(True)
hb.get_children()[1].get_children()[1].set_no_show_all(True)
hb.get_children()[1].get_children()[2].set_no_show_all(True)
def toggle_boxes(self, apps_box, no_apps_box, login_box):
"""
Change the status of all the boxes in one time
:param apps_box: bool
:param no_apps_box: bool
:param login_box: bool
: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):
hb = Gtk.HeaderBar()
hb.set_show_close_button(True)
def hide_header_bar(self):
"""
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)
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_image = Gtk.Image.new_from_gicon(remove_icon,
Gtk.IconSize.BUTTON)
self.remove_button.set_tooltip_text(_("Remove selected two factor auth "
"sources"))
self.remove_button.set_sensitive(False)
remove_image = Gtk.Image.new_from_gicon(remove_icon, Gtk.IconSize.BUTTON)
self.remove_button.set_tooltip_text(_("Remove selected two factor auth sources"))
self.remove_button.set_image(remove_image)
self.remove_button.set_sensitive(False)
self.remove_button.set_no_show_all(True)
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_image = Gtk.Image.new_from_gicon(add_icon,
Gtk.IconSize.BUTTON)
add_button.set_tooltip_text(_("Add a new application"))
add_button.set_image(add_image)
add_button.connect("clicked", self.add_application)
left_box.add(add_button)
add_image = Gtk.Image.new_from_gicon(add_icon, Gtk.IconSize.BUTTON)
self.add_button.set_tooltip_text(_("Add a new application"))
self.add_button.set_image(add_image)
self.add_button.connect("clicked", self.add_application)
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_image = Gtk.Image.new_from_gicon(select_icon,
Gtk.IconSize.BUTTON)
select_button.set_tooltip_text(_("Select mode"))
select_button.set_image(select_image)
select_button.connect("clicked", self.toggle_select)
select_button.set_no_show_all(not self.app.auth.count() > 0)
select_image = Gtk.Image.new_from_gicon(select_icon, Gtk.IconSize.BUTTON)
self.select_button.set_tooltip_text(_("Select mode"))
self.select_button.set_image(select_image)
self.select_button.connect("clicked", self.toggle_select)
self.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_image = Gtk.Image.new_from_gicon(search_icon,
Gtk.IconSize.BUTTON)
search_button.set_tooltip_text(_("Search"))
search_button.set_image(search_image)
search_button.connect("clicked", self.toggle_searchobox)
search_button.set_no_show_all(not self.app.auth.count() > 0)
search_image = Gtk.Image.new_from_gicon(search_icon, Gtk.IconSize.BUTTON)
self.search_button.set_tooltip_text(_("Search"))
self.search_button.set_image(search_image)
self.search_button.connect("clicked", self.toggle_search_box)
self.search_button.set_no_show_all(not self.app.auth.count() > 0)
cancel_buton = Gtk.Button()
cancel_buton.set_label(_("Cancel"))
cancel_buton.connect("clicked", self.toggle_select)
cancel_buton.set_no_show_all(True)
self.cancel_button.set_label(_("Cancel"))
self.cancel_button.connect("clicked", self.toggle_select)
self.cancel_button.set_no_show_all(True)
right_box.add(search_button)
right_box.add(select_button)
right_box.add(cancel_buton)
right_box.add(self.search_button)
right_box.add(self.select_button)
right_box.add(self.cancel_button)
hb.pack_start(left_box)
hb.pack_end(right_box)
self.set_titlebar(hb)
self.hb.pack_start(left_box)
self.hb.pack_end(right_box)
self.set_titlebar(self.hb)
def add_application(self, *args):
"""
Create add application window
"""
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:
search_box = self.get_children()[0].get_children()[
0].get_children()[0]
is_visible = search_box.get_no_show_all()
is_visible = self.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:
search_button.get_style_context().add_class("toggle")
search_box.get_children()[0].grab_focus_without_selecting()
self.search_button.get_style_context().add_class("toggle")
self.search_entry.grab_focus_without_selecting()
else:
search_button.get_style_context().remove_class("toggle")
self.listbox.set_filter_func(lambda x, y, z: True, None, False)
self.search_button.get_style_context().remove_class("toggle")
self.list_box.set_filter_func(lambda x, y, z: True, None, False)
def toggle_select(self, *args):
i = 0
button_visible = self.remove_button.get_visible()
headerbar = self.get_children()[1]
headerbar.set_show_close_button(button_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)
"""
Toggle select mode
"""
is_visible = self.remove_button.get_visible()
if not button_visible:
self.listbox.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
headerbar.get_style_context().add_class("selection-mode")
self.remove_button.set_visible(not is_visible)
self.remove_button.set_no_show_all(is_visible)
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:
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
headerbar.get_style_context().remove_class("selection-mode")
self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.hb.get_style_context().remove_class("selection-mode")
if self.selected_app_idx:
index = self.selected_app_idx
else:
index = 0
listrow_box = self.listbox.get_row_at_index(index)
self.listbox.select_row(listrow_box)
list_row_box = self.list_box.get_row_at_index(index)
self.list_box.select_row(list_row_box)
for row in self.listbox.get_children():
checkbox = self.get_checkbox_from_row(row)
for row in self.list_box.get_children():
checkbox = ListBoxRow.get_checkbox(row)
visible = checkbox.get_visible()
selected = checkbox.get_active()
if not button_visible:
if not is_visible:
self.select_application(checkbox)
checkbox.set_visible(not visible)
checkbox.set_no_show_all(visible)
def select_application(self, checkbutton):
"""
Select an application in the application ListBox
:param checkbutton:
"""
is_active = checkbutton.get_active()
is_visible = checkbutton.get_visible()
listbox_row = checkbutton.get_parent().get_parent().get_parent()
if is_active:
self.listbox.select_row(listbox_row)
self.list_box.select_row(listbox_row)
if is_visible:
self.selected_count += 1
else:
self.listbox.unselect_row(listbox_row)
self.list_box.unselect_row(listbox_row)
if is_visible:
self.selected_count -= 1
self.remove_button.set_sensitive(self.selected_count > 0)
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()
if len(data) > 0:
return data in app_label[2].get_children()[0].get_text().lower()
return data in app_label
else:
return True
def get_checkbox_from_row(self, row):
if row:
return row.get_children()[0].get_children()[0].get_children()[0]
else:
return None
def select_row(self, listbox, listbox_row):
def select_row(self, list_box, listbox_row):
"""
Select row @override the clicked event by default for ListBoxRow
"""
index = listbox_row.get_index()
button_visible = self.remove_button.get_visible()
checkbox = self.get_checkbox_from_row(listbox_row)
checkbox = ListBoxRow.get_checkbox(listbox_row)
if button_visible:
checkbox.set_active(not checkbox.get_active())
else:
if self.selected_app_idx:
listrow_box = self.listbox.get_row_at_index(
self.selected_app_idx)
self.listbox.unselect_row(listbox_row)
self.list_box.unselect_row(self.list_box.get_row_at_index(self.selected_app_idx))
self.selected_app_idx = index
listrow_box = self.listbox.get_row_at_index(index)
self.listbox.select_row(listbox_row)
self.list_box.select_row(self.list_box.get_row_at_index(index))
# TODO : show a nice message when no application is added
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()
# Create a ScrolledWindow for installed applications
self.listbox = Gtk.ListBox()
self.listbox.get_style_context().add_class("applications-list")
self.listbox.set_adjustment()
self.listbox.connect("row_activated", self.select_row)
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
list_box.pack_start(self.listbox, True, True, 0)
self.list_box.get_style_context().add_class("applications-list")
self.list_box.set_adjustment()
self.list_box.connect("row_activated", self.select_row)
self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
self.apps_list_box.pack_start(self.list_box, True, True, 0)
scrolled_win = Gtk.ScrolledWindow()
scrolled_win.add_with_viewport(list_box)
self.get_children()[0].get_children()[0].pack_start(
scrolled_win, True, True, 0)
scrolled_win.add_with_viewport(self.apps_list_box)
self.apps_box.pack_start(scrolled_win, True, True, 0)
apps = self.app.auth.fetch_apps()
i = 0
@ -355,39 +387,46 @@ class Window(Gtk.ApplicationWindow):
while i < count:
row = ListBoxRow(self, apps[i][0], apps[i][1], apps[i][2],
apps[i][3])
self.listbox.add(row.get_listrow())
self.list_box.add(row.get_list_row())
i += 1
nolist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
vbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
def generate_no_apps_box(self):
"""
Generate a box with no applications message
"""
logo_image = Gtk.Image()
logo_image.set_from_icon_name("dialog-information-symbolic",
Gtk.IconSize.DIALOG)
vbox.pack_start(logo_image, False, False, 6)
no_proivders_label = Gtk.Label()
no_proivders_label.set_text(_("There's no application at the moment"))
vbox.pack_start(no_proivders_label, False, False, 6)
no_apps_label = Gtk.Label()
no_apps_label.set_text(_("There's no application at the moment"))
nolist_box.pack_start(vbox, True, True, 0)
self.get_children()[0].pack_start(nolist_box, True, True, 0)
self.get_children()[0].get_children()[0].set_no_show_all(count == 0)
self.get_children()[0].get_children()[
1].set_no_show_all(not count == 0)
self.no_apps_box.pack_start(logo_image, False, False, 6)
self.no_apps_box.pack_start(no_apps_label, False, False, 6)
self.main_box.pack_start(self.no_apps_box, True, False, 0)
def update_list(self, id, name, secret_code, image):
row = ListBoxRow(self, id, name, secret_code, image)
self.listbox.add(row.get_listrow())
self.listbox.show_all()
def append(self, uid, name, secret_code, image):
"""
Add an element to the ListBox
: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):
"""
Copy the secret code to clipboard
"""
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()
self.listbox.select_row(row)
selected_row = self.listbox.get_selected_row()
label = selected_row.get_children()[0].get_children()[1].get_children()
code = label[0].get_text()
self.list_box.select_row(row)
selected_row = self.list_box.get_selected_row()
code = ListBoxRow.get_label(selected_row)
try:
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.clear()
@ -397,56 +436,70 @@ class Window(Gtk.ApplicationWindow):
logging.error(str(e))
def refresh_window(self):
mainbox = self.get_children()[0]
"""
Refresh windows components
"""
count = self.app.auth.count()
headerbar = self.get_children()[1]
if count == 0:
mainbox.get_children()[0].set_visible(False)
mainbox.get_children()[1].set_visible(True)
mainbox.get_children()[1].set_no_show_all(False)
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")
is_locked = self.app.locked
if is_locked:
self.toggle_boxes(False, False, True)
self.hide_header_bar()
else:
self.get_children()[0].get_children()[0].set_no_show_all(False)
self.get_children()[0].get_children()[0].set_visible(True)
self.get_children()[0].get_children()[0].show_all()
headerbar.get_children()[0].get_children()[0].set_visible(False)
headerbar.get_children()[1].get_children()[0].set_visible(True)
headerbar.get_children()[1].get_children()[1].set_visible(True)
mainbox.get_children()[1].set_visible(False)
if count == 0:
self.toggle_boxes(False, True, False)
self.toggle_hb_buttons(False, True, False, False, False)
else:
self.toggle_boxes(True, False, False)
self.toggle_hb_buttons(False, True, True, True, False)
headerbar.get_children()[0].get_children()[1].set_no_show_all(False)
headerbar.get_children()[0].get_children()[1].set_visible(True)
self.main_box.show_all()
self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
headerbar = self.get_children()[1]
left_box = headerbar.get_children()[0]
right_box = headerbar.get_children()[1]
right_box.get_children()[0].set_visible(count > 0)
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
left_box.get_children()[0].set_visible(False)
def toggle_hb_buttons(self, remove, add, search, select, cancel):
"""
Toggle header bar buttons visibilty
:param remove: (bool)
:param add: (bool)
: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):
"""
Remove an application
"""
if len(args) > 0:
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?")
confirmation = ConfirmationMessage(self, message)
confirmation.show()
if confirmation.get_confirmation():
if self.listbox.get_selected_row():
selected_row = self.listbox.get_selected_row()
self.listbox.remove(selected_row)
label_id = selected_row.get_children()[0].get_children()[2]
self.app.auth.remove_by_id(int(label_id.get_text()))
if self.list_box.get_selected_row():
selected_row = self.list_box.get_selected_row()
self.list_box.remove(selected_row)
app_id = ListBoxRow.get_id(selected_row)
self.app.auth.remove_by_id(app_id)
confirmation.destroy()
self.refresh_window()
def show_about(self, *args):
"""
Shows about dialog
"""
builder = Gtk.Builder()
builder.add_from_file(self.app.pkgdatadir + "/data/about.ui")
@ -456,9 +509,13 @@ class Window(Gtk.ApplicationWindow):
dialog.destroy()
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.set_transient_for(self)
shortcuts.show()
shortcuts = builder.get_object("shortcuts")
shortcuts.set_transient_for(self)
shortcuts.show()

View file

@ -6,7 +6,6 @@ desktop_DATA = twofactorauth.desktop
uidir = $(pkgdatadir)/data
ui_DATA = about.ui \
shortcuts.ui \
menu.ui \
style.css
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 ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,7 +17,8 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\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"
msgstr ""
@ -25,11 +26,11 @@ msgstr ""
msgid "Keyboard shortcuts"
msgstr ""
#: data/menu.ui:19
#: data/menu.ui:19 TwoFactorAuth/application.py:55
msgid "About"
msgstr ""
#: data/menu.ui:23
#: data/menu.ui:23 TwoFactorAuth/application.py:56
msgid "Quit"
msgstr ""
@ -84,131 +85,143 @@ msgid ""
"later</a> for details."
msgstr ""
#: TwoFactorAuth/widgets/window.py:111
msgid "Do you really want to remove this application?"
#: TwoFactorAuth/widgets/window.py:131
msgid "Do you really want to remove selected applications?"
msgstr ""
#: TwoFactorAuth/widgets/window.py:133
#: TwoFactorAuth/widgets/window.py:152
msgid "Enter your password"
msgstr ""
#: TwoFactorAuth/widgets/window.py:137
msgid "Login"
#: TwoFactorAuth/widgets/window.py:156
msgid "Unlock"
msgstr ""
#: TwoFactorAuth/widgets/window.py:186
#: TwoFactorAuth/widgets/window.py:220
msgid "Remove selected two factor auth sources"
msgstr ""
#: TwoFactorAuth/widgets/window.py:198
#: TwoFactorAuth/widgets/add_authenticator.py:33
#: TwoFactorAuth/widgets/window.py:228
#: TwoFactorAuth/widgets/add_authenticator.py:28
msgid "Add a new application"
msgstr ""
#: TwoFactorAuth/widgets/window.py:207
#: TwoFactorAuth/widgets/window.py:237
msgid "Select mode"
msgstr ""
#: TwoFactorAuth/widgets/window.py:216
#: TwoFactorAuth/widgets/window.py:244
msgid "Search"
msgstr ""
#: TwoFactorAuth/widgets/window.py:222
#: TwoFactorAuth/widgets/change_password.py:122
#: TwoFactorAuth/widgets/icon_finder.py:79
#: TwoFactorAuth/widgets/authenticator_logo.py:140
#: TwoFactorAuth/widgets/add_authenticator.py:180
#: TwoFactorAuth/widgets/window.py:249
#: TwoFactorAuth/widgets/change_password.py:137
#: TwoFactorAuth/widgets/icon_finder.py:85
#: TwoFactorAuth/widgets/authenticator_logo.py:139
#: TwoFactorAuth/widgets/add_authenticator.py:204
msgid "Cancel"
msgstr ""
#: TwoFactorAuth/widgets/window.py:364
#: TwoFactorAuth/widgets/window.py:402
msgid "There's no application at the moment"
msgstr ""
#: TwoFactorAuth/widgets/window.py:431
#: TwoFactorAuth/widgets/window.py:487
msgid "Do you really want to remove the application?"
msgstr ""
#: TwoFactorAuth/widgets/change_password.py:44
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
#: TwoFactorAuth/widgets/change_password.py:12
msgid "Modifier"
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"
msgstr ""
#: TwoFactorAuth/widgets/authenticator_logo.py:145
#: TwoFactorAuth/widgets/authenticator_logo.py:144
msgid "Choose"
msgstr ""
#: TwoFactorAuth/widgets/settings.py:36
#: TwoFactorAuth/widgets/settings.py:39
msgid "Preferences"
msgstr ""
#: TwoFactorAuth/widgets/settings.py:40
#: TwoFactorAuth/widgets/settings.py:43
msgid "Account"
msgstr ""
#: TwoFactorAuth/widgets/settings.py:46
#: TwoFactorAuth/widgets/settings.py:53
msgid "Enable password: "
msgstr ""
#: TwoFactorAuth/widgets/settings.py:55
#: TwoFactorAuth/widgets/settings.py:61
msgid "Password: "
msgstr ""
#: TwoFactorAuth/widgets/settings.py:73
#: TwoFactorAuth/widgets/settings.py:82
msgid "Password generation time (s): "
msgstr ""
#: TwoFactorAuth/widgets/listrow.py:105
#: TwoFactorAuth/widgets/listrow.py:156
msgid "Copy the generated code.."
msgstr ""
#: TwoFactorAuth/widgets/listrow.py:115
#: TwoFactorAuth/widgets/listrow.py:165
msgid "Remove the source.."
msgstr ""
#: TwoFactorAuth/widgets/listrow.py:172
#: TwoFactorAuth/widgets/listrow.py:220
msgid "Couldn't generate the secret code"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:81
msgid "Application name : "
#: TwoFactorAuth/widgets/add_authenticator.py:96
msgid "Application Name"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:89
msgid "Two-factor secret : "
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:113
msgid "Select from provided icons"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:118
msgid "Select a file"
#: TwoFactorAuth/widgets/add_authenticator.py:103
msgid "Secret Code"
msgstr ""
#: 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"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:148
#: TwoFactorAuth/widgets/add_authenticator.py:165
msgid "Please choose a file"
msgstr ""
#: TwoFactorAuth/widgets/add_authenticator.py:185
#: TwoFactorAuth/widgets/add_authenticator.py:209
msgid "Add"
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 ""