Compare commits
5 Commits
275bdb6583
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b2e43e2c3 | |||
| 5815fffcab | |||
| ee7bde5adc | |||
| 6af7b9b1b8 | |||
| b292556403 |
162
gtkpass.py
162
gtkpass.py
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
import gi
|
import gi
|
||||||
@@ -27,6 +28,7 @@ class GTKPass(Gtk.Window):
|
|||||||
self.conf = self.passs.conf
|
self.conf = self.passs.conf
|
||||||
self._border = 5
|
self._border = 5
|
||||||
self._expand = False
|
self._expand = False
|
||||||
|
self._selected = None
|
||||||
self.make_ui()
|
self.make_ui()
|
||||||
|
|
||||||
def make_ui(self):
|
def make_ui(self):
|
||||||
@@ -77,8 +79,10 @@ class GTKPass(Gtk.Window):
|
|||||||
self.ts_filter = self.tree_store.filter_new()
|
self.ts_filter = self.tree_store.filter_new()
|
||||||
self.ts_filter.set_visible_column(0)
|
self.ts_filter.set_visible_column(0)
|
||||||
self.treeview = Gtk.TreeView(model=self.ts_filter)
|
self.treeview = Gtk.TreeView(model=self.ts_filter)
|
||||||
|
self.treeview.set_activate_on_single_click(True)
|
||||||
self.treeview.set_headers_visible(False)
|
self.treeview.set_headers_visible(False)
|
||||||
self.treeview.connect("key-release-event", self.on_treeview_keypress)
|
self.treeview.connect("key-release-event", self.on_treeview_keypress)
|
||||||
|
self.treeview.connect('row-activated', self.on_row_activated)
|
||||||
|
|
||||||
icon_renderer = Gtk.CellRendererPixbuf()
|
icon_renderer = Gtk.CellRendererPixbuf()
|
||||||
text_renderer = Gtk.CellRendererText()
|
text_renderer = Gtk.CellRendererText()
|
||||||
@@ -168,6 +172,7 @@ class GTKPass(Gtk.Window):
|
|||||||
|
|
||||||
b_dir = Gtk.ToolButton()
|
b_dir = Gtk.ToolButton()
|
||||||
b_dir.set_icon_name("folder-new-symbolic")
|
b_dir.set_icon_name("folder-new-symbolic")
|
||||||
|
b_dir.connect("clicked", self.on_new_dir)
|
||||||
toolbar.insert(b_dir, 1)
|
toolbar.insert(b_dir, 1)
|
||||||
|
|
||||||
b_edit = Gtk.ToolButton()
|
b_edit = Gtk.ToolButton()
|
||||||
@@ -176,6 +181,7 @@ class GTKPass(Gtk.Window):
|
|||||||
|
|
||||||
b_del = Gtk.ToolButton()
|
b_del = Gtk.ToolButton()
|
||||||
b_del.set_icon_name("edit-delete-symbolic")
|
b_del.set_icon_name("edit-delete-symbolic")
|
||||||
|
b_del.connect("clicked", self.on_delete)
|
||||||
toolbar.insert(b_del, 3)
|
toolbar.insert(b_del, 3)
|
||||||
|
|
||||||
b_gitpush = Gtk.ToolButton()
|
b_gitpush = Gtk.ToolButton()
|
||||||
@@ -250,6 +256,21 @@ class GTKPass(Gtk.Window):
|
|||||||
self.make_subtree_visible(model, iter)
|
self.make_subtree_visible(model, iter)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def on_row_activated(self, treeview, treepath, treeview_col):
|
||||||
|
selection = treeview.get_selection()
|
||||||
|
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
model, treeiter = selection.get_selected()
|
||||||
|
|
||||||
|
if (self._selected is not None and
|
||||||
|
self._selected == model[treeiter][4]):
|
||||||
|
self._selected = None
|
||||||
|
selection.unselect_all()
|
||||||
|
else:
|
||||||
|
self._selected = model[treeiter][4]
|
||||||
|
|
||||||
def on_selected(self, selection):
|
def on_selected(self, selection):
|
||||||
model, treeiter = selection.get_selected()
|
model, treeiter = selection.get_selected()
|
||||||
|
|
||||||
@@ -300,6 +321,84 @@ class GTKPass(Gtk.Window):
|
|||||||
if event.keyval == Gdk.KEY_Left and treeview.get_cursor()[0]:
|
if event.keyval == Gdk.KEY_Left and treeview.get_cursor()[0]:
|
||||||
treeview.collapse_row(treeview.get_cursor()[0])
|
treeview.collapse_row(treeview.get_cursor()[0])
|
||||||
|
|
||||||
|
def on_new_dir(self, button):
|
||||||
|
if self._selected is None:
|
||||||
|
path = ''
|
||||||
|
else:
|
||||||
|
path = self._selected
|
||||||
|
|
||||||
|
dialog = NewDirDialog(self, path)
|
||||||
|
response = dialog.run()
|
||||||
|
dirname = dialog.get_dirname()
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
if response != Gtk.ResponseType.OK or not dirname:
|
||||||
|
return
|
||||||
|
|
||||||
|
result, msg = self.passs.new_dir(os.path.join(path, dirname))
|
||||||
|
if not result:
|
||||||
|
dialog = Gtk.MessageDialog(transient_for=self,
|
||||||
|
flags=0,
|
||||||
|
message_type=Gtk.MessageType.INFO,
|
||||||
|
buttons=Gtk.ButtonsType.CLOSE,
|
||||||
|
text='There was an error')
|
||||||
|
|
||||||
|
dialog.format_secondary_text(msg)
|
||||||
|
dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
selection = self.treeview.get_selection()
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
tree_model_filter, tree_paths = selection.get_selected_rows()
|
||||||
|
tree_store = tree_model_filter.get_model()
|
||||||
|
tree_iter = tree_store.get_iter(tree_paths[0])
|
||||||
|
|
||||||
|
self.tree_store.append(tree_iter, [True, dirname,
|
||||||
|
Pango.Weight.NORMAL, "folder",
|
||||||
|
os.path.join(path, dirname), False])
|
||||||
|
|
||||||
|
|
||||||
|
def on_delete(self, button):
|
||||||
|
if not self._selected:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: add configurable confirmation?
|
||||||
|
result, msg = self.passs.delete(self._selected)
|
||||||
|
if result == self.passs.NON_EMPTY:
|
||||||
|
dialog = Gtk.MessageDialog(transient_for=self,
|
||||||
|
flags=0,
|
||||||
|
message_type=Gtk.MessageType.QUESTION,
|
||||||
|
buttons=Gtk.ButtonsType.OK_CANCEL,
|
||||||
|
text='Directory not empty')
|
||||||
|
dialog.format_secondary_text(f'Do you want to delete '
|
||||||
|
f'{self._selected} recursively?')
|
||||||
|
response = dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
if response == Gtk.ResponseType.OK:
|
||||||
|
result, msg = self.passs.delete(self._selected, True)
|
||||||
|
|
||||||
|
if result == self.passs.ERROR:
|
||||||
|
dialog = Gtk.MessageDialog(transient_for=self,
|
||||||
|
flags=0,
|
||||||
|
message_type=Gtk.MessageType.INFO,
|
||||||
|
buttons=Gtk.ButtonsType.CLOSE,
|
||||||
|
text='There was an error')
|
||||||
|
dialog.format_secondary_text(msg)
|
||||||
|
dialog.run()
|
||||||
|
dialog.destroy()
|
||||||
|
self._selected = None
|
||||||
|
|
||||||
|
# remove selected branch/leaf from store
|
||||||
|
selection = self.treeview.get_selection()
|
||||||
|
tree_model_filter, tree_paths = selection.get_selected_rows()
|
||||||
|
tree_store = tree_model_filter.get_model()
|
||||||
|
|
||||||
|
for path in tree_paths:
|
||||||
|
tree_iter = tree_store.get_iter(path)
|
||||||
|
tree_store.remove(tree_iter)
|
||||||
|
|
||||||
def on_key_press_event(self, widget, event):
|
def on_key_press_event(self, widget, event):
|
||||||
ctrl = (event.state & Gdk.ModifierType.CONTROL_MASK)
|
ctrl = (event.state & Gdk.ModifierType.CONTROL_MASK)
|
||||||
if ctrl and event.keyval == Gdk.KEY_b:
|
if ctrl and event.keyval == Gdk.KEY_b:
|
||||||
@@ -311,6 +410,32 @@ class GTKPass(Gtk.Window):
|
|||||||
# TODO: clear clipboard after a minute or so.
|
# TODO: clear clipboard after a minute or so.
|
||||||
|
|
||||||
|
|
||||||
|
class NewDirDialog(Gtk.Dialog):
|
||||||
|
def __init__(self, parent, path):
|
||||||
|
super().__init__(title="Enter new directory", transient_for=parent,
|
||||||
|
flags=0)
|
||||||
|
self.set_modal(True)
|
||||||
|
self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
|
||||||
|
Gtk.STOCK_OK, Gtk.ResponseType.OK)
|
||||||
|
|
||||||
|
label = Gtk.Label(label=f"Create new directory under "
|
||||||
|
f"{'/' if not path else path} path")
|
||||||
|
|
||||||
|
box = self.get_content_area()
|
||||||
|
box.add(label)
|
||||||
|
self.entry = Gtk.Entry()
|
||||||
|
self.entry.connect("key-release-event", self.on_release_key)
|
||||||
|
box.add(self.entry)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def on_release_key(self, entry, event):
|
||||||
|
if event.keyval == Gdk.KEY_Return:
|
||||||
|
self.response(Gtk.ResponseType.OK)
|
||||||
|
|
||||||
|
def get_dirname(self):
|
||||||
|
return self.entry.get_text()
|
||||||
|
|
||||||
|
|
||||||
class Leaf:
|
class Leaf:
|
||||||
"""A simple class to hold Leaf data"""
|
"""A simple class to hold Leaf data"""
|
||||||
def __init__(self, name, path):
|
def __init__(self, name, path):
|
||||||
@@ -346,6 +471,10 @@ class Tree:
|
|||||||
|
|
||||||
class PassStore:
|
class PassStore:
|
||||||
"""Password store GUI app"""
|
"""Password store GUI app"""
|
||||||
|
NON_EMPTY = 1
|
||||||
|
SUCCESS = 0
|
||||||
|
ERROR = 2
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.store_path = self._get_store_path()
|
self.store_path = self._get_store_path()
|
||||||
self.data = Tree()
|
self.data = Tree()
|
||||||
@@ -363,6 +492,7 @@ class PassStore:
|
|||||||
return path
|
return path
|
||||||
|
|
||||||
def gather_pass_tree(self):
|
def gather_pass_tree(self):
|
||||||
|
self.data = Tree()
|
||||||
self._gather_pass_tree(self.data, self.store_path, '')
|
self._gather_pass_tree(self.data, self.store_path, '')
|
||||||
|
|
||||||
def get_pass(self, path):
|
def get_pass(self, path):
|
||||||
@@ -373,6 +503,38 @@ class PassStore:
|
|||||||
else:
|
else:
|
||||||
return False, proc.stderr
|
return False, proc.stderr
|
||||||
|
|
||||||
|
def new_dir(self, dirname):
|
||||||
|
path = os.path.join(self.store_path, dirname)
|
||||||
|
try:
|
||||||
|
os.mkdir(os.path.join('/root', path), mode=500)
|
||||||
|
return True, ''
|
||||||
|
except IOError as exc:
|
||||||
|
return False, str(exc)
|
||||||
|
|
||||||
|
def delete(self, item, recursively=False):
|
||||||
|
path = os.path.join(self.store_path, item)
|
||||||
|
|
||||||
|
if os.path.exists(path) and os.path.isdir(path) and recursively:
|
||||||
|
try:
|
||||||
|
shutil.rmtree(path)
|
||||||
|
except IOError as exc:
|
||||||
|
return self.ERROR, str(exc)
|
||||||
|
elif os.path.exists(path) and os.path.isdir(path):
|
||||||
|
_, files, dirs = next(os.walk(path))
|
||||||
|
if files or dirs:
|
||||||
|
return self.NON_EMPTY, ""
|
||||||
|
try:
|
||||||
|
shutil.rmtree(path)
|
||||||
|
except IOError as exc:
|
||||||
|
return self.ERROR, str(exc)
|
||||||
|
elif not os.path.exists and os.path.isfile(path + '.gpg'):
|
||||||
|
try:
|
||||||
|
os.unlink(path + '.gpg')
|
||||||
|
except IOError as exc:
|
||||||
|
return self.ERROR, str(exc)
|
||||||
|
|
||||||
|
return self.SUCCESS, ''
|
||||||
|
|
||||||
def _gather_pass_tree(self, model, root, dirname):
|
def _gather_pass_tree(self, model, root, dirname):
|
||||||
fullpath = os.path.join(root, dirname)
|
fullpath = os.path.join(root, dirname)
|
||||||
ps_path = fullpath[len(self.store_path)+1:]
|
ps_path = fullpath[len(self.store_path)+1:]
|
||||||
|
|||||||
Reference in New Issue
Block a user