Merge pull request #11 from bil-elmoussaoui/beta

Beta
This commit is contained in:
Bilal Elmoussaoui 2016-06-15 02:12:58 +02:00 committed by GitHub
commit 759e1c5b2c
15 changed files with 513 additions and 390 deletions

View file

@ -1,5 +1,6 @@
app_PYTHON = \
application.py \
utils.py \
__init__.py
appdir = $(pythondir)/TwoFactorAuth

View file

@ -4,9 +4,10 @@ require_version("Gtk", "3.0")
require_version("GnomeKeyring", "1.0")
from gi.repository import Gtk, GLib, Gio, Gdk, GObject, GnomeKeyring as GK
from TwoFactorAuth.widgets.window import Window
from TwoFactorAuth.models.authenticator import Authenticator
from TwoFactorAuth.models.database import Database
from TwoFactorAuth.widgets.settings import SettingsWindow
from TwoFactorAuth.models.settings import SettingsReader
from TwoFactorAuth.utils import *
import logging
import signal
from gettext import gettext as _
@ -16,12 +17,10 @@ class Application(Gtk.Application):
win = None
alive = True
locked = False
menu = Gio.Menu()
auth = Authenticator()
use_GMenu = True
settings_window = None
settings_action = None
def __init__(self):
Gtk.Application.__init__(self,
application_id="org.gnome.TwoFactorAuth",
@ -29,19 +28,15 @@ class Application(Gtk.Application):
GLib.set_application_name(_("TwoFactorAuth"))
GLib.set_prgname("Gnome-TwoFactorAuth")
current_desktop = env.get("XDG_CURRENT_DESKTOP")
if current_desktop:
self.use_GMenu = current_desktop.lower() == "gnome"
else:
self.use_GMenu = False
self.menu = Gio.Menu()
self.db = Database()
self.cfg = SettingsReader()
self.locked = self.cfg.read("state", "login")
result = GK.unlock_sync("Gnome-TwoFactorAuth", None)
if result == GK.Result.CANCELLED:
self.quit()
self.cfg = SettingsReader()
if self.cfg.read("state", "login"):
self.locked = True
cssProviderFile = Gio.File.new_for_uri('resource:///org/gnome/TwoFactorAuth/style.css')
cssProvider = Gtk.CssProvider()
screen = Gdk.Screen.get_default()
@ -95,26 +90,22 @@ class Application(Gtk.Application):
action.connect("activate", self.on_quit)
self.add_action(action)
if self.use_GMenu:
if is_gnome():
self.set_app_menu(self.menu)
logging.debug("Adding gnome shell menu")
def do_activate(self, *args):
self.win = Window(self)
self.win.show_all()
self.add_window(self.win)
if not self.win:
self.win = Window(self)
self.win.show_all()
self.add_window(self.win)
else:
self.win.present()
def refresh_menu(self):
if self.use_GMenu:
self.settings_action.set_enabled(
not self.settings_action.get_enabled())
def on_toggle_lock(self, *args):
if not self.locked:
self.locked = not self.locked
self.win.password_entry.grab_focus_without_selecting()
self.refresh_menu()
self.win.refresh_window()
if is_gnome():
is_enabled = self.settings_action.get_enabled()
self.settings_action.set_enabled(not is_enabled)
def on_shortcuts(self, *args):
"""
@ -143,8 +134,11 @@ class Application(Gtk.Application):
"""
Shows settings window
"""
settings_window = SettingsWindow(self.win)
settings_window.show_window()
if not self.settings_window:
self.settings_window = SettingsWindow(self.win)
self.settings_window.show_window()
else:
self.settings_window.present()
def on_quit(self, *args):
"""

View file

@ -2,5 +2,5 @@ appdir = $(pythondir)/TwoFactorAuth/models
app_PYTHON = \
code.py \
authenticator.py \
database.py \
settings.py

View file

@ -1,29 +1,15 @@
import sqlite3
import logging
from os import path, mknod, makedirs, environ as env
from gi.repository import GdkPixbuf, Gtk
from gi.repository import GnomeKeyring as GK
from hashlib import sha256
from TwoFactorAuth.utils import create_file, get_home_path
class Authenticator:
class Database:
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
while i < len(dirs) - 1:
directory = "/".join(dirs[0:i + 1]).strip()
if not path.exists(directory) and len(directory) != 0:
makedirs(directory)
logging.debug("Creating directory %s " % directory)
i += 1
# create database file
mknod(database_file)
database_file = get_home_path() + '/.config/TwoFactorAuth/database.db'
if create_file(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(
@ -134,28 +120,6 @@ class Authenticator:
logging.error("SQL: Couldn't fetch accounts list %s" % str(e))
return None
@staticmethod
def get_auth_icon(image):
"""
Generate a GdkPixbuf image
:param image: icon name or image path
:return: GdkPixbux Image
"""
directory = path.join(env.get("DATA_DIR"), "applications") + "/"
theme = Gtk.IconTheme.get_default()
if path.isfile(directory + image) and path.exists(directory + image):
icon = GdkPixbuf.Pixbuf.new_from_file(directory + image)
elif path.isfile(image) and path.exists(image):
icon = GdkPixbuf.Pixbuf.new_from_file(image)
elif theme.has_icon(path.splitext(image)[0]):
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:
icon = icon.scale_simple(48, 48,
GdkPixbuf.InterpType.BILINEAR)
return icon
def get_latest_id(self):
"""
Get the latest uid on accounts table

46
TwoFactorAuth/utils.py Normal file
View file

@ -0,0 +1,46 @@
from os import path, mknod, makedirs, environ as env
from gi.repository import GdkPixbuf, Gtk
import logging
def is_gnome():
return env.get("XDG_CURRENT_DESKTOP").lower() == "gnome"
def get_home_path():
return path.expanduser("~")
def get_icon(image):
"""
Generate a GdkPixbuf image
:param image: icon name or image path
:return: GdkPixbux Image
"""
directory = path.join(env.get("DATA_DIR"), "applications") + "/"
theme = Gtk.IconTheme.get_default()
if path.isfile(directory + image) and path.exists(directory + image):
icon = GdkPixbuf.Pixbuf.new_from_file(directory + image)
elif path.isfile(image) and path.exists(image):
icon = GdkPixbuf.Pixbuf.new_from_file(image)
elif theme.has_icon(path.splitext(image)[0]):
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:
icon = icon.scale_simple(48, 48, GdkPixbuf.InterpType.BILINEAR)
return icon
def create_file(file_path):
if not (path.isfile(file_path) and path.exists(file_path)):
dirs = file_path.split("/")
i = 0
while i < len(dirs) - 1:
directory = "/".join(dirs[0:i + 1]).strip()
if not path.exists(directory) and len(directory) != 0:
makedirs(directory)
logging.debug("Creating directory %s " % directory)
i += 1
mknod(file_path)
return True
else:
return False

View file

@ -4,9 +4,12 @@ app_PYTHON = \
add_account.py \
confirmation.py \
account_row.py \
login_window.py \
headerbar.py \
applications_list.py \
application_row.py \
change_password.py \
no_account_window.py \
search_bar.py \
settings.py \
window.py

View file

@ -1,9 +1,10 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, GObject, GLib
from gi.repository import Gtk, Gdk, GLib
from TwoFactorAuth.models.code import Code
from TwoFactorAuth.models.authenticator import Authenticator
from TwoFactorAuth.models.settings import SettingsReader
from TwoFactorAuth.models.database import Database
from TwoFactorAuth.utils import get_icon
from threading import Thread
from time import sleep
import logging
@ -28,7 +29,7 @@ class AccountRow(Thread, Gtk.ListBoxRow):
self.parent = parent
self.id = uid
self.name = name
self.secret_code = Authenticator.fetch_secret_code(secret_code)
self.secret_code = Database.fetch_secret_code(secret_code)
if self.secret_code:
self.code = Code(self.secret_code)
else:
@ -94,16 +95,23 @@ class AccountRow(Thread, Gtk.ListBoxRow):
"""
self.alive = False
def copy_code(self, event_box, box):
def copy_code(self, *args):
"""
Copy code shows the code box for a while (10s by default)
"""
self.timer = 0
self.parent.copy_code(event_box)
code = self.get_code().get_secret_code()
try:
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.clear()
clipboard.set_text(code, len(code))
logging.debug("Secret code copied to clipboard")
except Exception as e:
logging.error(str(e))
self.code_box.set_visible(True)
self.code_box.set_no_show_all(False)
self.code_box.show_all()
GObject.timeout_add_seconds(1, self.update_timer)
GLib.timeout_add_seconds(1, self.update_timer)
def update_timer(self, *args):
"""
@ -133,7 +141,7 @@ class AccountRow(Thread, Gtk.ListBoxRow):
h_box.pack_start(self.checkbox, False, True, 0)
# account logo
auth_icon = Authenticator.get_auth_icon(self.logo)
auth_icon = get_icon(self.logo)
auth_logo = Gtk.Image(xalign=0)
auth_logo.set_from_pixbuf(auth_icon)
h_box.pack_start(auth_logo, False, True, 6)

View file

@ -4,7 +4,7 @@ from gi.repository import Gtk, Gdk
import logging
from TwoFactorAuth.widgets.applications_list import ApplicationChooserWindow
from TwoFactorAuth.models.code import Code
from TwoFactorAuth.models.authenticator import Authenticator
from TwoFactorAuth.utils import get_icon
from gettext import gettext as _
@ -85,7 +85,7 @@ class AddAccount(Gtk.Window):
hbox_two_factor.pack_end(self.secret_code, False, True, 0)
hbox_two_factor.pack_end(two_factor_label, False, True, 0)
auth_icon = Authenticator.get_auth_icon("image-missing")
auth_icon = get_icon("image-missing")
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, True, False, 6)
@ -110,7 +110,7 @@ class AddAccount(Gtk.Window):
Update image logo
"""
self.selected_image = image
auth_icon = Authenticator.get_auth_icon(image)
auth_icon = get_icon(image)
self.logo_image.clear()
self.logo_image.set_from_pixbuf(auth_icon)
@ -136,9 +136,9 @@ class AddAccount(Gtk.Window):
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_account(name_entry, secret_entry,
self.parent.app.db.add_account(name_entry, secret_entry,
image_entry)
uid = self.parent.app.auth.get_latest_id()
uid = self.parent.app.db.get_latest_id()
self.parent.append_list_box(
uid, name_entry, secret_entry, image_entry)
self.parent.refresh_window()

View file

@ -1,10 +1,10 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, GLib, Gio, Gdk
from TwoFactorAuth.models.authenticator import Authenticator
from TwoFactorAuth.widgets.search_bar import SearchBar
from TwoFactorAuth.widgets.application_row import ApplicationRow
from os import path, listdir, environ as env
from TwoFactorAuth.utils import get_icon
from gettext import gettext as _
@ -63,7 +63,7 @@ class ApplicationChooserWindow(Gtk.Window):
img_path = self.logos[i]
app_name = path.splitext(img_path)[0].strip(".").title()
# Application logo
app_logo = Authenticator.get_auth_icon(img_path)
app_logo = get_icon(img_path)
self.listbox.add(ApplicationRow(app_name, app_logo))
i += 1

View file

@ -0,0 +1,197 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio
import logging
from gettext import gettext as _
from TwoFactorAuth.utils import *
class HeaderBar(Gtk.HeaderBar):
search_button = Gtk.ToggleButton()
add_button = Gtk.Button()
settings_button = Gtk.Button()
remove_button = Gtk.Button()
cancel_button = Gtk.Button()
select_button = Gtk.Button()
lock_button = Gtk.Button()
popover = None
def __init__(self, app):
self.app = app
Gtk.HeaderBar.__init__(self)
self.generate()
def generate(self):
self.set_show_close_button(True)
right_box = self.generate_right_box()
left_box = self.generate_left_box()
if not is_gnome():
# add settings menu
self.generate_popover(right_box)
self.pack_start(left_box)
self.pack_end(right_box)
def generate_left_box(self):
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
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 accounts"))
self.remove_button.set_image(remove_image)
self.remove_button.set_sensitive(False)
self.toggle_remove_button(False)
add_icon = Gio.ThemedIcon(name="list-add-symbolic")
add_image = Gtk.Image.new_from_gicon(add_icon, Gtk.IconSize.BUTTON)
self.add_button.set_tooltip_text(_("Add a new account"))
self.add_button.set_image(add_image)
pass_enabled = self.app.cfg.read("state", "login")
can_be_locked = not self.app.locked and pass_enabled
lock_icon = Gio.ThemedIcon(name="changes-prevent-symbolic")
lock_image = Gtk.Image.new_from_gicon(lock_icon, Gtk.IconSize.BUTTON)
self.lock_button.set_tooltip_text(_("Lock the Application"))
self.lock_button.set_image(lock_image)
self.toggle_lock_button(can_be_locked)
left_box.add(self.remove_button)
left_box.add(self.add_button)
left_box.add(self.lock_button)
return left_box
def generate_right_box(self):
count = self.app.db.count()
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
select_icon = Gio.ThemedIcon(name="object-select-symbolic")
select_image = Gtk.Image.new_from_gicon(
select_icon, Gtk.IconSize.BUTTON)
self.select_button.set_tooltip_text(_("Selection mode"))
self.select_button.set_image(select_image)
self.toggle_select_button(count > 0)
search_icon = Gio.ThemedIcon(name="system-search-symbolic")
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.toggle_search_button(count > 0)
self.cancel_button.set_label(_("Cancel"))
self.toggle_cancel_button(False)
right_box.add(self.search_button)
right_box.add(self.select_button)
right_box.add(self.cancel_button)
return right_box
def generate_popover(self, box):
settings_icon = Gio.ThemedIcon(name="open-menu-symbolic")
settings_image = Gtk.Image.new_from_gicon(
settings_icon, Gtk.IconSize.BUTTON)
self.settings_button.set_tooltip_text(_("Settings"))
self.settings_button.set_image(settings_image)
self.settings_button.connect("clicked", self.toggle_popover)
self.popover = Gtk.Popover.new_from_model(
self.settings_button, self.app.menu)
self.popover.props.width_request = 200
box.add(self.settings_button)
def toggle_popover(self, *args):
if self.popover:
if self.popover.get_visible():
self.popover.hide()
else:
self.popover.show_all()
def toggle_select_mode(self):
is_visible = self.remove_button.get_visible()
pass_enabled = self.app.cfg.read("state", "login")
self.toggle_remove_button(not is_visible)
self.toggle_cancel_button(not is_visible)
self.set_show_close_button(is_visible)
self.toggle_settings_button(is_visible)
self.toggle_lock_button(is_visible and pass_enabled)
self.toggle_add_button(is_visible)
self.toggle_select_button(is_visible)
if not is_visible:
self.get_style_context().add_class("selection-mode")
else:
self.get_style_context().remove_class("selection-mode")
def toggle_search(self):
self.search_button.set_active(not self.search_button.get_active())
def toggle_search_button(self, visible):
self.search_button.set_visible(visible)
self.search_button.set_no_show_all(not visible)
def toggle_select_button(self, visible):
self.select_button.set_visible(visible)
self.select_button.set_no_show_all(not visible)
def toggle_settings_button(self, visible):
if not is_gnome():
self.settings_button.set_visible(visible)
self.lock_bsettings_buttonutton.set_no_show_all(not visible)
def toggle_lock_button(self, visible):
self.lock_button.set_visible(visible)
self.lock_button.set_no_show_all(not visible)
def toggle_add_button(self, visible):
self.add_button.set_visible(visible)
self.add_button.set_no_show_all(not visible)
def toggle_cancel_button(self, visible):
self.cancel_button.set_visible(visible)
self.cancel_button.set_no_show_all(not visible)
def toggle_remove_button(self, visible):
self.remove_button.set_visible(visible)
self.remove_button.set_no_show_all(not visible)
def hide(self):
self.toggle_add_button(False)
self.toggle_lock_button(False)
self.toggle_cancel_button(False)
self.toggle_remove_button(False)
self.toggle_search_button(False)
self.toggle_settings_button(True)
self.toggle_select_button(False)
def is_on_select_mode(self):
return self.remove_button.get_visible()
def refresh(self):
is_locked = self.app.locked
pass_enabled = self.app.cfg.read("state", "login")
can_be_locked = not is_locked and pass_enabled
count = self.app.db.count()
if is_locked:
self.hide()
else:
if count == 0:
self.toggle_add_button(True)
self.toggle_select_button(False)
self.toggle_remove_button(False)
self.toggle_search_button(False)
self.toggle_lock_button(can_be_locked)
self.toggle_settings_button(True)
self.toggle_cancel_button(False)
else:
self.toggle_add_button(True)
self.toggle_select_button(True)
self.toggle_remove_button(False)
self.toggle_search_button(True)
self.toggle_lock_button(can_be_locked)
self.toggle_settings_button(True)
self.toggle_cancel_button(False)

View file

@ -0,0 +1,69 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, Gdk, GObject, GLib
import logging
from hashlib import sha256
from gettext import gettext as _
class LoginWindow(Gtk.Box):
password_entry = None
unlock_button = None
def __init__(self, application):
self.app = application
Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
self.password_entry = Gtk.Entry()
self.unlock_button = Gtk.Button()
self.generate_box()
def generate_box(self):
password_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
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)
self.unlock_button.set_label(_("Unlock"))
self.unlock_button.connect("clicked", self.on_unlock)
password_box.pack_start(self.unlock_button, False, False, 6)
self.pack_start(password_box, True, False, 6)
def on_unlock(self, *args):
"""
Password check and unlock
"""
typed_pass = self.password_entry.get_text()
ecrypted_pass = sha256(typed_pass.encode("utf-8")).hexdigest()
login_pass = self.app.cfg.read("password", "login")
if ecrypted_pass == login_pass or login_pass == typed_pass:
self.password_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.toggle_lock()
self.password_entry.set_text("")
else:
self.password_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, "dialog-error-symbolic")
def toggle_lock(self):
"""
Lock/unlock the application
"""
self.app.locked = not self.app.locked
if self.app.locked:
self.focus()
self.app.refresh_menu()
self.app.win.refresh_window()
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)
def focus(self):
self.password_entry.grab_focus_without_selecting()

View file

@ -0,0 +1,32 @@
from gi import require_version
require_version("Gtk", "3.0")
from gi.repository import Gtk
import logging
from gettext import gettext as _
class NoAccountWindow(Gtk.Box):
def __init__(self):
Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL,
spacing=6)
self.generate_box()
def generate_box(self):
logo_image = Gtk.Image()
logo_image.set_from_icon_name("dialog-information-symbolic",
Gtk.IconSize.DIALOG)
no_apps_label = Gtk.Label()
no_apps_label.set_text(_("There's no account at the moment"))
self.pack_start(logo_image, False, False, 6)
self.pack_start(no_apps_label, False, False, 6)
def toggle(self, visible):
self.set_visible(visible)
self.set_no_show_all(not visible)
def hide(self):
self.toggle(False)
def show(self):
self.toggle(True)

