mirror of
https://gitlab.gnome.org/World/Authenticator.git
synced 2025-03-04 08:44:40 +01:00
commit
759e1c5b2c
15 changed files with 513 additions and 390 deletions
|
@ -1,5 +1,6 @@
|
|||
app_PYTHON = \
|
||||
application.py \
|
||||
utils.py \
|
||||
__init__.py
|
||||
|
||||
appdir = $(pythondir)/TwoFactorAuth
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -2,5 +2,5 @@ appdir = $(pythondir)/TwoFactorAuth/models
|
|||
|
||||
app_PYTHON = \
|
||||
code.py \
|
||||
authenticator.py \
|
||||
database.py \
|
||||
settings.py
|
||||
|
|
|
@ -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
46
TwoFactorAuth/utils.py
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
197
TwoFactorAuth/widgets/headerbar.py
Normal file
197
TwoFactorAuth/widgets/headerbar.py
Normal 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)
|
69
TwoFactorAuth/widgets/login_window.py
Normal file
69
TwoFactorAuth/widgets/login_window.py
Normal 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()
|
32
TwoFactorAuth/widgets/no_account_window.py
Normal file
32
TwoFactorAuth/widgets/no_account_window.py
Normal 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)
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -58,6 +58,13 @@
|
|||
<property name="accelerator"><Primary>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"><Primary>L</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
|
Loading…
Add table
Reference in a new issue