Use the new SudoDialog
This commit is contained in:
parent
beab6c6611
commit
67513c2357
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.16.1 -->
|
||||
<!-- Generated with glade 3.18.3 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.6"/>
|
||||
<!-- interface-requires mugshot_window 1.0 -->
|
||||
<requires lib="mugshot_window" version="1.0"/>
|
||||
<!-- interface-local-resource-path ../media -->
|
||||
<object class="GtkBox" id="box8">
|
||||
<property name="visible">True</property>
|
||||
|
|
|
@ -192,7 +192,7 @@ class MugshotWindow(Window):
|
|||
self.fax_entry = builder.get_object('fax')
|
||||
|
||||
# Users without sudo rights cannot change their name.
|
||||
self.set_name_editable(SudoDialog.check_sudo())
|
||||
self.set_name_editable(SudoDialog.check_dependencies(['chfn']))
|
||||
|
||||
# Stock photo browser
|
||||
self.stock_browser = builder.get_object('stock_browser')
|
||||
|
@ -311,13 +311,24 @@ class MugshotWindow(Window):
|
|||
changes."""
|
||||
logger.debug('Applying changes...')
|
||||
if self.get_chfn_details_updated():
|
||||
if not self.save_chfn_details():
|
||||
success, response = self.save_chfn_details()
|
||||
if not success:
|
||||
# Password was incorrect, complain.
|
||||
primary = _("Authentication Failed")
|
||||
if response in [Gtk.ResponseType.NONE,
|
||||
Gtk.ResponseType.CANCEL,
|
||||
Gtk.ResponseType.DELETE_EVENT]:
|
||||
msg_type = Gtk.MessageType.WARNING
|
||||
primary = _("Authentication cancelled.")
|
||||
elif response == Gtk.ResponseType.REJECT:
|
||||
msg_type = Gtk.MessageType.WARNING
|
||||
primary = _("Authentication failed.")
|
||||
else:
|
||||
msg_type = Gtk.MessageType.ERROR
|
||||
primary = _("An error occurred when saving changes.")
|
||||
|
||||
secondary = _("User details were not updated.")
|
||||
dialog = Gtk.MessageDialog(transient_for=self, flags=0,
|
||||
message_type=
|
||||
Gtk.MessageType.WARNING,
|
||||
message_type=msg_type,
|
||||
buttons=Gtk.ButtonsType.OK,
|
||||
text=primary)
|
||||
dialog.format_secondary_text(secondary)
|
||||
|
@ -555,8 +566,7 @@ class MugshotWindow(Window):
|
|||
"""Handle password prompts from the interactive chfn commands."""
|
||||
# Force the C language for guaranteed english strings in the script.
|
||||
logger.debug('Executing: %s' % command)
|
||||
child = pexpect.spawn(command, env={"LANG": "C"})
|
||||
child.timeout = 5
|
||||
child = SudoDialog.env_spawn(command, 5)
|
||||
child.write_to_stdout = True
|
||||
try:
|
||||
child.expect([".*ssword.*", pexpect.EOF])
|
||||
|
@ -582,13 +592,13 @@ class MugshotWindow(Window):
|
|||
sudo_dialog.format_secondary_text(_("This is a security measure to "
|
||||
"prevent unwanted updates\n"
|
||||
"to your personal information."))
|
||||
sudo_dialog.run()
|
||||
response = sudo_dialog.run()
|
||||
sudo_dialog.hide()
|
||||
password = sudo_dialog.get_password()
|
||||
sudo_dialog.destroy()
|
||||
|
||||
if not password:
|
||||
return False
|
||||
return (False, response)
|
||||
|
||||
sudo = which('sudo')
|
||||
chfn = which('chfn')
|
||||
|
@ -606,7 +616,7 @@ class MugshotWindow(Window):
|
|||
home_phone = 'none'
|
||||
|
||||
# Full name can only be modified by root. Try using sudo to modify.
|
||||
if SudoDialog.check_sudo():
|
||||
if SudoDialog.check_dependencies(['chfn']):
|
||||
logger.debug('Updating Full Name...')
|
||||
command = "%s %s -f \"%s\" %s" % (sudo, chfn, full_name, username)
|
||||
if self.process_terminal_password(command, password):
|
||||
|
@ -629,7 +639,7 @@ class MugshotWindow(Window):
|
|||
else:
|
||||
success = False
|
||||
|
||||
return success
|
||||
return (success, response)
|
||||
|
||||
# = LibreOffice ========================================================= #
|
||||
def get_libreoffice_details_updated(self):
|
||||
|
|
|
@ -23,22 +23,70 @@ from locale import gettext as _
|
|||
|
||||
import pexpect
|
||||
|
||||
gtk_version = (Gtk.get_major_version(),
|
||||
Gtk.get_minor_version(),
|
||||
Gtk.get_micro_version())
|
||||
|
||||
def check_sudo():
|
||||
"""Return True if user has permission to use sudo."""
|
||||
child = pexpect.spawn('sudo -v', env={"LANG": "C"})
|
||||
child.timeout = 1
|
||||
# Check for failure message.
|
||||
try:
|
||||
child.expect(["Sorry", pexpect.EOF])
|
||||
child.close()
|
||||
|
||||
def check_gtk_version(major_version, minor_version, micro=0):
|
||||
"""Return true if running gtk >= requested version"""
|
||||
return gtk_version >= (major_version, minor_version, micro)
|
||||
|
||||
# Check if the LANG variable needs to be set
|
||||
use_env = False
|
||||
|
||||
|
||||
def check_dependencies(commands=[]):
|
||||
"""Check for the existence of required commands, and sudo access"""
|
||||
# Check for sudo
|
||||
if pexpect.which("sudo") is None:
|
||||
return False
|
||||
except:
|
||||
|
||||
# Check for required commands
|
||||
for command in commands:
|
||||
if pexpect.which(command) is None:
|
||||
return False
|
||||
|
||||
# Check for LANG requirements
|
||||
child = env_spawn('sudo -v', 1)
|
||||
if child.expect([".*ssword.*", "Sorry",
|
||||
pexpect.EOF,
|
||||
pexpect.TIMEOUT]) == 3:
|
||||
global use_env
|
||||
use_env = True
|
||||
child.close()
|
||||
|
||||
# Check for sudo rights
|
||||
child = env_spawn('sudo -v', 1)
|
||||
try:
|
||||
index = child.expect([".*ssword.*", "Sorry",
|
||||
pexpect.EOF, pexpect.TIMEOUT])
|
||||
child.close()
|
||||
return True
|
||||
if index == 0 or index == 2:
|
||||
# User in sudoers, or already admin
|
||||
return True
|
||||
elif index == 1 or index == 3:
|
||||
# User not in sudoers
|
||||
return False
|
||||
|
||||
except:
|
||||
# Something else went wrong.
|
||||
child.close()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class SudoDialog(Gtk.MessageDialog):
|
||||
def env_spawn(command, timeout):
|
||||
"""Use pexpect.spawn, adapt for timeout and env requirements."""
|
||||
if use_env:
|
||||
child = pexpect.spawn(command, env={"LANG": "C"})
|
||||
else:
|
||||
child = pexpect.spawn(command)
|
||||
child.timeout = timeout
|
||||
return child
|
||||
|
||||
|
||||
class SudoDialog(Gtk.Dialog):
|
||||
'''
|
||||
Creates a new SudoDialog. This is a replacement for using gksudo which
|
||||
provides additional flexibility when performing sudo commands.
|
||||
|
@ -58,33 +106,93 @@ class SudoDialog(Gtk.MessageDialog):
|
|||
- REJECT: Password invalid.
|
||||
- ACCEPT: Password valid.
|
||||
'''
|
||||
def __init__(self, parent=None, icon=None, message=None, name=None,
|
||||
retries=-1):
|
||||
def __init__(self, title=None, parent=None, icon=None, message=None,
|
||||
name=None, retries=-1):
|
||||
"""Initialize the SudoDialog."""
|
||||
# default dialog parameters
|
||||
message_type = Gtk.MessageType.QUESTION
|
||||
buttons = Gtk.ButtonsType.NONE
|
||||
|
||||
# initialize the dialog
|
||||
super(SudoDialog, self).__init__(transient_for=parent,
|
||||
super(SudoDialog, self).__init__(title=title,
|
||||
transient_for=parent,
|
||||
modal=True,
|
||||
destroy_with_parent=True,
|
||||
message_type=message_type,
|
||||
buttons=buttons,
|
||||
text='')
|
||||
self.set_dialog_icon(icon)
|
||||
destroy_with_parent=True)
|
||||
#
|
||||
self.connect("show", self.on_show)
|
||||
if title is None:
|
||||
title = _("Password Required")
|
||||
self.set_title(title)
|
||||
|
||||
# add buttons
|
||||
button_box = self.get_children()[0].get_children()[1]
|
||||
self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
|
||||
self.set_border_width(5)
|
||||
|
||||
# Content Area
|
||||
content_area = self.get_content_area()
|
||||
grid = Gtk.Grid.new()
|
||||
grid.set_row_spacing(6)
|
||||
grid.set_column_spacing(12)
|
||||
grid.set_margin_left(5)
|
||||
grid.set_margin_right(5)
|
||||
content_area.add(grid)
|
||||
|
||||
# Icon
|
||||
self.dialog_icon = Gtk.Image.new_from_icon_name("dialog-password",
|
||||
Gtk.IconSize.DIALOG)
|
||||
grid.attach(self.dialog_icon, 0, 0, 1, 2)
|
||||
|
||||
# Text
|
||||
self.primary_text = Gtk.Label.new("")
|
||||
self.primary_text.set_use_markup(True)
|
||||
self.primary_text.set_halign(Gtk.Align.START)
|
||||
self.secondary_text = Gtk.Label.new("")
|
||||
self.secondary_text.set_use_markup(True)
|
||||
self.secondary_text.set_halign(Gtk.Align.START)
|
||||
self.secondary_text.set_margin_top(6)
|
||||
grid.attach(self.primary_text, 1, 0, 1, 1)
|
||||
grid.attach(self.secondary_text, 1, 1, 1, 1)
|
||||
|
||||
# Infobar
|
||||
self.infobar = Gtk.InfoBar.new()
|
||||
self.infobar.set_margin_top(12)
|
||||
self.infobar.set_message_type(Gtk.MessageType.WARNING)
|
||||
content_area = self.infobar.get_content_area()
|
||||
infobar_icon = Gtk.Image.new_from_icon_name("dialog-warning",
|
||||
Gtk.IconSize.BUTTON)
|
||||
label = Gtk.Label.new(_("Incorrect password... try again."))
|
||||
content_area.add(infobar_icon)
|
||||
content_area.add(label)
|
||||
grid.attach(self.infobar, 0, 2, 2, 1)
|
||||
content_area.show_all()
|
||||
self.infobar.set_no_show_all(True)
|
||||
|
||||
# Password
|
||||
label = Gtk.Label.new("")
|
||||
label.set_use_markup(True)
|
||||
label.set_markup("<b>%s</b>" % _("Password:"))
|
||||
label.set_halign(Gtk.Align.START)
|
||||
label.set_margin_top(12)
|
||||
self.password_entry = Gtk.Entry()
|
||||
self.password_entry.set_visibility(False)
|
||||
self.password_entry.set_activates_default(True)
|
||||
self.password_entry.set_margin_top(12)
|
||||
grid.attach(label, 0, 3, 1, 1)
|
||||
grid.attach(self.password_entry, 1, 3, 1, 1)
|
||||
|
||||
# Buttons
|
||||
button = self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
|
||||
button_box = button.get_parent()
|
||||
button_box.set_margin_top(24)
|
||||
ok_button = Gtk.Button.new_with_label(_("OK"))
|
||||
ok_button.connect("clicked", self.on_ok_clicked)
|
||||
ok_button.set_receives_default(True)
|
||||
ok_button.set_can_default(True)
|
||||
ok_button.set_sensitive(False)
|
||||
self.set_default(ok_button)
|
||||
button_box.pack_start(ok_button, False, False, 0)
|
||||
if check_gtk_version(3, 12):
|
||||
button_box.pack_start(ok_button, True, True, 0)
|
||||
else:
|
||||
button_box.pack_start(ok_button, False, False, 0)
|
||||
|
||||
self.password_entry.connect("changed", self.on_password_changed,
|
||||
ok_button)
|
||||
|
||||
self.set_dialog_icon(icon)
|
||||
|
||||
# add primary and secondary text
|
||||
if message:
|
||||
|
@ -98,47 +206,11 @@ class SudoDialog(Gtk.MessageDialog):
|
|||
self.format_primary_text(primary_text)
|
||||
self.format_secondary_text(secondary_text)
|
||||
|
||||
# Pack the content area with password-related widgets.
|
||||
content_area = self.get_content_area()
|
||||
|
||||
# Use an alignment to move align the password widgets with the text.
|
||||
self.password_alignment = Gtk.Alignment()
|
||||
# Make an educated guess about how for to align.
|
||||
left_align = Gtk.icon_size_lookup(Gtk.IconSize.DIALOG)[1] + 16
|
||||
self.password_alignment.set_padding(12, 12, left_align, 0)
|
||||
|
||||
# Outer password box for incorrect password label and inner widgets.
|
||||
password_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
|
||||
spacing=12)
|
||||
password_outer.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
# Password error label, only displayed when unsuccessful.
|
||||
self.password_info = Gtk.Label(label="")
|
||||
self.password_info.set_markup("<b>%s</b>" %
|
||||
_("Incorrect password... try again."))
|
||||
|
||||
# Inner password box for Password: label and password entry.
|
||||
password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
|
||||
spacing=12)
|
||||
password_label = Gtk.Label(label=_("Password:"))
|
||||
self.password_entry = Gtk.Entry()
|
||||
self.password_entry.set_visibility(False)
|
||||
self.password_entry.set_activates_default(True)
|
||||
self.password_entry.connect("changed", self.on_password_changed,
|
||||
ok_button)
|
||||
|
||||
# Pack all the widgets.
|
||||
password_box.pack_start(password_label, False, False, 0)
|
||||
password_box.pack_start(self.password_entry, True, True, 0)
|
||||
password_outer.pack_start(self.password_info, True, True, 0)
|
||||
password_outer.pack_start(password_box, True, True, 0)
|
||||
self.password_alignment.add(password_outer)
|
||||
content_area.pack_start(self.password_alignment, True, True, 0)
|
||||
content_area.show_all()
|
||||
self.password_info.set_visible(False)
|
||||
|
||||
self.attempted_logins = 0
|
||||
self.max_attempted_logins = retries
|
||||
|
||||
self.show_all()
|
||||
|
||||
def on_password_changed(self, widget, button):
|
||||
"""Set the apply button sensitivity based on password input."""
|
||||
button.set_sensitive(len(widget.get_text()) > 0)
|
||||
|
@ -146,11 +218,14 @@ class SudoDialog(Gtk.MessageDialog):
|
|||
def format_primary_text(self, message_format):
|
||||
'''
|
||||
Format the primary text widget.
|
||||
|
||||
API extension to match with format_secondary_text.
|
||||
'''
|
||||
label = self.get_message_area().get_children()[0]
|
||||
label.set_text(message_format)
|
||||
self.primary_text.set_markup("<big><b>%s</b></big>" % message_format)
|
||||
|
||||
def format_secondary_text(self, message_format):
|
||||
'''
|
||||
Format the secondary text widget.
|
||||
'''
|
||||
self.secondary_text.set_markup(message_format)
|
||||
|
||||
def set_dialog_icon(self, icon=None):
|
||||
'''
|
||||
|
@ -166,24 +241,21 @@ class SudoDialog(Gtk.MessageDialog):
|
|||
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon,
|
||||
icon_size,
|
||||
icon_size)
|
||||
image = Gtk.Image.new_from_pixbuf(pixbuf)
|
||||
self.dialog_icon.set_from_pixbuf(pixbuf)
|
||||
self.set_icon_from_file(icon)
|
||||
else:
|
||||
# icon is an named icon, so load it directly to an image
|
||||
image = Gtk.Image.new_from_icon_name(icon, icon_size)
|
||||
self.dialog_icon.set_from_icon_name(icon, icon_size)
|
||||
self.set_icon_name(icon)
|
||||
else:
|
||||
# fallback on password icon
|
||||
image = Gtk.Image.new_from_icon_name('dialog-password', icon_size)
|
||||
self.dialog_icon.set_from_icon_name('dialog-password', icon_size)
|
||||
self.set_icon_name('dialog-password')
|
||||
# align, show, and set the image.
|
||||
image.set_alignment(Gtk.Align.CENTER, Gtk.Align.FILL)
|
||||
image.show()
|
||||
self.set_image(image)
|
||||
|
||||
def on_show(self, widget):
|
||||
'''When the dialog is displayed, clear the password.'''
|
||||
self.set_password('')
|
||||
self.password_valid = False
|
||||
|
||||
def on_ok_clicked(self, widget):
|
||||
'''
|
||||
|
@ -193,26 +265,22 @@ class SudoDialog(Gtk.MessageDialog):
|
|||
If unsuccessful, try again until reaching maximum attempted logins,
|
||||
then emit the response signal with REJECT.
|
||||
'''
|
||||
top, bottom, left, right = self.password_alignment.get_padding()
|
||||
# Password cannot be validated without sudo
|
||||
if (not check_sudo()) or self.attempt_login():
|
||||
if self.attempt_login():
|
||||
self.password_valid = True
|
||||
# Adjust the dialog for attactiveness.
|
||||
self.password_alignment.set_padding(12, bottom, left, right)
|
||||
self.password_info.hide()
|
||||
self.emit("response", Gtk.ResponseType.ACCEPT)
|
||||
else:
|
||||
self.password_valid = False
|
||||
# Adjust the dialog for attactiveness.
|
||||
self.password_alignment.set_padding(0, bottom, left, right)
|
||||
self.password_info.show()
|
||||
self.set_password('')
|
||||
self.infobar.show()
|
||||
self.password_entry.grab_focus()
|
||||
if self.attempted_logins == self.max_attempted_logins:
|
||||
self.attempted_logins = 0
|
||||
self.emit("response", Gtk.ResponseType.REJECT)
|
||||
|
||||
def get_password(self):
|
||||
'''Return the currently entered password, or None if blank.'''
|
||||
if not self.password_valid:
|
||||
return None
|
||||
password = self.password_entry.get_text()
|
||||
if password == '':
|
||||
return None
|
||||
|
@ -232,12 +300,11 @@ class SudoDialog(Gtk.MessageDialog):
|
|||
Return True if successful.
|
||||
'''
|
||||
# Set the pexpect variables and spawn the process.
|
||||
child = pexpect.spawn('sudo /bin/true', env={"LANG": "C"})
|
||||
child.timeout = 1
|
||||
child = env_spawn('sudo /bin/true', 1)
|
||||
try:
|
||||
# Check for password prompt or program exit.
|
||||
child.expect([".*ssword.*", pexpect.EOF])
|
||||
child.sendline(self.get_password())
|
||||
child.sendline(self.password_entry.get_text())
|
||||
child.expect(pexpect.EOF)
|
||||
except pexpect.TIMEOUT:
|
||||
# If we timeout, that means the password was unsuccessful.
|
||||
|
|
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2014-08-24 10:25-0400\n"
|
||||
"POT-Creation-Date: 2014-08-24 11:44-0400\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"
|
||||
|
@ -61,7 +61,7 @@ msgstr ""
|
|||
msgid "Browse…"
|
||||
msgstr ""
|
||||
|
||||
#: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:579
|
||||
#: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:589
|
||||
msgid "Mugshot"
|
||||
msgstr ""
|
||||
|
||||
|
@ -130,70 +130,82 @@ msgstr ""
|
|||
msgid "Retry"
|
||||
msgstr ""
|
||||
|
||||
#. Password was incorrect, complain.
|
||||
#: ../mugshot/MugshotWindow.py:316
|
||||
msgid "Authentication Failed"
|
||||
#: ../mugshot/MugshotWindow.py:321
|
||||
msgid "Authentication cancelled."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:317
|
||||
#: ../mugshot/MugshotWindow.py:324
|
||||
msgid "Authentication failed."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:327
|
||||
msgid "An error occurred when saving changes."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:329
|
||||
msgid "User details were not updated."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:488
|
||||
#: ../mugshot/MugshotWindow.py:499
|
||||
msgid "Update Pidgin buddy icon?"
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:489
|
||||
#: ../mugshot/MugshotWindow.py:500
|
||||
msgid "Would you also like to update your Pidgin buddy icon?"
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:580
|
||||
#: ../mugshot/MugshotWindow.py:590
|
||||
msgid "Enter your password to change user details."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:582
|
||||
#: ../mugshot/MugshotWindow.py:592
|
||||
msgid ""
|
||||
"This is a security measure to prevent unwanted updates\n"
|
||||
"to your personal information."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:787
|
||||
#: ../mugshot/MugshotWindow.py:797
|
||||
msgid "Update LibreOffice user details?"
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot/MugshotWindow.py:788
|
||||
#: ../mugshot/MugshotWindow.py:798
|
||||
msgid "Would you also like to update your user details in LibreOffice?"
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:80
|
||||
#: ../mugshot_lib/SudoDialog.py:120
|
||||
msgid "Password Required"
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:157
|
||||
msgid "Incorrect password... try again."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:167
|
||||
msgid "Password:"
|
||||
msgstr ""
|
||||
|
||||
#. Buttons
|
||||
#: ../mugshot_lib/SudoDialog.py:178
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:81
|
||||
#: ../mugshot_lib/SudoDialog.py:181
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:94
|
||||
#: ../mugshot_lib/SudoDialog.py:202
|
||||
msgid ""
|
||||
"Enter your password to\n"
|
||||
"perform administrative tasks."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:96
|
||||
#: ../mugshot_lib/SudoDialog.py:204
|
||||
#, python-format
|
||||
msgid ""
|
||||
"The application '%s' lets you\n"
|
||||
"modify essential parts of your system."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:117
|
||||
msgid "Incorrect password... try again."
|
||||
msgstr ""
|
||||
|
||||
#: ../mugshot_lib/SudoDialog.py:122
|
||||
msgid "Password:"
|
||||
msgstr ""
|
||||
|
||||
#: ../data/appdata/mugshot.appdata.xml.in.h:1
|
||||
msgid "Lightweight user configuration"
|
||||
msgstr ""
|
||||
|
|
Loading…
Reference in New Issue