#!/usr/bin/python3 """ A mobile first interface for the standard unix password manager written in python """ import os from typing import Union import wx import wx.lib.scrolledpanel as scrolled from pass_handler import Pass, copy_to_clipboard, get_password_from_path, pass_pull, pass_push class PassUi(wx.Frame): """ The wx.Frame for passui """ def redraw(*args): """Decorator used for redrawing the widgets in the sizer Returns: function: The decorated function """ func = args[0] def wrapper(self, *wrapper_args): """The wrapper function for the decorator """ self.sizer.Clear(delete_windows=True) func(self, *wrapper_args) self.pnl.SetupScrolling() self.sizer.Layout() return wrapper def __init__(self, *args, **kw): """__init__. :param args: :param kw: """ super().__init__(*args, **kw) self.pass_handler: Pass = Pass() # create a panel in the frame self.pnl: wx.lib.scrolledpanel.ScrolledPanel = scrolled.ScrolledPanel(self, -1, style=wx.VSCROLL) self.pnl.SetupScrolling() # and create a sizer to manage the layout of child widgets self.sizer: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL) self.pnl.SetSizer(self.sizer) self.add_buttons() @redraw def add_buttons(self): """add_buttons.""" self.add_tools() if self.pass_handler.cur_dir != self.pass_handler.top_dir: btn: wx.Button = self.make_back_button() self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event: self.path_button_clicked(), btn) index: int = 0 for c_path in self.pass_handler.cur_paths: if c_path != self.pass_handler.cur_dir: label: str = '๐Ÿ—€ ' + os.path.basename(os.path.normpath(c_path)) btn: wx.Button = wx.Button(self.pnl, label=label) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event, path=c_path: self.path_button_clicked( path), btn) index = index + 1 index = 0 for password in self.pass_handler.cur_passwords: label: str = '๐Ÿ— ' + os.path.splitext( os.path.basename(os.path.normpath(password)))[0] btn: wx.Button = wx.Button(self.pnl, label=label) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event, m_index=index: self.password_button_clicked( m_index), btn) index = index + 1 def add_push_pull(self): """add_push_pull.""" push_btn: wx.Button = wx.Button(self.pnl, label="Push to remote") self.sizer.Add(push_btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event: pass_push(), push_btn) pull_btn: wx.Button = wx.Button(self.pnl, label="Pull from remote") self.sizer.Add(pull_btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event: pass_pull(), pull_btn) def add_tools(self, index: Union[None, int] = None): """add_tools. :param index: """ btn: wx.Button = wx.Button(self.pnl, label="Show tools") font: wx.Font = btn.GetFont().MakeBold() btn.SetFont(font) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event, m_index=index: self.show_tools(m_index), btn) def back_button_clicked(self, index: Union[None, int] = None): """back_button_clicked. :param index: """ if index: self.show_password_dialog(index) else: self.add_buttons() def delete_password(self, index: int): """delete_password. :param index: """ path: str = self.pass_handler.get_pass_path_from_index(index, "password") dlg: wx.MessageDialog = wx.MessageDialog(self.pnl, "Delete " + path + "?", "Are you sure?", style=wx.CANCEL | wx.CANCEL_DEFAULT | wx.OK) dlg.SetOKCancelLabels("&Yes", "&Don't delete") reply: int = dlg.ShowModal() if reply == wx.ID_CANCEL: return self.pass_handler.delete_password(path) self.back_button_clicked() def make_back_button(self, index: Union[None, int] = None) -> wx.Button: """make_back_button. :param index: """ if index is not None: label: str = self.pass_handler.get_pass_path_from_index(index, "password") else: label: str = self.pass_handler.cur_dir.replace(self.pass_handler.top_dir, '') btn: wx.Button = wx.Button(self.pnl, label=label + 'โคด') font: wx.Font = btn.GetFont().MakeItalic().MakeBold() btn.SetFont(font) return btn def password_button_clicked(self, index: int): """password_button_clicked. :param index: """ self.show_password_dialog(index) def path_button_clicked(self, path: Union[None, str] = None): """path_button_clicked. :param path: """ if path is None: path = os.path.abspath(os.path.join(self.pass_handler.cur_dir, os.pardir)) self.pass_handler.cur_dir = path self.pass_handler.cur_paths = self.pass_handler.get_pass_paths() self.pass_handler.cur_passwords = self.pass_handler.get_pass_passwords() self.add_buttons() def save_to_pass(self, path: str, text: wx.TextCtrl, name: Union[None, wx.TextCtrl] = None): """save_to_pass. :param path: :param text: :param name: """ full_path: str = os.path.dirname(self.pass_handler.top_dir + '/' + path.lstrip('/')) if name is not None: path: str = name.GetLineText(0) full_path: str = os.path.dirname(self.pass_handler.top_dir + '/' + path.lstrip('/')) filename: str = full_path + '.gpg' if os.path.exists(full_path) or os.path.exists(filename): dlg: wx.MessageDialog = wx.MessageDialog( self.pnl, "Path: " + path + " already exists! Please update password path", "Error", style=wx.OK) dlg.ShowModal() reply: int = wx.ID_CANCEL else: dlg: wx.MessageDialog = wx.MessageDialog(self.pnl, "Save to " + path + "?", "Are you sure?", style=wx.CANCEL | wx.CANCEL_DEFAULT | wx.OK) dlg.SetOKCancelLabels("&Yes", "&Don't save") reply: int = dlg.ShowModal() if reply == wx.ID_CANCEL: return password: str = str() for line_no in range(text.GetNumberOfLines()): password += text.GetLineText(line_no) password += '\n' # FIXME: Is this right? Maybe we break stuff with the trailing newline? self.pass_handler.save_to_pass(password, path, full_path) self.back_button_clicked() @redraw def show_new_dialog(self): """show_new_dialog. """ pass_path: str = self.pass_handler.cur_dir.replace(self.pass_handler.top_dir, '') btn: wx.Button = self.make_back_button() self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event, path=self.pass_handler.cur_dir: self.path_button_clicked( path), btn) name_sizer: wx.BoxSizer = wx.BoxSizer(orient=wx.VERTICAL) # pylint: disable=no-member name_sizer.Add(wx.StaticText(self.pnl, -1, "Password path:"), 0, wx.EXPAND) # pylint: disable=no-member pw_sizer: wx.BoxSizer = wx.BoxSizer(orient=wx.VERTICAL) # pylint: disable=no-member pw_sizer.Add(wx.StaticText(self.pnl, -1, "Password:"), 0, wx.EXPAND) # pylint: disable=no-member name: wx.TextCtrl = wx.TextCtrl(self.pnl, value=pass_path, style=wx.TE_DONTWRAP) text: wx.TextCtrl = wx.TextCtrl(self.pnl, style=wx.TE_MULTILINE | wx.TE_DONTWRAP) s_btn: wx.Button = wx.Button(self.pnl, label="Save password") name_sizer.Add(name, 0, wx.EXPAND) # pylint: disable=no-member pw_sizer.Add(text, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(name_sizer, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(pw_sizer, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(s_btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event, path=pass_path, m_text=text, m_name=name: self. save_to_pass(path, m_text, m_name), s_btn) name.SetFocus() @redraw def show_password_dialog(self, index: int): """show_password_dialog. :param index: """ self.add_tools(index) pass_path: str = self.pass_handler.get_pass_path_from_index(index, "password") c_path: str = self.pass_handler.top_dir + os.path.dirname(pass_path) password: str = get_password_from_path(pass_path) btn: wx.Button = self.make_back_button(index) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind( wx.EVT_BUTTON, lambda event, path=c_path: self.path_button_clicked(path), btn) s_btn: wx.Button = wx.Button(self.pnl, label="Show/edit password") c_btn: wx.Button = wx.Button(self.pnl, label="Copy password") d_btn: wx.Button = wx.Button(self.pnl, label="Delete password") self.sizer.Add(s_btn, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(c_btn, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(d_btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event, text=password: copy_to_clipboard(text), c_btn) self.Bind( wx.EVT_BUTTON, lambda event, m_index=index: self.show_password(m_index), s_btn) self.Bind( wx.EVT_BUTTON, lambda event, m_index=index: self.delete_password(m_index), d_btn) @redraw def show_password(self, index: int): """show_password. :param index: """ self.add_tools(index) pass_path: str = self.pass_handler.get_pass_path_from_index(index, "password") c_path: str = self.pass_handler.top_dir + os.path.dirname(pass_path) password: str = get_password_from_path(pass_path) btn: wx.Button = self.make_back_button(index) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind( wx.EVT_BUTTON, lambda event, path=c_path: self.path_button_clicked(path), btn) text: wx.TextCtrl = wx.TextCtrl(self.pnl, value=password, style=wx.TE_MULTILINE | wx.TE_DONTWRAP) c_btn: wx.Button = wx.Button(self.pnl, label="Copy password") s_btn: wx.Button = wx.Button(self.pnl, label="Save password") self.sizer.Add(text, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(c_btn, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(s_btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event, m_text=password: copy_to_clipboard(m_text), c_btn) self.Bind(wx.EVT_BUTTON, lambda event, path=pass_path, m_text=text: self.save_to_pass( path, m_text), s_btn) @redraw def show_tools(self, index: Union[None, int] = None): """show_tools. :param index: """ btn: wx.Button = wx.Button(self.pnl, label="Go back") font: wx.Font = btn.GetFont().MakeBold() btn.SetFont(font) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind( wx.EVT_BUTTON, lambda event, m_index=index: self.back_button_clicked(m_index), btn) n_btn: wx.Button = wx.Button(self.pnl, label="Add new password") font: wx.Font = n_btn.GetFont().MakeBold() n_btn.SetFont(font) self.sizer.Add(n_btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event: self.show_new_dialog(), n_btn) self.add_push_pull() if __name__ == '__main__': # When this module is run (not imported) then create the app, the # frame, show it, and start the event loop. app: wx.App = wx.App() frm: PassUi = PassUi(None, title='PassUi') frm.Show() app.MainLoop()