View file

@ -5,6 +5,9 @@ from TwoFactorAuth.widgets.add_account import AddAccount
from TwoFactorAuth.widgets.confirmation import ConfirmationMessage
from TwoFactorAuth.widgets.account_row import AccountRow
from TwoFactorAuth.widgets.search_bar import SearchBar
from TwoFactorAuth.widgets.login_window import LoginWindow
from TwoFactorAuth.widgets.no_account_window import NoAccountWindow
from TwoFactorAuth.widgets.headerbar import HeaderBar
import logging
from hashlib import sha256
from gettext import gettext as _
@ -16,26 +19,15 @@ class Window(Gtk.ApplicationWindow):
selected_count = 0
counter = 0
hb = Gtk.HeaderBar()
hb = None
main_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)
login_box = None
no_account_box = None
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()
settings_button = Gtk.Button()
remove_button = Gtk.Button()
cancel_button = Gtk.Button()
select_button = Gtk.Button()
lock_button = Gtk.Button()
popover = None
settings_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
pop_settings = Gtk.ModelButton.new()
password_entry = Gtk.Entry()
def __init__(self, application):
self.app = application
@ -43,7 +35,7 @@ class Window(Gtk.ApplicationWindow):
self.generate_header_bar()
self.generate_search_bar()
self.generate_accounts_list()
self.generate_no_apps_box()
self.generate_no_accounts_box()
self.generate_login_form()
self.refresh_window()
GLib.timeout_add_seconds(60, self.refresh_counter)
@ -70,15 +62,23 @@ class Window(Gtk.ApplicationWindow):
"""
keypress = Gdk.keyval_name(key_event.keyval).lower()
if not self.app.locked:
count = self.app.db.count()
if count > 0:
if self.list_box.get_selected_row():
index = self.list_box.get_selected_row().get_index()
else:
index = 0
selected_row = self.list_box.get_row_at_index(index)
control_mask = Gdk.ModifierType.CONTROL_MASK
count = self.app.auth.count()
if keypress == "c":
if key_event.state == control_mask:
self.copy_code()
selected_row.copy_code()
elif keypress == "l":
if key_event.state == control_mask:
self.login_box.toggle_lock()
elif keypress == "f":
if key_event.state == control_mask:
self.search_button.set_active(
not self.search_button.get_active())
self.hb.toggle_search()
elif keypress == "s":
if key_event.state == control_mask:
self.toggle_select()
@ -89,29 +89,27 @@ class Window(Gtk.ApplicationWindow):
self.remove_account()
elif keypress == "return":
if count > 0:
if self.list_box.get_selected_row():
index = self.list_box.get_selected_row().get_index()
else:
index = 0
self.list_box.get_row_at_index(index).toggle_code_box()
selected_row.toggle_code_box()
elif keypress == "backspace":
if self.search_bar.is_empty():
self.search_button.set_active(False)
self.hb.search_button.set_active(False)
elif keypress == "escape":
if self.search_bar.is_visible():
self.search_button.set_active(False)
self.hb.search_button.set_active(False)
if not self.select_button.get_visible():
self.toggle_select()
elif keypress == "up" or keypress == "down":
dx = -1 if keypress == "up" else 1
if count != 0:
index = self.list_box.get_selected_row().get_index()
index = (index + dx)%count
selected_row = self.list_box.get_row_at_index(index)
self.list_box.select_row(selected_row)
row = self.list_box.get_selected_row()
if row:
index = row.get_index()
index = (index + dx)%count
selected_row = self.list_box.get_row_at_index(index)
self.list_box.select_row(selected_row)
else:
if keypress == "return":
self.on_unlock_clicked()
self.login_box.on_unlock()
def refresh_counter(self):
"""
@ -122,7 +120,7 @@ class Window(Gtk.ApplicationWindow):
if self.app.cfg.read("auto-lock", "preferences"):
if self.counter == self.app.cfg.read("auto-lock-time", "preferences") - 1:
self.counter = 0
self.toggle_app_lock()
self.toggle_lock()
return True
def generate_search_bar(self):
@ -130,7 +128,7 @@ class Window(Gtk.ApplicationWindow):
Generate search bar box and entry
"""
self.search_bar = SearchBar(self.list_box)
self.search_button.connect("toggled", self.search_bar.toggle)
self.hb.search_button.connect("toggled", self.search_bar.toggle)
self.apps_box.pack_start(self.search_bar, False, True, 0)
self.main_box.pack_start(self.apps_box, True, True, 0)
@ -148,7 +146,7 @@ class Window(Gtk.ApplicationWindow):
if checkbox.get_active():
label_id = row.get_id()
row.kill()
self.app.auth.remove_by_id(label_id)
self.app.db.remove_by_id(label_id)
self.list_box.remove(row)
self.list_box.unselect_all()
confirmation.destroy()
@ -159,156 +157,22 @@ class Window(Gtk.ApplicationWindow):
"""
Generate login form
"""
password_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
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)
unlock_button = Gtk.Button()
unlock_button.set_label(_("Unlock"))
unlock_button.connect("clicked", self.on_unlock_clicked)
password_box.pack_start(unlock_button, False, False, 6)
self.login_box.pack_start(password_box, True, False, 6)
self.login_box = LoginWindow(self.app)
self.hb.lock_button.connect("clicked", self.login_box.toggle_lock)
self.main_box.pack_start(self.login_box, True, False, 0)
def on_unlock_clicked(self, *args):
"""
Password check and unlock
"""
typed_pass = self.password_entry.get_text()
ecrypted_pass = sha256(typed_pass.encode("utf-8")).hexdigest()
login_pass = self.app.cfg.read("password", "login")
if ecrypted_pass == login_pass or login_pass == typed_pass:
self.password_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.SECONDARY, None)
self.toggle_app_lock()
self.password_entry.set_text("")
else:
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
self.app.refresh_menu()
self.refresh_window()
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 hide_header_bar(self):
"""
Hide all buttons on the header bar
"""
self.toggle_hb_buttons(False, False, False, False, False, False, False)
def generate_header_bar(self):
"""
Generate a header bar box
"""
count = self.app.auth.count()
self.hb.set_show_close_button(True)
left_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
right_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
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 accounts"))
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)
add_icon = Gio.ThemedIcon(name="list-add-symbolic")
add_image = Gtk.Image.new_from_gicon(add_icon, Gtk.IconSize.BUTTON)
self.add_button.set_tooltip_text(_("Add a new account"))
self.add_button.set_image(add_image)
self.add_button.connect("clicked", self.add_account)
pass_enabled = self.app.cfg.read("state", "login")
can_be_locked = not self.app.locked and pass_enabled
lock_icon = Gio.ThemedIcon(name="changes-prevent-symbolic")
lock_image = Gtk.Image.new_from_gicon(lock_icon, Gtk.IconSize.BUTTON)
self.lock_button.set_tooltip_text(_("Lock the Application"))
self.lock_button.set_image(lock_image)
self.lock_button.connect("clicked", self.app.on_toggle_lock)
self.lock_button.set_no_show_all(not can_be_locked)
self.lock_button.set_visible(can_be_locked)
left_box.add(self.remove_button)
left_box.add(self.add_button)
left_box.add(self.lock_button)
select_icon = Gio.ThemedIcon(name="object-select-symbolic")
select_image = Gtk.Image.new_from_gicon(
select_icon, Gtk.IconSize.BUTTON)
self.select_button.set_tooltip_text(_("Selection mode"))
self.select_button.set_image(select_image)
self.select_button.connect("clicked", self.toggle_select)
self.select_button.set_no_show_all(not count > 0)
self.select_button.set_visible(count > 0)
search_icon = Gio.ThemedIcon(name="system-search-symbolic")
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.set_no_show_all(not count > 0)
self.search_button.set_visible(count > 0)
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(self.search_button)
right_box.add(self.select_button)
right_box.add(self.cancel_button)
if not self.app.use_GMenu:
self.generate_popover(right_box)
self.hb.pack_start(left_box)
self.hb.pack_end(right_box)
self.hb = HeaderBar(self.app)
# connect signals
self.hb.cancel_button.connect("clicked", self.toggle_select)
self.hb.select_button.connect("clicked", self.toggle_select)
self.hb.remove_button.connect("clicked", self.remove_selected)
self.hb.add_button.connect("clicked", self.add_account)
self.set_titlebar(self.hb)
def generate_popover(self, box):
settings_icon = Gio.ThemedIcon(name="open-menu-symbolic")
settings_image = Gtk.Image.new_from_gicon(
settings_icon, Gtk.IconSize.BUTTON)
self.settings_button.set_tooltip_text(_("Settings"))
self.settings_button.set_image(settings_image)
self.settings_button.connect("clicked", self.toggle_popover)
self.popover = Gtk.Popover.new_from_model(
self.settings_button, self.app.menu)
self.popover.props.width_request = 200
box.add(self.settings_button)
def toggle_popover(self, *args):
if self.popover:
if self.popover.get_visible():
self.popover.hide()
else:
self.popover.show_all()
def add_account(self, *args):
"""
Create add application window
@ -320,28 +184,13 @@ class Window(Gtk.ApplicationWindow):
"""
Toggle select mode
"""
is_visible = self.remove_button.get_visible()
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)
if not self.app.use_GMenu:
self.settings_button.set_visible(is_visible)
self.hb.toggle_select_mode()
pass_enabled = self.app.cfg.read("state", "login")
self.lock_button.set_visible(is_visible and pass_enabled)
self.add_button.set_visible(is_visible)
self.select_button.set_visible(is_visible)
if not is_visible:
is_select_mode = self.hb.is_on_select_mode()
if is_select_mode:
self.list_box.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
self.hb.get_style_context().add_class("selection-mode")
else:
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
@ -355,11 +204,12 @@ class Window(Gtk.ApplicationWindow):
code_label = row.get_code_label()
visible = checkbox.get_visible()
selected = checkbox.get_active()
if not is_visible:
style_context = code_label.get_style_context()
if is_select_mode:
self.select_account(checkbox)
code_label.get_style_context().add_class("application-secret-code-select-mode")
style_context.add_class("application-secret-code-select-mode")
else:
code_label.get_style_context().remove_class(
style_context.remove_class(
"application-secret-code-select-mode")
checkbox.set_visible(not visible)
@ -381,16 +231,16 @@ class Window(Gtk.ApplicationWindow):
self.list_box.unselect_row(listbox_row)
if is_visible:
self.selected_count -= 1
self.remove_button.set_sensitive(self.selected_count > 0)
self.hb.remove_button.set_sensitive(self.selected_count > 0)
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()
is_select_mode = self.hb.is_on_select_mode()
checkbox = listbox_row.get_checkbox()
if button_visible:
if is_select_mode:
checkbox.set_active(not checkbox.get_active())
else:
if self.selected_app_idx:
@ -405,7 +255,7 @@ class Window(Gtk.ApplicationWindow):
"""
Generate an account ListBox inside of a ScrolledWindow
"""
count = self.app.auth.count()
count = self.app.db.count()
# Create a ScrolledWindow for accounts
self.list_box.get_style_context().add_class("applications-list")
@ -418,7 +268,7 @@ class Window(Gtk.ApplicationWindow):
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()
apps = self.app.db.fetch_apps()
i = 0
count = len(apps)
while i < count:
@ -426,20 +276,12 @@ class Window(Gtk.ApplicationWindow):
apps[i][3]))
i += 1
def generate_no_apps_box(self):
def generate_no_accounts_box(self):
"""
Generate a box with no accounts message
"""
logo_image = Gtk.Image()
logo_image.set_from_icon_name("dialog-information-symbolic",
Gtk.IconSize.DIALOG)
no_apps_label = Gtk.Label()
no_apps_label.set_text(_("There's no account at the moment"))
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)
self.no_account_box = NoAccountWindow()
self.main_box.pack_start(self.no_account_box, True, False, 0)
def append_list_box(self, uid, name, secret_code, image):
"""
@ -453,78 +295,31 @@ class Window(Gtk.ApplicationWindow):
self.list_box.add(AccountRow(self, uid, name, secret_code, image))
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.list_box.select_row(row)
selected_row = self.list_box.get_selected_row()
code = selected_row.get_code()
try:
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
clipboard.clear()
clipboard.set_text(code, len(code))
logging.debug("Secret code copied to clipboard")
except Exception as e:
logging.error(str(e))
def refresh_window(self):
"""
Refresh windows components
"""
count = self.app.auth.count()
is_locked = self.app.locked
pass_enabled = self.app.cfg.read("state", "login")
can_be_locked = not is_locked and pass_enabled
count = self.app.db.count()
if is_locked:
self.toggle_boxes(False, False, True)
self.hide_header_bar()
self.login_box.show()
self.no_account_box.hide()
self.apps_box.set_visible(False)
self.apps_box.set_no_show_all(True)
else:
self.login_box.hide()
if count == 0:
self.toggle_boxes(False, True, False)
self.toggle_hb_buttons(
False, True, False, False, False, True, can_be_locked)
self.no_account_box.show()
self.apps_box.set_visible(False)
self.apps_box.set_no_show_all(True)
else:
self.toggle_boxes(True, False, False)
self.toggle_hb_buttons(
False, True, True, True, False, True, can_be_locked)
self.pop_settings.set_sensitive(not is_locked)
self.no_account_box.hide()
self.apps_box.set_visible(True)
self.apps_box.set_no_show_all(False)
self.hb.refresh()
self.main_box.show_all()
self.list_box.set_selection_mode(Gtk.SelectionMode.SINGLE)
def toggle_hb_buttons(self, remove, add, search, select, cancel, settings, lock):
"""
Toggle header bar buttons visibility
:param remove: (bool)
:param add: (bool)
:param search: (bool)
:param select: (bool)
:param cancel: (bool)
:param settings: (bool)
:param lock: (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)
self.lock_button.set_visible(lock)
self.lock_button.set_no_show_all(not lock)
if not self.app.use_GMenu:
self.settings_button.set_visible(settings)
self.settings_button.set_no_show_all(not settings)
def remove_account(self, *args):
"""
Remove an application
@ -542,7 +337,7 @@ class Window(Gtk.ApplicationWindow):
app_id = selected_row.get_id()
selected_row.kill()
self.list_box.remove(selected_row)
self.app.auth.remove_by_id(app_id)
self.app.db.remove_by_id(app_id)
confirmation.destroy()
self.refresh_window()

View file

@ -58,6 +58,13 @@
<property name="accelerator">&lt;Primary&gt;S</property>
</object>
</child>
<child>
<object class="GtkShortcutsShortcut">
<property name="visible">True</property>
<property name="title" translatable="yes" context="shortcut window">Lock the application</property>
<property name="accelerator">&lt;Primary&gt;L</property>
</object>
</child>
</object>
</child>

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-06-12 14:18+0200\n"
"POT-Creation-Date: 2016-06-15 02:08+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"
@ -52,6 +52,11 @@ msgctxt "shortcut window"
msgid "Selection mode"
msgstr ""
#: data/shortcuts.ui:64
msgctxt "shortcut window"
msgid "Lock the application"
msgstr ""
#: data/about.ui:14
msgid "Simple application to generate two-factor authentication code"
msgstr ""
@ -63,56 +68,50 @@ msgid ""
"later</a> for details."
msgstr ""
#: TwoFactorAuth/widgets/window.py:142
msgid "Do you really want to remove selected accounts?"
msgstr ""
#: TwoFactorAuth/widgets/window.py:165
msgid "Enter your password"
msgstr ""
#: TwoFactorAuth/widgets/window.py:169
msgid "Unlock"
msgstr ""
#: TwoFactorAuth/widgets/window.py:235
#: TwoFactorAuth/widgets/headerbar.py:43
msgid "Remove selected accounts"
msgstr ""
#: TwoFactorAuth/widgets/window.py:243 TwoFactorAuth/widgets/add_account.py:28
#: TwoFactorAuth/widgets/headerbar.py:50
#: TwoFactorAuth/widgets/add_account.py:28
#: TwoFactorAuth/widgets/add_account.py:43
msgid "Add a new account"
msgstr ""
#: TwoFactorAuth/widgets/window.py:251
#: TwoFactorAuth/widgets/headerbar.py:57
msgid "Lock the Application"
msgstr ""
#: TwoFactorAuth/widgets/window.py:263
#: TwoFactorAuth/widgets/headerbar.py:73
msgid "Selection mode"
msgstr ""
#: TwoFactorAuth/widgets/window.py:272
#: TwoFactorAuth/widgets/headerbar.py:80
#: TwoFactorAuth/widgets/applications_list.py:88
msgid "Search"
msgstr ""
#: TwoFactorAuth/widgets/window.py:277 TwoFactorAuth/widgets/add_account.py:48
#: TwoFactorAuth/widgets/headerbar.py:84
#: TwoFactorAuth/widgets/add_account.py:48
#: TwoFactorAuth/widgets/change_password.py:143
#: TwoFactorAuth/widgets/applications_list.py:80
msgid "Cancel"
msgstr ""
#: TwoFactorAuth/widgets/window.py:296 TwoFactorAuth/widgets/settings.py:24
#: TwoFactorAuth/application.py:65
#: TwoFactorAuth/widgets/headerbar.py:96 TwoFactorAuth/widgets/settings.py:24
#: TwoFactorAuth/application.py:60
msgid "Settings"
msgstr ""
#: TwoFactorAuth/widgets/window.py:438
#: TwoFactorAuth/widgets/no_account_window.py:19
msgid "There's no account at the moment"
msgstr ""
#: TwoFactorAuth/widgets/window.py:536
#: TwoFactorAuth/widgets/window.py:140
msgid "Do you really want to remove selected accounts?"
msgstr ""
#: TwoFactorAuth/widgets/window.py:340
msgid "Do you really want to remove this account?"
msgstr ""
@ -156,25 +155,33 @@ msgstr ""
msgid "Next"
msgstr ""
#: TwoFactorAuth/widgets/account_row.py:153
#: TwoFactorAuth/widgets/login_window.py:23
msgid "Enter your password"
msgstr ""
#: TwoFactorAuth/widgets/login_window.py:26
msgid "Unlock"
msgstr ""
#: TwoFactorAuth/widgets/account_row.py:161
msgid "Copy the generated code"
msgstr ""
#: TwoFactorAuth/widgets/account_row.py:163
#: TwoFactorAuth/widgets/account_row.py:171
msgid "Remove the account"
msgstr ""
#: TwoFactorAuth/widgets/account_row.py:169
#: TwoFactorAuth/widgets/account_row.py:225
#: TwoFactorAuth/widgets/account_row.py:177
#: TwoFactorAuth/widgets/account_row.py:233
#, python-format
msgid "Expires in %s seconds"
msgstr ""
#: TwoFactorAuth/widgets/account_row.py:175
#: TwoFactorAuth/widgets/account_row.py:183
msgid "Error during the generation of code"
msgstr ""
#: TwoFactorAuth/widgets/account_row.py:221
#: TwoFactorAuth/widgets/account_row.py:229
msgid "Couldn't generate the secret code"
msgstr ""
@ -198,18 +205,18 @@ msgstr ""
msgid "Secret code generation time (s) :"
msgstr ""
#: TwoFactorAuth/application.py:29
#: TwoFactorAuth/application.py:28
msgid "TwoFactorAuth"
msgstr ""
#: TwoFactorAuth/application.py:73
#: TwoFactorAuth/application.py:68
msgid "Shortcuts"
msgstr ""
#: TwoFactorAuth/application.py:75
#: TwoFactorAuth/application.py:70
msgid "About"
msgstr ""
#: TwoFactorAuth/application.py:76
#: TwoFactorAuth/application.py:71
msgid "Quit"
msgstr ""