Add basic authentication -- your connectionmanager config will be cleared and this will likely break other UIs
This commit is contained in:
parent
cc7a685397
commit
37c3b4334c
|
@ -58,7 +58,6 @@ class _ConfigManager:
|
|||
self.config_directory = directory
|
||||
|
||||
def get_config_dir(self):
|
||||
log.debug("get_config_dir: %s", self.config_directory)
|
||||
return self.config_directory
|
||||
|
||||
def close(self, config):
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
#
|
||||
# authmanager.py
|
||||
#
|
||||
# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import os.path
|
||||
import random
|
||||
import stat
|
||||
|
||||
import deluge.component as component
|
||||
import deluge.configmanager as configmanager
|
||||
|
||||
class AuthManager(component.Component):
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "AuthManager")
|
||||
self.auth = {}
|
||||
|
||||
def start(self):
|
||||
self.__load_auth_file()
|
||||
|
||||
def stop(self):
|
||||
self.auth = {}
|
||||
|
||||
def shutdown(self):
|
||||
pass
|
||||
|
||||
def authorize(self, username, password):
|
||||
"""
|
||||
Authorizes users based on username and password
|
||||
|
||||
:param username: str, username
|
||||
:param password: str, password
|
||||
:returns: True or False
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
|
||||
if username not in self.auth:
|
||||
return False
|
||||
|
||||
if self.auth[username] == password:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def __load_auth_file(self):
|
||||
auth_file = configmanager.get_config_dir("auth")
|
||||
# Check for auth file and create if necessary
|
||||
if not os.path.exists(auth_file):
|
||||
# We create a 'localclient' account with a random password
|
||||
try:
|
||||
from hashlib import sha1 as sha_hash
|
||||
except ImportError:
|
||||
from sha import new as sha_hash
|
||||
open(auth_file, "w").write("localclient:" + sha_hash(str(random.random())).hexdigest())
|
||||
# Change the permissions on the file so only this user can read/write it
|
||||
os.chmod(auth_file, stat.S_IREAD | stat.S_IWRITE)
|
||||
|
||||
# Load the auth file into a dictionary: {username: password, ...}
|
||||
f = open(auth_file, "r")
|
||||
for line in f:
|
||||
if line.startswith("#"):
|
||||
# This is a comment line
|
||||
continue
|
||||
username, password = line.split(":")
|
||||
self.auth[username] = password
|
|
@ -53,6 +53,9 @@ from deluge.core.signalmanager import SignalManager
|
|||
from deluge.core.filtermanager import FilterManager
|
||||
from deluge.core.preferencesmanager import PreferencesManager
|
||||
from deluge.core.autoadd import AutoAdd
|
||||
from deluge.core.authmanager import AuthManager
|
||||
from deluge.core.rpcserver import BasicAuthXMLRPCRequestHandler
|
||||
|
||||
from deluge.log import LOG as log
|
||||
|
||||
STATUS_KEYS = ['active_time', 'compact', 'distributed_copies', 'download_payload_rate', 'eta',
|
||||
|
@ -91,7 +94,9 @@ class Core(
|
|||
try:
|
||||
log.info("Starting XMLRPC server on port %s", port)
|
||||
SimpleXMLRPCServer.SimpleXMLRPCServer.__init__(
|
||||
self, (hostname, port), logRequests=False, allow_none=True)
|
||||
self, (hostname, port),
|
||||
requestHandler=BasicAuthXMLRPCRequestHandler,
|
||||
logRequests=False, allow_none=True)
|
||||
except:
|
||||
log.info("Daemon already running or port not available..")
|
||||
sys.exit(0)
|
||||
|
@ -202,6 +207,9 @@ class Core(
|
|||
# Create the AutoAdd component
|
||||
self.autoadd = AutoAdd()
|
||||
|
||||
# Start the AuthManager
|
||||
self.authmanager = AuthManager()
|
||||
|
||||
# New release check information
|
||||
self.new_release = None
|
||||
|
||||
|
@ -530,7 +538,7 @@ class Core(
|
|||
return None
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def export_get_config_values(self, keys):
|
||||
"""Get the config values for the entered keys"""
|
||||
config = {}
|
||||
|
@ -540,7 +548,7 @@ class Core(
|
|||
except KeyError:
|
||||
pass
|
||||
return config
|
||||
|
||||
|
||||
|
||||
def export_set_config(self, config):
|
||||
"""Set the config with values from dictionary"""
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
import gobject
|
||||
|
||||
from deluge.SimpleXMLRPCServer import SimpleXMLRPCServer
|
||||
from deluge.SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
|
||||
from SocketServer import ThreadingMixIn
|
||||
from base64 import decodestring, encodestring
|
||||
|
||||
|
@ -99,10 +100,15 @@ class XMLRPCServer(ThreadingMixIn, SimpleXMLRPCServer):
|
|||
|
||||
class BasicAuthXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
|
||||
def do_POST(self):
|
||||
auth = self.headers['authorization']
|
||||
auth = auth.replace("Basic ","")
|
||||
decoded_auth = decodestring(auth)
|
||||
# Check authentication here
|
||||
# if cannot authenticate, end the connection or
|
||||
# otherwise call original
|
||||
return SimpleXMLRPCRequestHandler.do_POST(self)
|
||||
if "authorization" in self.headers:
|
||||
auth = self.headers['authorization']
|
||||
auth = auth.replace("Basic ","")
|
||||
decoded_auth = decodestring(auth)
|
||||
# Check authentication here
|
||||
if component.get("AuthManager").authorize(*decoded_auth.split(":")):
|
||||
# User authorized, call the real do_POST now
|
||||
return SimpleXMLRPCRequestHandler.do_POST(self)
|
||||
|
||||
# if cannot authenticate, end the connection
|
||||
self.send_response(401)
|
||||
self.end_headers()
|
||||
|
|
|
@ -64,7 +64,7 @@ class Transport(xmlrpclib.Transport):
|
|||
errcode, errmsg, headers = h.getreply()
|
||||
|
||||
if errcode != 200:
|
||||
raise ProtocolError(
|
||||
raise xmlrpclib.ProtocolError(
|
||||
host + handler,
|
||||
errcode, errmsg,
|
||||
headers
|
||||
|
|
|
@ -31,6 +31,7 @@ import os
|
|||
import subprocess
|
||||
import time
|
||||
import threading
|
||||
import urlparse
|
||||
|
||||
import deluge.component as component
|
||||
import deluge.xmlrpclib as xmlrpclib
|
||||
|
@ -45,7 +46,7 @@ DEFAULT_HOST = DEFAULT_URI.split(":")[1][2:]
|
|||
DEFAULT_PORT = DEFAULT_URI.split(":")[-1]
|
||||
|
||||
DEFAULT_CONFIG = {
|
||||
"hosts": [DEFAULT_HOST + ":" + DEFAULT_PORT]
|
||||
"hosts": [DEFAULT_URI]
|
||||
}
|
||||
|
||||
HOSTLIST_COL_PIXBUF = 0
|
||||
|
@ -58,9 +59,31 @@ HOSTLIST_STATUS = [
|
|||
"Connected"
|
||||
]
|
||||
|
||||
HOSTLIST_PIXBUFS = [
|
||||
# This is populated in ConnectionManager.__init__
|
||||
]
|
||||
|
||||
if deluge.common.windows_check():
|
||||
import win32api
|
||||
|
||||
|
||||
def cell_render_host(column, cell, model, row, data):
|
||||
host = model[row][data]
|
||||
u = urlparse.urlsplit(host)
|
||||
if not u.hostname:
|
||||
host = "http://" + host
|
||||
u = urlparse.urlsplit(host)
|
||||
if u.username:
|
||||
text = u.username + ":*@" + u.hostname + ":" + str(u.port)
|
||||
else:
|
||||
text = u.hostname + ":" + str(u.port)
|
||||
|
||||
cell.set_property('text', text)
|
||||
|
||||
def cell_render_pixbuf(column, cell, model, row, data):
|
||||
state = model[row][data]
|
||||
cell.set_property('pixbuf', HOSTLIST_PIXBUFS[state])
|
||||
|
||||
class ConnectionManager(component.Component):
|
||||
def __init__(self):
|
||||
component.Component.__init__(self, "ConnectionManager")
|
||||
|
@ -71,15 +94,32 @@ class ConnectionManager(component.Component):
|
|||
|
||||
self.window = component.get("MainWindow")
|
||||
self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG)
|
||||
|
||||
# Test to see if it's an old config
|
||||
if DEFAULT_HOST + ":" + DEFAULT_PORT in self.config.config:
|
||||
# This is likely an older 1.0 config, so lets mv it and start fresh
|
||||
self.config = None
|
||||
hostlist_conf = deluge.configmanager.get_config_dir("hostlist.conf")
|
||||
shutil.move(hostlist_conf, hostlist_conf + ".1.0")
|
||||
self.config = ConfigManager("hostlist.conf", DEFAULT_CONFIG)
|
||||
# Change the permissions on the file so only this user can read/write it
|
||||
os.chmod(hostlist_conf, stat.S_IREAD | stat.S_IWRITE)
|
||||
|
||||
self.gtkui_config = ConfigManager("gtkui.conf")
|
||||
self.connection_manager = self.glade.get_widget("connection_manager")
|
||||
# Make the Connection Manager window a transient for the main window.
|
||||
self.connection_manager.set_transient_for(self.window.window)
|
||||
|
||||
# Create status pixbufs
|
||||
for stock_id in (gtk.STOCK_NO, gtk.STOCK_YES, gtk.STOCK_CONNECT):
|
||||
HOSTLIST_PIXBUFS.append(self.connection_manager.render_icon(stock_id, gtk.ICON_SIZE_MENU))
|
||||
|
||||
self.hostlist = self.glade.get_widget("hostlist")
|
||||
self.connection_manager.set_icon(common.get_logo(32))
|
||||
|
||||
self.glade.get_widget("image1").set_from_pixbuf(common.get_logo(32))
|
||||
|
||||
# connection status pixbuf, hostname:port, status
|
||||
self.liststore = gtk.ListStore(gtk.gdk.Pixbuf, str, int)
|
||||
|
||||
# Holds the online status of hosts
|
||||
|
@ -95,9 +135,11 @@ class ConnectionManager(component.Component):
|
|||
render = gtk.CellRendererPixbuf()
|
||||
column = gtk.TreeViewColumn(
|
||||
"Status", render, pixbuf=HOSTLIST_COL_PIXBUF)
|
||||
column.set_cell_data_func(render, cell_render_pixbuf, 2)
|
||||
self.hostlist.append_column(column)
|
||||
render = gtk.CellRendererText()
|
||||
column = gtk.TreeViewColumn("Host", render, text=HOSTLIST_COL_URI)
|
||||
column.set_cell_data_func(render, cell_render_host, 1)
|
||||
self.hostlist.append_column(column)
|
||||
|
||||
self.glade.signal_autoconnect({
|
||||
|
@ -138,28 +180,50 @@ class ConnectionManager(component.Component):
|
|||
if self.gtkui_config["autoconnect"] and \
|
||||
self.gtkui_config["autoconnect_host_uri"] != None:
|
||||
uri = self.gtkui_config["autoconnect_host_uri"]
|
||||
# Make sure the uri is proper
|
||||
if uri[:7] != "http://":
|
||||
uri = "http://" + uri
|
||||
if self.test_online_status(uri):
|
||||
# Host is online, so lets connect
|
||||
client.set_core_uri(uri)
|
||||
self.hide()
|
||||
elif self.gtkui_config["autostart_localhost"]:
|
||||
# Check to see if we are trying to connect to a localhost
|
||||
if uri[7:].split(":")[0] == "localhost" or \
|
||||
uri[7:].split(":")[0] == "127.0.0.1":
|
||||
u = urlparse.urlsplit(uri)
|
||||
if u.hostname == "localhost" or u.hostname == "127.0.0.1":
|
||||
# This is a localhost, so lets try to start it
|
||||
port = uri[7:].split(":")[1]
|
||||
# First add it to the list
|
||||
self.add_host("localhost", port)
|
||||
self.start_localhost(port)
|
||||
# We need to wait for the host to start before connecting
|
||||
while not self.test_online_status(uri):
|
||||
self.add_host("localhost", u.port)
|
||||
self.start_localhost(u.port)
|
||||
# Get the localhost uri with authentication details
|
||||
auth_uri = None
|
||||
while not auth_uri:
|
||||
# We need to keep trying because the daemon may not have been started yet
|
||||
# and the 'auth' file may not have been created
|
||||
auth_uri = self.get_localhost_auth_uri(uri)
|
||||
time.sleep(0.01)
|
||||
client.set_core_uri(uri)
|
||||
|
||||
# We need to wait for the host to start before connecting
|
||||
while not self.test_online_status(auth_uri):
|
||||
time.sleep(0.01)
|
||||
client.set_core_uri(auth_uri)
|
||||
self.hide()
|
||||
|
||||
def get_localhost_auth_uri(self, uri):
|
||||
"""
|
||||
Grabs the localclient auth line from the 'auth' file and creates a localhost uri
|
||||
|
||||
:param uri: the uri to add the authentication info to
|
||||
:returns: a localhost uri containing authentication information or None if the information is not available
|
||||
"""
|
||||
auth_file = deluge.configmanager.get_config_dir("auth")
|
||||
if os.path.exists(auth_file):
|
||||
u = urlparse.urlsplit(uri)
|
||||
for line in open(auth_file):
|
||||
username, password = line.split(":")
|
||||
if username == "localclient":
|
||||
# We use '127.0.0.1' in place of 'localhost' just incase this isn't defined properly
|
||||
hostname = u.hostname.replace("localhost", "127.0.0.1")
|
||||
return u.scheme + "://" + username + ":" + password + "@" + hostname + ":" + str(u.port)
|
||||
return None
|
||||
|
||||
def start(self):
|
||||
if self.gtkui_config["autoconnect"]:
|
||||
# We need to update the autoconnect_host_uri on connection to host
|
||||
|
@ -195,30 +259,24 @@ class ConnectionManager(component.Component):
|
|||
"""Updates the host status"""
|
||||
def update_row(model=None, path=None, row=None, columns=None):
|
||||
uri = model.get_value(row, HOSTLIST_COL_URI)
|
||||
uri = "http://" + uri
|
||||
threading.Thread(target=self.test_online_status, args=(uri,)).start()
|
||||
try:
|
||||
online = self.online_status[uri]
|
||||
except:
|
||||
online = False
|
||||
|
||||
# Update hosts status
|
||||
if online:
|
||||
image = gtk.STOCK_YES
|
||||
online = HOSTLIST_STATUS.index("Online")
|
||||
else:
|
||||
image = gtk.STOCK_NO
|
||||
online = HOSTLIST_STATUS.index("Offline")
|
||||
|
||||
if urlparse.urlsplit(uri).hostname == "localhost" or urlparse.urlsplit(uri).hostname == "127.0.0.1":
|
||||
uri = self.get_localhost_auth_uri(uri)
|
||||
|
||||
if uri == current_uri:
|
||||
# We are connected to this host, so lets display the connected
|
||||
# icon.
|
||||
image = gtk.STOCK_CONNECT
|
||||
online = HOSTLIST_STATUS.index("Connected")
|
||||
|
||||
pixbuf = self.connection_manager.render_icon(
|
||||
image, gtk.ICON_SIZE_MENU)
|
||||
|
||||
model.set_value(row, HOSTLIST_COL_PIXBUF, pixbuf)
|
||||
model.set_value(row, HOSTLIST_COL_STATUS, online)
|
||||
|
||||
current_uri = client.get_core_uri()
|
||||
|
@ -260,12 +318,10 @@ class ConnectionManager(component.Component):
|
|||
|
||||
# Check to see if a localhost is selected
|
||||
localhost = False
|
||||
if uri.split(":")[0] == "localhost" or uri.split(":")[0] == "127.0.0.1":
|
||||
u = urlparse.urlsplit(uri)
|
||||
if u.hostname == "localhost" or u.hostname == "127.0.0.1":
|
||||
localhost = True
|
||||
|
||||
# Make actual URI string
|
||||
uri = "http://" + uri
|
||||
|
||||
# Make sure buttons are sensitive at start
|
||||
self.glade.get_widget("button_startdaemon").set_sensitive(True)
|
||||
self.glade.get_widget("button_connect").set_sensitive(True)
|
||||
|
@ -321,7 +377,11 @@ class ConnectionManager(component.Component):
|
|||
online = True
|
||||
host = None
|
||||
try:
|
||||
host = xmlrpclib.ServerProxy(uri.replace("localhost", "127.0.0.1"))
|
||||
u = urlparse.urlsplit(uri)
|
||||
if u.hostname == "localhost" or u.hostname == "127.0.0.1":
|
||||
host = xmlrpclib.ServerProxy(self.get_localhost_auth_uri(uri))
|
||||
else:
|
||||
host = xmlrpclib.ServerProxy(uri)
|
||||
host.ping()
|
||||
except socket.error:
|
||||
online = False
|
||||
|
@ -341,18 +401,32 @@ class ConnectionManager(component.Component):
|
|||
dialog.set_icon(common.get_logo(16))
|
||||
hostname_entry = self.glade.get_widget("entry_hostname")
|
||||
port_spinbutton = self.glade.get_widget("spinbutton_port")
|
||||
username_entry = self.glade.get_widget("entry_username")
|
||||
password_entry = self.glade.get_widget("entry_password")
|
||||
response = dialog.run()
|
||||
if response == 1:
|
||||
username = username_entry.get_text()
|
||||
password = password_entry.get_text()
|
||||
hostname = hostname_entry.get_text()
|
||||
if not urlparse.urlsplit(hostname).hostname:
|
||||
# We need to add a http://
|
||||
hostname = "http://" + hostname
|
||||
u = urlparse.urlsplit(hostname)
|
||||
if username and password:
|
||||
host = u.scheme + "://" + username + ":" + password + "@" + u.hostname
|
||||
else:
|
||||
host = hostname
|
||||
|
||||
# We add the host
|
||||
self.add_host(hostname_entry.get_text(),
|
||||
port_spinbutton.get_value_as_int())
|
||||
self.add_host(host, port_spinbutton.get_value_as_int())
|
||||
|
||||
dialog.hide()
|
||||
|
||||
def add_host(self, hostname, port):
|
||||
"""Adds the host to the list"""
|
||||
if hostname.startswith("http://"):
|
||||
hostname = hostname[7:]
|
||||
if not urlparse.urlsplit(hostname).scheme:
|
||||
# We need to add http:// to this
|
||||
hostname = "http://" + hostname
|
||||
|
||||
# Check to make sure the hostname is at least 1 character long
|
||||
if len(hostname) < 1:
|
||||
|
@ -407,18 +481,17 @@ class ConnectionManager(component.Component):
|
|||
row = self.liststore.get_iter(paths[0])
|
||||
status = self.liststore.get_value(row, HOSTLIST_COL_STATUS)
|
||||
uri = self.liststore.get_value(row, HOSTLIST_COL_URI)
|
||||
port = uri.split(":")[1]
|
||||
u = urlparse.urlsplit(uri)
|
||||
if HOSTLIST_STATUS[status] == "Online" or\
|
||||
HOSTLIST_STATUS[status] == "Connected":
|
||||
# We need to stop this daemon
|
||||
uri = "http://" + uri
|
||||
# Call the shutdown method on the daemon
|
||||
core = xmlrpclib.ServerProxy(uri)
|
||||
core.shutdown()
|
||||
# Update display to show change
|
||||
self.update()
|
||||
elif HOSTLIST_STATUS[status] == "Offline":
|
||||
self.start_localhost(port)
|
||||
self.start_localhost(u.port)
|
||||
|
||||
def start_localhost(self, port):
|
||||
"""Starts a localhost daemon"""
|
||||
|
@ -444,11 +517,11 @@ class ConnectionManager(component.Component):
|
|||
uri = self.liststore.get_value(row, HOSTLIST_COL_URI)
|
||||
# Determine if this is a localhost
|
||||
localhost = False
|
||||
port = uri.split(":")[1]
|
||||
if uri.split(":")[0] == "localhost":
|
||||
u = urlparse.urlsplit(uri)
|
||||
if u.hostname == "localhost" or u.hostname == "127.0.0.1":
|
||||
localhost = True
|
||||
|
||||
uri = "http://" + uri
|
||||
|
||||
if status == HOSTLIST_STATUS.index("Connected"):
|
||||
# Stop all the components first.
|
||||
component.stop()
|
||||
|
@ -465,9 +538,14 @@ class ConnectionManager(component.Component):
|
|||
if localhost:
|
||||
self.start_localhost(port)
|
||||
# We need to wait for the host to start before connecting
|
||||
while not self.test_online_status(uri):
|
||||
auth_uri = None
|
||||
while not auth_uri:
|
||||
auth_uri = self.get_localhost_auth_uri(uri)
|
||||
time.sleep(0.01)
|
||||
client.set_core_uri(uri)
|
||||
|
||||
while not self.test_online_status(auth_uri):
|
||||
time.sleep(0.01)
|
||||
client.set_core_uri(auth_uri)
|
||||
self._update_list()
|
||||
self.hide()
|
||||
|
||||
|
@ -477,7 +555,11 @@ class ConnectionManager(component.Component):
|
|||
return
|
||||
|
||||
# Status is OK, so lets change to this host
|
||||
client.set_core_uri(uri)
|
||||
if localhost:
|
||||
client.set_core_uri(self.get_localhost_auth_uri(uri))
|
||||
else:
|
||||
client.set_core_uri(uri)
|
||||
|
||||
self.hide()
|
||||
|
||||
def on_chk_autoconnect_toggled(self, widget):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
|
||||
<!--Generated with glade3 3.4.5 on Fri Nov 7 23:39:07 2008 -->
|
||||
<!--Generated with glade3 3.4.5 on Tue Dec 9 21:46:39 2008 -->
|
||||
<glade-interface>
|
||||
<widget class="GtkDialog" id="connection_manager">
|
||||
<property name="has_focus">True</property>
|
||||
|
@ -378,6 +378,64 @@
|
|||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox5">
|
||||
<property name="visible">True</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label5">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Username:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkEntry" id="entry_username">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkHBox" id="hbox6">
|
||||
<property name="visible">True</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<widget class="GtkLabel" id="label6">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Password:</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<widget class="GtkEntry" id="entry_password">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</widget>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="action_area">
|
||||
<widget class="GtkHButtonBox" id="dialog-action_area3">
|
||||
<property name="visible">True</property>
|
||||
|
|
Loading…
Reference in New Issue