Initial commit
This commit is contained in:
commit
2ea569c7ed
7 changed files with 553 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*~
|
||||||
|
__pycache__
|
47
Idle.py
Normal file
47
Idle.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import ctypes
|
||||||
|
import ctypes.util
|
||||||
|
|
||||||
|
from gi.repository import GdkX11
|
||||||
|
|
||||||
|
|
||||||
|
class XScreenSaverInfo(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
('window', ctypes.c_ulong),
|
||||||
|
('state', ctypes.c_int),
|
||||||
|
('kind', ctypes.c_int),
|
||||||
|
('til_or_since', ctypes.c_ulong),
|
||||||
|
('idle', ctypes.c_ulong),
|
||||||
|
('eventMask', ctypes.c_ulong)
|
||||||
|
]
|
||||||
|
XScreenSaverInfo_p = ctypes.POINTER(XScreenSaverInfo)
|
||||||
|
|
||||||
|
display_p = ctypes.c_void_p
|
||||||
|
xid = ctypes.c_ulong
|
||||||
|
c_int_p = ctypes.POINTER(ctypes.c_int)
|
||||||
|
|
||||||
|
libXsspath = ctypes.util.find_library('Xss')
|
||||||
|
if libXsspath == None:
|
||||||
|
raise OSError('libXss could not be found.')
|
||||||
|
libXss = ctypes.cdll.LoadLibrary(libXsspath)
|
||||||
|
libXss.XScreenSaverQueryExtension.argtypes = display_p, c_int_p, c_int_p
|
||||||
|
libXss.XScreenSaverAllocInfo.restype = XScreenSaverInfo_p
|
||||||
|
libXss.XScreenSaverQueryInfo.argtypes = (display_p, xid, XScreenSaverInfo_p)
|
||||||
|
|
||||||
|
dpy_p = hash(GdkX11.x11_get_default_xdisplay())
|
||||||
|
rootwindow = GdkX11.x11_get_default_root_xwindow()
|
||||||
|
|
||||||
|
_event_basep = ctypes.c_int()
|
||||||
|
_error_basep = ctypes.c_int()
|
||||||
|
if libXss.XScreenSaverQueryExtension(ctypes.c_void_p(dpy_p), ctypes.byref(_event_basep),
|
||||||
|
ctypes.byref(_error_basep)) == 0:
|
||||||
|
raise OSError('XScreenSaver Extension not available on display.')
|
||||||
|
|
||||||
|
xss_info_p = libXss.XScreenSaverAllocInfo()
|
||||||
|
if xss_info_p == None:
|
||||||
|
raise OSError('XScreenSaverAllocInfo: Out of Memory.')
|
||||||
|
|
||||||
|
def getIdleSec():
|
||||||
|
if libXss.XScreenSaverQueryInfo(dpy_p, rootwindow, xss_info_p) == 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return int(xss_info_p.contents.idle) / 1000
|
69
LockWindow.py
Normal file
69
LockWindow.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from gi.repository import Gtk, Gdk, GObject, GLib
|
||||||
|
|
||||||
|
|
||||||
|
class LockWindow(Gtk.Window):
|
||||||
|
__gsignals__ = {
|
||||||
|
'tryUnlock': (GObject.SIGNAL_RUN_FIRST, None, (str,))
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Gtk.Window.__init__(self)
|
||||||
|
|
||||||
|
bg = Gtk.Image.new_from_file("bg.svg")
|
||||||
|
self.add(bg)
|
||||||
|
|
||||||
|
self.set_decorated(False)
|
||||||
|
self.set_skip_taskbar_hint(True)
|
||||||
|
self.set_skip_pager_hint(True)
|
||||||
|
self.set_keep_above(True)
|
||||||
|
self.fullscreen()
|
||||||
|
|
||||||
|
builder = Gtk.Builder()
|
||||||
|
|
||||||
|
if not builder.add_from_file("unlock.ui"):
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
self.unlockWindow = builder.get_object('unlock_window')
|
||||||
|
self.promptEntry = builder.get_object('prompt_entry')
|
||||||
|
self.messageLabel = builder.get_object('message_label')
|
||||||
|
self.logoutButton = builder.get_object('logout_button')
|
||||||
|
self.unlockButton = builder.get_object('unlock_button')
|
||||||
|
|
||||||
|
self.unlockWindow.set_position(Gtk.WindowPosition.CENTER_ALWAYS)
|
||||||
|
self.unlockWindow.set_transient_for(self)
|
||||||
|
self.unlockWindow.set_modal(True)
|
||||||
|
|
||||||
|
self.connect('delete-event', lambda w, e: True)
|
||||||
|
self.unlockWindow.connect('delete-event', lambda w, e: True)
|
||||||
|
|
||||||
|
self.promptEntry.connect('activate', lambda w: self._tryUnlock())
|
||||||
|
self.unlockButton.connect('clicked', lambda w: self._tryUnlock())
|
||||||
|
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
def _tryUnlock(self):
|
||||||
|
self.promptEntry.set_sensitive(False)
|
||||||
|
GLib.idle_add(lambda: self.emit('tryUnlock', self.promptEntry.get_text()))
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.promptEntry.set_text('')
|
||||||
|
self.promptEntry.set_sensitive(True)
|
||||||
|
self.promptEntry.grab_focus()
|
||||||
|
self.messageLabel.set_label('')
|
||||||
|
|
||||||
|
def setMessage(self, message):
|
||||||
|
self.messageLabel.set_label(message)
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
self.show_all()
|
||||||
|
self.unlockWindow.show_all()
|
||||||
|
|
||||||
|
self.promptEntry.grab_focus()
|
||||||
|
|
||||||
|
def unlock(self):
|
||||||
|
self.unlockWindow.hide()
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
self.reset()
|
38
Locker.py
Normal file
38
Locker.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
|
import Idle
|
||||||
|
|
||||||
|
|
||||||
|
class Locker(object):
|
||||||
|
def __init__(self, lockTimeout, doLock, doUnlock, logoutTimeout = 0, doLogout = None):
|
||||||
|
self.locked = False
|
||||||
|
self.lockTimeout = lockTimeout
|
||||||
|
self.logoutTimeout = logoutTimeout
|
||||||
|
self.doLock = doLock
|
||||||
|
self.doUnlock = doUnlock
|
||||||
|
self.doLogout = doLogout
|
||||||
|
|
||||||
|
GLib.timeout_add_seconds(1, self.checkLock)
|
||||||
|
|
||||||
|
def checkLock(self):
|
||||||
|
if self.locked:
|
||||||
|
return False
|
||||||
|
|
||||||
|
idle = Idle.getIdleSec()
|
||||||
|
if (idle >= self.lockTimeout):
|
||||||
|
GLib.idle_add(self.lock)
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def lock(self):
|
||||||
|
if not self.locked:
|
||||||
|
self.doLock()
|
||||||
|
self.locked = True
|
||||||
|
|
||||||
|
def unlock(self):
|
||||||
|
if self.locked:
|
||||||
|
self.doUnlock()
|
||||||
|
self.locked = False
|
||||||
|
|
||||||
|
GLib.timeout_add(100, self.checkLock)
|
170
pam.py
Normal file
170
pam.py
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
# (c) 2007 Chris AtLee <chris@atlee.ca>
|
||||||
|
# Licensed under the MIT license:
|
||||||
|
# http://www.opensource.org/licenses/mit-license.php
|
||||||
|
"""
|
||||||
|
PAM module for python
|
||||||
|
|
||||||
|
Provides an authenticate function that will allow the caller to authenticate
|
||||||
|
a user against the Pluggable Authentication Modules (PAM) on the system.
|
||||||
|
|
||||||
|
Implemented using ctypes, so no compilation is necessary.
|
||||||
|
|
||||||
|
---
|
||||||
|
modified to work [better] with python3.2, 2011-12-6, david ford, <david@blue-labs.org>
|
||||||
|
i haven't paid any attention to making sure things work in python2. there may be
|
||||||
|
problems in my_conv()
|
||||||
|
---
|
||||||
|
Just some encoding fixes for python3, 2012-01-27 Matthias Schiffer <mschiffer@universe-factory.net>
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import locale
|
||||||
|
if sys.version_info >= (3,):
|
||||||
|
py3k = True
|
||||||
|
else:
|
||||||
|
py3k = False
|
||||||
|
|
||||||
|
from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, pointer, sizeof
|
||||||
|
from ctypes import c_void_p, c_uint, c_char_p, c_char, c_int
|
||||||
|
from ctypes import memmove
|
||||||
|
from ctypes.util import find_library
|
||||||
|
|
||||||
|
class PamHandle(Structure):
|
||||||
|
"""wrapper class for pam_handle_t"""
|
||||||
|
_fields_ = [
|
||||||
|
("handle", c_void_p)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
Structure.__init__(self)
|
||||||
|
self.handle = 0
|
||||||
|
|
||||||
|
class PamMessage(Structure):
|
||||||
|
"""wrapper class for pam_message structure"""
|
||||||
|
_fields_ = [
|
||||||
|
("msg_style", c_int),
|
||||||
|
("msg", c_char_p),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
|
||||||
|
|
||||||
|
class PamResponse(Structure):
|
||||||
|
"""wrapper class for pam_response structure"""
|
||||||
|
_fields_ = [
|
||||||
|
("resp", c_char_p),
|
||||||
|
("resp_retcode", c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
|
||||||
|
|
||||||
|
CONV_FUNC = CFUNCTYPE(
|
||||||
|
c_int,
|
||||||
|
c_int,
|
||||||
|
POINTER(POINTER(PamMessage)),
|
||||||
|
POINTER(POINTER(PamResponse)),
|
||||||
|
c_void_p)
|
||||||
|
|
||||||
|
class PamConv(Structure):
|
||||||
|
"""wrapper class for pam_conv structure"""
|
||||||
|
_fields_ = [
|
||||||
|
("conv", CONV_FUNC),
|
||||||
|
("appdata_ptr", c_void_p)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Various constants
|
||||||
|
PAM_PROMPT_ECHO_OFF = 1
|
||||||
|
PAM_PROMPT_ECHO_ON = 2
|
||||||
|
PAM_ERROR_MSG = 3
|
||||||
|
PAM_TEXT_INFO = 4
|
||||||
|
|
||||||
|
LIBC = CDLL(find_library("c"))
|
||||||
|
LIBPAM = CDLL(find_library("pam"))
|
||||||
|
|
||||||
|
CALLOC = LIBC.calloc
|
||||||
|
CALLOC.restype = c_void_p
|
||||||
|
CALLOC.argtypes = [c_uint, c_uint]
|
||||||
|
|
||||||
|
PAM_START = LIBPAM.pam_start
|
||||||
|
PAM_START.restype = c_int
|
||||||
|
PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)]
|
||||||
|
|
||||||
|
PAM_STRERROR = LIBPAM.pam_strerror
|
||||||
|
PAM_STRERROR.restype = c_char_p
|
||||||
|
PAM_STRERROR.argtypes = [POINTER(PamHandle), c_int]
|
||||||
|
|
||||||
|
PAM_AUTHENTICATE = LIBPAM.pam_authenticate
|
||||||
|
PAM_AUTHENTICATE.restype = c_int
|
||||||
|
PAM_AUTHENTICATE.argtypes = [PamHandle, c_int]
|
||||||
|
|
||||||
|
class pam():
|
||||||
|
code = 0
|
||||||
|
reason = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def authenticate(self, username, password, service='login'):
|
||||||
|
"""username and password authenticate for the given service.
|
||||||
|
|
||||||
|
Returns True for success, or False. self.code is the integer
|
||||||
|
value representing the numerice failure reason, or 0 on success.
|
||||||
|
self.reason is the textual reason.
|
||||||
|
|
||||||
|
Python3 expects bytes() for ctypes inputs. This function will make
|
||||||
|
necessary conversions using the latin-1 coding.
|
||||||
|
|
||||||
|
username: the username to authenticate
|
||||||
|
password: the password in plain text
|
||||||
|
service: the PAM service to authenticate against.
|
||||||
|
Defaults to 'login' """
|
||||||
|
|
||||||
|
@CONV_FUNC
|
||||||
|
def my_conv(n_messages, messages, p_response, app_data):
|
||||||
|
"""Simple conversation function that responds to any
|
||||||
|
prompt where the echo is off with the supplied password"""
|
||||||
|
# Create an array of n_messages response objects
|
||||||
|
addr = CALLOC(n_messages, sizeof(PamResponse))
|
||||||
|
p_response[0] = cast(addr, POINTER(PamResponse))
|
||||||
|
for i in range(n_messages):
|
||||||
|
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
|
||||||
|
cs = c_char_p(password)
|
||||||
|
dst = CALLOC(sizeof(c_char_p), len(password)+1)
|
||||||
|
memmove(dst , cs, len(password))
|
||||||
|
p_response.contents[i].resp = dst
|
||||||
|
p_response.contents[i].resp_retcode = 0
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# python3 ctypes prefers bytes, pretend everyone will be happy using latin-1
|
||||||
|
if py3k:
|
||||||
|
if isinstance(username, str):
|
||||||
|
username = bytes(username, locale.getpreferredencoding())
|
||||||
|
if isinstance(password, str):
|
||||||
|
password = bytes(password, locale.getpreferredencoding())
|
||||||
|
if isinstance(service, str):
|
||||||
|
service = bytes(service, locale.getpreferredencoding())
|
||||||
|
|
||||||
|
handle = PamHandle()
|
||||||
|
conv = PamConv(my_conv, 0)
|
||||||
|
retval = PAM_START(service, username, pointer(conv), pointer(handle))
|
||||||
|
|
||||||
|
if retval != 0:
|
||||||
|
# This is not an authentication error, something has gone wrong starting up PAM
|
||||||
|
self.code = retval
|
||||||
|
self.reason = str(PAM_STRERROR(pointer(handle), retval), locale.getpreferredencoding())
|
||||||
|
return False
|
||||||
|
|
||||||
|
retval = PAM_AUTHENTICATE(handle, 0)
|
||||||
|
|
||||||
|
if retval == 0:
|
||||||
|
# success
|
||||||
|
logic = True
|
||||||
|
else:
|
||||||
|
logic = False
|
||||||
|
|
||||||
|
# store information to inform the caller why we failed
|
||||||
|
self.code = retval
|
||||||
|
self.reason = str(PAM_STRERROR(pointer(handle), retval), locale.getpreferredencoding())
|
||||||
|
|
||||||
|
return logic
|
51
pylock.py
Normal file
51
pylock.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import pwd
|
||||||
|
|
||||||
|
from gi.repository import Gtk, Gdk
|
||||||
|
|
||||||
|
from Locker import Locker
|
||||||
|
from LockWindow import LockWindow
|
||||||
|
import pam
|
||||||
|
|
||||||
|
|
||||||
|
def get_username():
|
||||||
|
return pwd.getpwuid(os.getuid())[0]
|
||||||
|
|
||||||
|
|
||||||
|
theme = 'UzL-login'
|
||||||
|
timeout = 5
|
||||||
|
username = get_username()
|
||||||
|
|
||||||
|
|
||||||
|
def handler(signum, frame):
|
||||||
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, handler)
|
||||||
|
signal.signal(signal.SIGTERM, handler)
|
||||||
|
signal.signal(signal.SIGQUIT, handler)
|
||||||
|
|
||||||
|
|
||||||
|
Gtk.Settings.get_default().set_property('gtk-theme-name', theme)
|
||||||
|
|
||||||
|
window = LockWindow()
|
||||||
|
locker = Locker(timeout, lambda: window.lock(), lambda: window.unlock())
|
||||||
|
pamAuth = pam.pam()
|
||||||
|
|
||||||
|
|
||||||
|
def tryUnlock(w, password):
|
||||||
|
if pamAuth.authenticate(username, password):
|
||||||
|
locker.unlock()
|
||||||
|
else:
|
||||||
|
window.reset()
|
||||||
|
window.setMessage(pamAuth.reason)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
window.connect('tryUnlock', tryUnlock)
|
||||||
|
|
||||||
|
Gtk.main()
|
176
unlock.ui
Normal file
176
unlock.ui
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="2.24"/>
|
||||||
|
<object class="GtkWindow" id="unlock_window">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="resizable">False</property>
|
||||||
|
<property name="decorated">False</property>
|
||||||
|
<property name="has_resize_grip">False</property>
|
||||||
|
<signal name="size-allocate" handler="login_window_size_allocate_cb" swapped="no"/>
|
||||||
|
<child>
|
||||||
|
<object class="GtkNotebook" id="login_notebook">
|
||||||
|
<property name="width_request">700</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="show_tabs">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="box1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">40</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage" id="image1">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="pixbuf">siegel.png</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="vbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="border_width">12</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="hostname_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" comments="This is a placeholder string and will be replaced with the hostname of the system">[hostname]</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="font-desc" value="Sans 18"/>
|
||||||
|
</attributes>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkVBox" id="login_box">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="prompt_box">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">6</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="prompt_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Password:</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="prompt_entry">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="visibility">False</property>
|
||||||
|
<property name="invisible_char">•</property>
|
||||||
|
<property name="primary_icon_activatable">False</property>
|
||||||
|
<property name="secondary_icon_activatable">False</property>
|
||||||
|
<signal name="activate" handler="login_cb" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="message_label">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" comments="This is a placeholder string and will be replaced with a message from PAM">[message]</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkHBox" id="hbox2">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="spacing">20</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="unlock_button">
|
||||||
|
<property name="label" translatable="yes">Unlock</property>
|
||||||
|
<property name="use_action_appearance">False</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="use_action_appearance">False</property>
|
||||||
|
<signal name="clicked" handler="login_cb" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="logout_button">
|
||||||
|
<property name="label" translatable="yes">Logout</property>
|
||||||
|
<property name="use_action_appearance">False</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="use_action_appearance">False</property>
|
||||||
|
<signal name="clicked" handler="cancel_cb" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="pack_type">end</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">3</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="tab">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
Reference in a new issue