28
"""Return True if user has permission to use sudo."""
29
child = pexpect.spawn('sudo -v', env={"LANG": "C"})
31
# Check for failure message.
26
gtk_version = (Gtk.get_major_version(),
27
Gtk.get_minor_version(),
28
Gtk.get_micro_version())
31
def check_gtk_version(major_version, minor_version, micro=0):
32
"""Return true if running gtk >= requested version"""
33
return gtk_version >= (major_version, minor_version, micro)
35
# Check if the LANG variable needs to be set
39
def check_dependencies(commands=[]):
40
"""Check for the existence of required commands, and sudo access"""
42
if pexpect.which("sudo") is None:
45
# Check for required commands
46
for command in commands:
47
if pexpect.which(command) is None:
50
# Check for LANG requirements
51
child = env_spawn('sudo -v', 1)
52
if child.expect([".*ssword.*", "Sorry",
54
pexpect.TIMEOUT]) == 3:
59
# Check for sudo rights
60
child = env_spawn('sudo -v', 1)
33
child.expect(["Sorry", pexpect.EOF])
62
index = child.expect([".*ssword.*", "Sorry",
63
pexpect.EOF, pexpect.TIMEOUT])
65
if index == 0 or index == 2:
66
# User in sudoers, or already admin
68
elif index == 1 or index == 3:
73
# Something else went wrong.
41
class SudoDialog(Gtk.MessageDialog):
79
def env_spawn(command, timeout):
80
"""Use pexpect.spawn, adapt for timeout and env requirements."""
84
child = pexpect.spawn(command, env)
86
child = pexpect.spawn(command)
87
child.timeout = timeout
91
class SudoDialog(Gtk.Dialog):
43
93
Creates a new SudoDialog. This is a replacement for using gksudo which
44
94
provides additional flexibility when performing sudo commands.
58
108
- REJECT: Password invalid.
59
109
- ACCEPT: Password valid.
61
def __init__(self, parent=None, icon=None, message=None, name=None,
111
def __init__(self, title=None, parent=None, icon=None, message=None,
112
name=None, retries=-1):
63
113
"""Initialize the SudoDialog."""
64
# default dialog parameters
65
message_type = Gtk.MessageType.QUESTION
66
buttons = Gtk.ButtonsType.NONE
68
114
# initialize the dialog
69
super(SudoDialog, self).__init__(transient_for=parent,
115
super(SudoDialog, self).__init__(title=title,
116
transient_for=parent,
71
destroy_with_parent=True,
72
message_type=message_type,
75
self.set_dialog_icon(icon)
118
destroy_with_parent=True)
76
120
self.connect("show", self.on_show)
79
button_box = self.get_children()[0].get_children()[1]
80
self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
122
title = _("Password Required")
123
self.set_title(title)
125
self.set_border_width(5)
128
content_area = self.get_content_area()
129
grid = Gtk.Grid.new()
130
grid.set_row_spacing(6)
131
grid.set_column_spacing(12)
132
grid.set_margin_left(5)
133
grid.set_margin_right(5)
134
content_area.add(grid)
137
self.dialog_icon = Gtk.Image.new_from_icon_name("dialog-password",
139
grid.attach(self.dialog_icon, 0, 0, 1, 2)
142
self.primary_text = Gtk.Label.new("")
143
self.primary_text.set_use_markup(True)
144
self.primary_text.set_halign(Gtk.Align.START)
145
self.secondary_text = Gtk.Label.new("")
146
self.secondary_text.set_use_markup(True)
147
self.secondary_text.set_halign(Gtk.Align.START)
148
self.secondary_text.set_margin_top(6)
149
grid.attach(self.primary_text, 1, 0, 1, 1)
150
grid.attach(self.secondary_text, 1, 1, 1, 1)
153
self.infobar = Gtk.InfoBar.new()
154
self.infobar.set_margin_top(12)
155
self.infobar.set_message_type(Gtk.MessageType.WARNING)
156
content_area = self.infobar.get_content_area()
157
infobar_icon = Gtk.Image.new_from_icon_name("dialog-warning",
159
label = Gtk.Label.new(_("Incorrect password... try again."))
160
content_area.add(infobar_icon)
161
content_area.add(label)
162
grid.attach(self.infobar, 0, 2, 2, 1)
163
content_area.show_all()
164
self.infobar.set_no_show_all(True)
167
label = Gtk.Label.new("")
168
label.set_use_markup(True)
169
label.set_markup("<b>%s</b>" % _("Password:"))
170
label.set_halign(Gtk.Align.START)
171
label.set_margin_top(12)
172
self.password_entry = Gtk.Entry()
173
self.password_entry.set_visibility(False)
174
self.password_entry.set_activates_default(True)
175
self.password_entry.set_margin_top(12)
176
grid.attach(label, 0, 3, 1, 1)
177
grid.attach(self.password_entry, 1, 3, 1, 1)
180
button = self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
181
button_box = button.get_parent()
182
button_box.set_margin_top(24)
81
183
ok_button = Gtk.Button.new_with_label(_("OK"))
82
184
ok_button.connect("clicked", self.on_ok_clicked)
83
185
ok_button.set_receives_default(True)
84
186
ok_button.set_can_default(True)
85
187
ok_button.set_sensitive(False)
86
188
self.set_default(ok_button)
87
button_box.pack_start(ok_button, False, False, 0)
189
if check_gtk_version(3, 12):
190
button_box.pack_start(ok_button, True, True, 0)
192
button_box.pack_start(ok_button, False, False, 0)
194
self.password_entry.connect("changed", self.on_password_changed,
197
self.set_dialog_icon(icon)
89
199
# add primary and secondary text
98
208
self.format_primary_text(primary_text)
99
209
self.format_secondary_text(secondary_text)
101
# Pack the content area with password-related widgets.
102
content_area = self.get_content_area()
104
# Use an alignment to move align the password widgets with the text.
105
self.password_alignment = Gtk.Alignment()
106
# Make an educated guess about how for to align.
107
left_align = Gtk.icon_size_lookup(Gtk.IconSize.DIALOG)[1] + 16
108
self.password_alignment.set_padding(12, 12, left_align, 0)
110
# Outer password box for incorrect password label and inner widgets.
111
password_outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
113
password_outer.set_orientation(Gtk.Orientation.VERTICAL)
114
# Password error label, only displayed when unsuccessful.
115
self.password_info = Gtk.Label(label="")
116
self.password_info.set_markup("<b>%s</b>" %
117
_("Incorrect password... try again."))
119
# Inner password box for Password: label and password entry.
120
password_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL,
122
password_label = Gtk.Label(label=_("Password:"))
123
self.password_entry = Gtk.Entry()
124
self.password_entry.set_visibility(False)
125
self.password_entry.set_activates_default(True)
126
self.password_entry.connect("changed", self.on_password_changed,
129
# Pack all the widgets.
130
password_box.pack_start(password_label, False, False, 0)
131
password_box.pack_start(self.password_entry, True, True, 0)
132
password_outer.pack_start(self.password_info, True, True, 0)
133
password_outer.pack_start(password_box, True, True, 0)
134
self.password_alignment.add(password_outer)
135
content_area.pack_start(self.password_alignment, True, True, 0)
136
content_area.show_all()
137
self.password_info.set_visible(False)
139
211
self.attempted_logins = 0
140
212
self.max_attempted_logins = retries
142
216
def on_password_changed(self, widget, button):
143
217
"""Set the apply button sensitivity based on password input."""
144
218
button.set_sensitive(len(widget.get_text()) > 0)
166
243
pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon,
169
image = Gtk.Image.new_from_pixbuf(pixbuf)
246
self.dialog_icon.set_from_pixbuf(pixbuf)
170
247
self.set_icon_from_file(icon)
172
249
# icon is an named icon, so load it directly to an image
173
image = Gtk.Image.new_from_icon_name(icon, icon_size)
250
self.dialog_icon.set_from_icon_name(icon, icon_size)
174
251
self.set_icon_name(icon)
176
253
# fallback on password icon
177
image = Gtk.Image.new_from_icon_name('dialog-password', icon_size)
254
self.dialog_icon.set_from_icon_name('dialog-password', icon_size)
178
255
self.set_icon_name('dialog-password')
179
# align, show, and set the image.
180
image.set_alignment(Gtk.Align.CENTER, Gtk.Align.FILL)
182
self.set_image(image)
184
257
def on_show(self, widget):
185
258
'''When the dialog is displayed, clear the password.'''
186
259
self.set_password('')
260
self.password_valid = False
188
262
def on_ok_clicked(self, widget):
193
267
If unsuccessful, try again until reaching maximum attempted logins,
194
268
then emit the response signal with REJECT.
196
top, bottom, left, right = self.password_alignment.get_padding()
197
# Password cannot be validated without sudo
198
if (not check_sudo()) or self.attempt_login():
270
if self.attempt_login():
199
271
self.password_valid = True
200
# Adjust the dialog for attactiveness.
201
self.password_alignment.set_padding(12, bottom, left, right)
202
self.password_info.hide()
203
272
self.emit("response", Gtk.ResponseType.ACCEPT)
205
274
self.password_valid = False
206
275
# Adjust the dialog for attactiveness.
207
self.password_alignment.set_padding(0, bottom, left, right)
208
self.password_info.show()
209
self.set_password('')
277
self.password_entry.grab_focus()
210
278
if self.attempted_logins == self.max_attempted_logins:
211
279
self.attempted_logins = 0
212
280
self.emit("response", Gtk.ResponseType.REJECT)
214
282
def get_password(self):
215
283
'''Return the currently entered password, or None if blank.'''
284
if not self.password_valid:
216
286
password = self.password_entry.get_text()
217
287
if password == '':