diff --git a/data/setbg b/data/setbg
index 86b411a..224069e 100755
--- a/data/setbg
+++ b/data/setbg
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-# Copyright 2008-2012 Calculate Ltd. http://www.calculate-linux.org
+# Copyright 2008-2015 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,7 +15,9 @@
# limitations under the License.
from PySide import QtCore, QtGui
-import os,sys
+import os
+import re
+
class ImageViewer(QtGui.QMainWindow):
def __init__(self):
@@ -28,25 +30,27 @@ class ImageViewer(QtGui.QMainWindow):
screen = QtGui.QDesktopWidget().screenGeometry()
self.setGeometry(screen)
- self.open('/usr/share/wallpapers/dm-background.png') or \
- self.open('/usr/share/apps/ksplash/Themes/CalculateSplashEn/'
- '400x300/background.png') or \
- self.setBackground()
+ if not any(self.open(x) for x in
+ ('/usr/share/wallpapers/dm-background.png',
+ '/usr/share/apps/ksplash/Themes/CalculateSplashEn/'
+ '400x300/background.png')):
+ self.set_background()
- def selectColor(self):
+ @staticmethod
+ def select_color():
try:
if filter(re.compile(r"(cld|cldx|cldg|cmc|cls)-themes-12").search,
- os.listdir('/var/db/pkg/media-gfx')):
+ os.listdir('/var/db/pkg/media-gfx')):
return "#73a363"
- except:
+ except OSError:
pass
return '#30648b'
- def setBackground(self):
- self.setStyleSheet("background-color: %s"%self.selectColor())
+ def set_background(self):
+ self.setStyleSheet("background-color: %s" % self.select_color())
- def open(self,fileName):
- image = QtGui.QImage(fileName)
+ def open(self, fn):
+ image = QtGui.QImage(fn)
if image.isNull():
return False
@@ -55,8 +59,10 @@ class ImageViewer(QtGui.QMainWindow):
self.imageLabel.adjustSize()
return True
+
if __name__ == '__main__':
import sys
+
app = QtGui.QApplication(sys.argv)
imageViewer = ImageViewer()
imageViewer.show()
diff --git a/pym/desktop/datavars.py b/pym/desktop/datavars.py
index f322aba..16abcc8 100644
--- a/pym/desktop/datavars.py
+++ b/pym/desktop/datavars.py
@@ -1,6 +1,6 @@
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
-# Copyright 2012-2013 Calculate Ltd. http://www.calculate-linux.org
+# Copyright 2012-2015 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,14 +15,15 @@
# limitations under the License.
__app__ = 'calculate-desktop'
-__version__ = '3.1.8'
+__version__ = '3.4.2'
-import os
import sys
from calculate.lib.datavars import DataVars
from calculate.lib.cl_lang import setLocalTranslate
-setLocalTranslate('cl_desktop3',sys.modules[__name__])
+
+setLocalTranslate('cl_desktop3', sys.modules[__name__])
+
class DataVarsDesktop(DataVars):
"""Variable class for desktop package"""
diff --git a/pym/desktop/desktop.py b/pym/desktop/desktop.py
index 8af9cb7..c93ee5c 100644
--- a/pym/desktop/desktop.py
+++ b/pym/desktop/desktop.py
@@ -1,6 +1,6 @@
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
-# Copyright 2010-2013 Calculate Ltd. http://www.calculate-linux.org
+# Copyright 2010-2015 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,51 +16,49 @@
import os
from os import path
-import re
import sys
-import pwd
import time
-import traceback
+from calculate.core.server.core_interfaces import MethodsInterface
-from calculate.desktop._cl_keys import getKey, clearKey
-from datavars import DataVarsDesktop, DataVars, __version__,__app__
+from calculate.desktop._cl_keys import getKey
-from calculate.lib.cl_template import (Template, ProgressTemplate,
- TemplatesError,templateFunction,iniParser)
-from calculate.lib.utils.files import (runOsCommand, isMount,process,
- getRunCommands,STDOUT,childMounts,getLoopFromPath,
- getMdRaidDevices,listDirectory,removeDir,
- makeDirectory)
-from calculate.lib.utils.common import (getpathenv,appendProgramToEnvFile,
- removeProgramToEnvFile,mountEcryptfs,
- CommonError, isBootstrapDataOnly)
+from calculate.lib.utils.files import (isMount, process,
+ getRunCommands, STDOUT, childMounts,
+ getLoopFromPath,
+ getMdRaidDevices, listDirectory,
+ removeDir,
+ makeDirectory, getProgPath)
+from calculate.lib.utils.common import (mountEcryptfs,
+ CommonError, isBootstrapDataOnly)
-from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
-setLocalTranslate('cl_desktop3',sys.modules[__name__])
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+
+_ = lambda x: x
+setLocalTranslate('cl_desktop3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
-from itertools import ifilter
+
import tarfile
import tempfile
import shutil
-from itertools import count
class DesktopError(Exception):
"""Desktop Error"""
-class Desktop:
+class Desktop(MethodsInterface):
"""
Модуль для настройки пользовательского сеанса и выполнения
принудительного выхода из X сессии пользователя
"""
-
+
def __init__(self):
self.homeDir = ""
self.clTempl = None
self.clVars = None
- def createCryptDir(self,userName,uid,gid,userDir,recreateOnError=False):
+ def createCryptDir(self, userName, uid, gid, userDir,
+ recreateOnError=False):
"""
Создать шифрование домашней директории, или подключить существующую
@@ -73,20 +71,21 @@ class Desktop:
# проверить наличие пароля в ключах ядра
if not userPwd or userPwd == "XXXXXXXX":
raise DesktopError(_("User password not found"))
- ecryptfsPath = path.join('/home/.ecryptfs',userName)
+ ecryptfsPath = path.join('/home/.ecryptfs', userName)
# если шифрование уже используется
if path.exists(ecryptfsPath):
- for d in (".ecryptfs",".Private"):
- source,target = path.join(ecryptfsPath,d),path.join(userDir,d)
+ for d in (".ecryptfs", ".Private"):
+ source, target = path.join(ecryptfsPath, d), path.join(userDir,
+ d)
if not path.lexists(target):
- os.symlink(source,target)
+ os.symlink(source, target)
# попытаться подключить шифрованные данные
try:
- if not mountEcryptfs(userName,userPwd,userDir):
+ if not mountEcryptfs(userName, userPwd, userDir):
error = _("Failed to mount ecrypted data")
except CommonError as e:
- error = (_("Failed to mount ecrypted data")+
- _(": ")+'":%s"'%str(e))
+ error = (_("Failed to mount ecrypted data") +
+ _(": ") + '":%s"' % str(e))
# если при подключении произошли ошибки
if error:
# заархивировать текущий профиль и удалить его
@@ -94,17 +93,17 @@ class Desktop:
self.printSUCCESS(_("Recovering encrypted data"))
if self.getMountUserPaths(userDir):
raise DesktopError(_("Failed to encrypt the directory"))
- for source in (userDir,ecryptfsPath):
+ for source in (userDir, ecryptfsPath):
if path.exists(source):
if listDirectory(source):
- target = source+".bak"
+ target = source + ".bak"
newtarget = target
if path.exists(target):
removeDir(target)
- os.rename(source,newtarget)
+ os.rename(source, newtarget)
else:
os.rmdir(source)
- self.createUserDir(userName,uid,gid,userDir)
+ self.createUserDir(userName, uid, gid, userDir)
# ошибка создания шифрования
else:
raise DesktopError(error)
@@ -116,39 +115,40 @@ class Desktop:
if isBootstrapDataOnly(userDir):
if childMounts(userDir):
raise DesktopError(
- _("Failed to create an encrypted user profile")+
- _(": ")+
+ _("Failed to create an encrypted user profile") +
+ _(": ") +
_("The home directory contains mount points"))
# поместить данные во временный tarfile
calculateName = ".calculate"
- calculatePath = path.join(userDir,calculateName)
+ calculatePath = path.join(userDir, calculateName)
tf = tempfile.TemporaryFile()
- with tarfile.open(fileobj=tf,mode='w:') as tarf:
- tarf.add(calculatePath,calculateName)
+ with tarfile.open(fileobj=tf, mode='w:') as tarf:
+ tarf.add(calculatePath, calculateName)
tf.flush()
tf.seek(0)
# удалить эти данные
shutil.rmtree(calculatePath)
# создать шифрованные данные
- e = process('/usr/bin/ecryptfs-setup-private','-u',userName,
- '-b','-l',userPwd,stderr=STDOUT)
+ e = process('/usr/bin/ecryptfs-setup-private', '-u', userName,
+ '-b', '-l', userPwd, stderr=STDOUT)
if e.failed():
raise DesktopError(e.read())
# если были данные от бутстрапа, то распаковать их
if tf:
- with tarfile.open(fileobj=tf,mode='r:') as tarf:
+ with tarfile.open(fileobj=tf, mode='r:') as tarf:
tarf.extractall(userDir)
except Exception as e:
# в случае ошибки сохраняем архив (с данными bootstrap)
# из памяти в файловую систему
if tf:
tf.seek(0)
- bakArchName = path.join(userDir,".calculate.tar.bz2")
- with open(bakArchName,'w') as f:
+ bakArchName = path.join(userDir, ".calculate.tar.bz2")
+ with open(bakArchName, 'w') as f:
f.write(tf.read())
- raise DesktopError(str(e)+
- _("Failed to create an encrypted user profile"))
+ raise DesktopError(str(e) +
+ _(
+ "Failed to create an encrypted user profile"))
finally:
if tf:
tf.close()
@@ -161,11 +161,11 @@ class Desktop:
if not path.exists(userDir):
os.makedirs(userDir)
if mode:
- os.chmod(userDir,mode)
- os.chown(userDir,uid,gid)
+ os.chmod(userDir, mode)
+ os.chown(userDir, uid, gid)
return True
else:
- raise DesktopError(_("Path %s exists") %userDir)
+ raise DesktopError(_("Path %s exists") % userDir)
def umountUserRes(self, *umountPaths):
"""
@@ -183,66 +183,53 @@ class Desktop:
Found user resources
"""
if not homeDir:
- userName = self.clVars.Get("ur_login")
+ self.clVars.Get("ur_login")
homeDir = self.clVars.Get("ur_home_path")
if not homeDir:
raise DesktopError(_("Failed to determine the home directory"))
dirStart, dirEnd = path.split(homeDir)
- mountProfileDir = path.join(dirStart, ".%s" %dirEnd)
- mountRemoteProfileDir = path.join(dirStart, ".%s.remote" %dirEnd)
- return filter(lambda x: x.startswith(homeDir) or\
- x.startswith(mountProfileDir) or\
- x.startswith(mountRemoteProfileDir),
- map(lambda x: x.split(" ")[1],\
+ mountProfileDir = path.join(dirStart, ".%s" % dirEnd)
+ mountRemoteProfileDir = path.join(dirStart, ".%s.remote" % dirEnd)
+ return filter(lambda x: x.startswith(homeDir) or
+ x.startswith(mountProfileDir) or
+ x.startswith(mountRemoteProfileDir),
+ map(lambda x: x.split(" ")[1],
open("/proc/mounts").readlines()))
- def execProg(self, cmdStrProg, inStr=False, envProg={}):
- """
- Exec external program
- """
- env_path = {"PATH":getpathenv()}
- env = {}
- env.update(os.environ.items() + env_path.items() + envProg.items())
- retCode,programOut = runOsCommand(cmdStrProg,in_str=inStr,env_dict=env)
- if not retCode:
- return programOut
- return False
-
def umountSleepPath(self, rpath):
"""
Отмонтировать указанный путь, а также отключить используемые
в этом пути loop устройства и raid
"""
# check for mount
+ umount_cmd = getProgPath('/bin/umount')
+ fuser_cmd = getProgPath("/bin/fuser")
loops = getLoopFromPath(rpath)
if loops:
- setLoops = set(map(lambda x:x.partition('/dev/')[2],loops))
+ setLoops = set(map(lambda x: x.partition('/dev/')[2], loops))
mdInfo = getMdRaidDevices()
- for k,v in mdInfo.items():
+ for k, v in mdInfo.items():
if setLoops & set(v):
- self.umountSleepPath('/dev/%s'%k)
- process('/sbin/mdadm','--stop','/dev/%s'%k).success()
+ self.umountSleepPath('/dev/%s' % k)
+ process('/sbin/mdadm', '--stop', '/dev/%s' % k).success()
for loop in loops:
self.umountSleepPath(loop)
- process('/sbin/losetup','-d',loop).success()
+ process('/sbin/losetup', '-d', loop).success()
if isMount(rpath):
- for waittime in [0,0.5,1,2]:
+ for waittime in [0, 0.5, 1, 2]:
time.sleep(waittime)
- if not self.execProg("umount %s"%rpath) is False \
- or not isMount(rpath):
- if not isMount(rpath):
- return True
- self.execProg("fuser -km %s"%rpath)
+ process(umount_cmd, rpath).success()
+ if not isMount(rpath):
+ return True
+ process(fuser_cmd, "-km", rpath).success()
for waittime in [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]:
time.sleep(waittime)
- if not self.execProg("umount %s"%rpath) is False \
- or not isMount(rpath):
- if not isMount(rpath):
- return True
- self.execProg("umount -l %s"%rpath)
+ if not isMount(rpath):
+ return True
+ process(umount_cmd, "-l", rpath).success()
else:
if isMount(rpath):
- self.printERROR(_("Failed to unmount directory %s")%rpath)
+ self.printERROR(_("Failed to unmount directory %s") % rpath)
return False
return True
@@ -257,9 +244,9 @@ class Desktop:
fastlogin_user = path.join(fastlogin, urLogin)
if not path.exists(fastlogin_user):
try:
- open(fastlogin_user,'w').close()
+ open(fastlogin_user, 'w').close()
return True
- except:
+ except IOError:
self.printWARNING(_("Failed to create the fastlogin mark file"))
return False
@@ -268,24 +255,25 @@ class Desktop:
Выполнить logout пользователя через dbus
"""
display = self.clVars.Select('cl_desktop_online_display',
- where='cl_desktop_online_user',eq=urLogin,limit=1)
+ where='cl_desktop_online_user', eq=urLogin,
+ limit=1)
session = self.clVars.Get('cl_desktop_xsession')
if session == 'xfce':
logoutCommand = "/usr/bin/qdbus org.xfce.SessionManager " \
- "/org/xfce/SessionManager Logout False False"
+ "/org/xfce/SessionManager Logout False False"
elif session == 'kde':
logoutCommand = "/usr/bin/kquitapp ksmserver"
elif session == 'gnome':
logoutCommand = "/usr/bin/qdbus org.gnome.SessionManager " \
- "/org/gnome/SessionManager Logout 1"
+ "/org/gnome/SessionManager Logout 1"
else:
raise DesktopError(_("Unable to detect the X session"))
- if process("su",urLogin,"-c",
- ("DISPLAY=:%s "%display)+logoutCommand).failed():
+ if process("su", urLogin, "-c",
+ ("DISPLAY=:%s " % display) + logoutCommand).failed():
raise DesktopError(_("Unable to send the logout command"))
return True
- def waitLogout(self,urLogin,waitTime,postWaitTime=5):
+ def waitLogout(self, urLogin, waitTime, postWaitTime=5):
"""
Ожидать завершения пользовательского сеанса
@@ -294,24 +282,24 @@ class Desktop:
waitTime: время ожидания завершения сеанса
"""
if filter(lambda x: "xdm/xdm\x00--logout" in x,
- getRunCommands()):
- for i in range(0,waitTime):
+ getRunCommands()):
+ for i in range(0, waitTime):
if not filter(lambda x: "xdm/xdm\x00--logout" in x,
- getRunCommands()):
+ getRunCommands()):
return True
time.sleep(1)
else:
raise DesktopError(_("Unable to wait for completion "
"of the user logout"))
- for wait in range(0,5):
+ for wait in range(0, 5):
self.clVars.Invalidate('cl_desktop_online_data')
- if not urLogin in self.clVars.Get('cl_desktop_online_user'):
+ if urLogin not in self.clVars.Get('cl_desktop_online_user'):
return True
time.sleep(1)
else:
return False
-
- def prepareFace(self,ur_home_path):
+
+ def prepareFace(self, ur_home_path):
"""Подготовить каталог пользователя с шифрованием для работы с .face
Для шифрованных профилей в корне домашней директории
@@ -325,7 +313,7 @@ class Desktop:
True/False в зависимости от успешности
"""
if path.exists(ur_home_path):
- symlink_path = path.join(ur_home_path,'.face')
+ symlink_path = path.join(ur_home_path, '.face')
if not path.lexists(symlink_path):
os.symlink('.ecryptfs/.face', symlink_path)
return True
diff --git a/pym/desktop/utils/cl_desktop.py b/pym/desktop/utils/cl_desktop.py
index 82ad2ef..b3bc271 100644
--- a/pym/desktop/utils/cl_desktop.py
+++ b/pym/desktop/utils/cl_desktop.py
@@ -1,6 +1,6 @@
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
-# Copyright 2010-2013 Calculate Ltd. http://www.calculate-linux.org
+# Copyright 2010-2015 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -16,21 +16,23 @@
import sys
from os import path
-from calculate.core.server.func import Action,Tasks
-from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
+from calculate.core.server.func import Action, Tasks
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
from calculate.lib.utils.files import FilesError, isMount
from calculate.desktop.desktop import DesktopError
from calculate.lib.cl_template import TemplatesError
-setLocalTranslate('cl_desktop3',sys.modules[__name__])
+_ = lambda x: x
+setLocalTranslate('cl_desktop3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
+
class ClDesktopLogoutAction(Action):
"""
Вывести пользователя из X сессии
"""
# ошибки, которые отображаются без подробностей
- native_error = (FilesError,DesktopError,TemplatesError)
+ native_error = (FilesError, DesktopError, TemplatesError)
successMessage = __("User logged out!")
failedMessage = __("Unable to log out")
@@ -38,20 +40,21 @@ class ClDesktopLogoutAction(Action):
# список задач для действия
tasks = [
- {'name':'user_logout',
- 'method':'Desktop.userLogout(cl_desktop_login)',
+ {'name': 'user_logout',
+ 'method': 'Desktop.userLogout(cl_desktop_login)',
},
- {'name':'wait_logout',
- 'message':__("Waiting for the logout"),
- 'method':'Desktop.waitLogout(cl_desktop_login,300)'}
- ]
+ {'name': 'wait_logout',
+ 'message': __("Waiting for the logout"),
+ 'method': 'Desktop.waitLogout(cl_desktop_login,300)'}
+ ]
+
class ClDesktopAction(Action):
"""
Настроить пользовательский профиль
"""
# ошибки, которые отображаются без подробностей
- native_error = (FilesError,DesktopError,TemplatesError)
+ native_error = (FilesError, DesktopError, TemplatesError)
successMessage = __("User account {ur_login} has been "
"successfully configured")
@@ -60,50 +63,51 @@ class ClDesktopAction(Action):
# список задач для действия
tasks = [
- # создать домашниюю директорию
- {'name':'create_home',
- 'message':__("Creating the home directory for {ur_login}"),
- 'method':'Desktop.createUserDir(ur_login,ur_uid,ur_gid,ur_home_path)',
- 'condition':lambda dv:not path.exists(dv.Get('ur_home_path'))
+ # создать домашниюю директорию
+ {'name': 'create_home',
+ 'message': __("Creating the home directory for {ur_login}"),
+ 'method': 'Desktop.createUserDir(ur_login,ur_uid,ur_gid,ur_home_path)',
+ 'condition': lambda dv: not path.exists(dv.Get('ur_home_path'))
},
- # используется ли шифрование
- {'name':'crypt',
- 'condition':lambda dv:(not isMount(dv.Get('ur_home_path')) and \
- dv.Get('ur_home_crypt_set') == 'on' and
- (not dv.isModuleInstalled("install") or
- dv.Get('install.cl_autologin') != dv.Get('ur_login')))
+ # используется ли шифрование
+ {'name': 'crypt',
+ 'condition': lambda dv: (not isMount(dv.Get('ur_home_path')) and
+ dv.Get('ur_home_crypt_set') == 'on' and
+ (not dv.isModuleInstalled("install") or
+ dv.Get('install.cl_autologin') != dv.Get(
+ 'ur_login')))
},
- # подготовить шифрованный профиль пользователя для работы с .icon
- {'name':'crypt:prepare_icon',
- 'method':'Desktop.prepareFace(ur_home_path)',
- 'condition':lambda Get:Get('ur_domain_set') == 'off'
+ # подготовить шифрованный профиль пользователя для работы с .icon
+ {'name': 'crypt:prepare_icon',
+ 'method': 'Desktop.prepareFace(ur_home_path)',
+ 'condition': lambda Get: Get('ur_domain_set') == 'off'
},
- # подключить шифрованные данные
- {'name':'crypt:ecryptfs',
- 'message':__("Mounting encrypted data"),
- 'method':'Desktop.createCryptDir(ur_login,ur_uid,ur_gid,'
- 'ur_home_path,False)'
+ # подключить шифрованные данные
+ {'name': 'crypt:ecryptfs',
+ 'message': __("Mounting encrypted data"),
+ 'method': 'Desktop.createCryptDir(ur_login,ur_uid,ur_gid,'
+ 'ur_home_path,False)'
},
- # настроить пользовательских профиль шаблонами
- {'name':'user_profile',
- 'message':__("Setting up the user profile"),
- 'method':'Desktop.applyTemplates(None,False,'\
+ # настроить пользовательских профиль шаблонами
+ {'name': 'user_profile',
+ 'message': __("Setting up the user profile"),
+ 'method': 'Desktop.applyTemplates(None,False,'
'False,None)',
- 'condition':lambda Get: Get('cl_desktop_force_setup_set') == 'on' or \
- Get('cl_desktop_update_profile_set') == 'on'
+ 'condition': lambda Get: (Get('cl_desktop_force_setup_set') == 'on' or
+ Get('cl_desktop_update_profile_set') == 'on')
},
- {'name':'fast_login',
- 'method':'Desktop.setFastlogin(ur_login)',
- 'essential':False,
- 'condition':lambda Get: Get('ur_domain_set') == 'off'
+ {'name': 'fast_login',
+ 'method': 'Desktop.setFastlogin(ur_login)',
+ 'essential': False,
+ 'condition': lambda Get: Get('ur_domain_set') == 'off'
},
- # отключить ресурсы подключенные в каталоге пользователя
- {'name':'umount_userres',
- 'message': _("Unmouning user resources"),
- 'method':'Desktop.umountUserRes(ur_mount_dirs)',
- 'condition': lambda dv:dv.Get('ur_mount_dirs'),
- 'depend': Tasks.failed()},
- {'name':'ecryptfs:umount_homedir',
- 'method':'Desktop.umountUserRes(ur_home_path)',
- 'depend': Tasks.failed()}
- ]
+ # отключить ресурсы подключенные в каталоге пользователя
+ {'name': 'umount_userres',
+ 'message': _("Unmouning user resources"),
+ 'method': 'Desktop.umountUserRes(ur_mount_dirs)',
+ 'condition': lambda dv: dv.Get('ur_mount_dirs'),
+ 'depend': Tasks.failed()},
+ {'name': 'ecryptfs:umount_homedir',
+ 'method': 'Desktop.umountUserRes(ur_home_path)',
+ 'depend': Tasks.failed()}
+ ]
diff --git a/pym/desktop/variables/action.py b/pym/desktop/variables/action.py
index 11ade65..de2fa42 100644
--- a/pym/desktop/variables/action.py
+++ b/pym/desktop/variables/action.py
@@ -1,6 +1,6 @@
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
-# Copyright 2008-2013 Calculate Ltd. http://www.calculate-linux.org
+# Copyright 2008-2015 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,14 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import os
import sys
-from os import path
-from calculate.lib.datavars import (Variable,VariableError,ReadonlyVariable,
- ActionVariable)
+from calculate.lib.datavars import ActionVariable
from calculate.lib.cl_lang import setLocalTranslate
-setLocalTranslate('cl_desktop3',sys.modules[__name__])
+
+setLocalTranslate('cl_desktop3', sys.modules[__name__])
+
class VariableAcDesktopMerge(ActionVariable):
"""
@@ -30,18 +29,19 @@ class VariableAcDesktopMerge(ActionVariable):
"""
nonchroot = True
- def action(self,cl_action):
+ def action(self, cl_action):
if cl_action == "merge":
return "on"
return "off"
+
class VariableAcDesktopProfile(ActionVariable):
"""
Action variable which has value "on" on user profile setup
"""
nonchroot = True
- def action(self,cl_action):
+ def action(self, cl_action):
if cl_action in ("desktop",):
return "on"
return "off"
diff --git a/pym/desktop/variables/desktop.py b/pym/desktop/variables/desktop.py
index 67b82d7..3ad64d0 100644
--- a/pym/desktop/variables/desktop.py
+++ b/pym/desktop/variables/desktop.py
@@ -1,6 +1,6 @@
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
-# Copyright 2010-2013 Calculate Ltd. http://www.calculate-linux.org
+# Copyright 2010-2015 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,50 +19,60 @@ import sys
import re
from os import path
import pwd
-from calculate.lib.datavars import Variable,VariableError,ReadonlyVariable, \
- ReadonlyTableVariable,FieldValue
+from calculate.lib.datavars import (Variable, VariableError, ReadonlyVariable,
+ ReadonlyTableVariable, FieldValue,
+ VariableInterface)
+from calculate.lib.utils.common import getValueFromConfig
from calculate.lib.variables.user import VariableUrLogin
-from calculate.lib.utils.files import (readLinesFile,process,isMount,
- listDirectory)
+from calculate.lib.utils.files import (readLinesFile, process,
+ listDirectory)
from calculate.desktop._cl_keys import getKey
from itertools import *
-from calculate.lib.cl_template import (templateFunction,iniParser)
+from calculate.lib.cl_template import templateFunction
+from calculate.lib.cl_ini_parser import iniParser
import hashlib
from calculate.lib.cl_lang import setLocalTranslate
-setLocalTranslate('cl_desktop3',sys.modules[__name__])
+
+_ = lambda x: x
+setLocalTranslate('cl_desktop3', sys.modules[__name__])
+
class VariableUrJidHost(ReadonlyVariable):
"""
Jabber host for user
"""
+
def get(self):
userJid = self.Get("ur_jid")
if userJid:
return userJid.partition('@')[2]
return ""
-class DomainInfoHelper:
+
+class DomainInfoHelper(VariableInterface):
"""
Вспомогательный класс для определения доменный ли пользователь
"""
+
def getUserDataInFile(self, login, filePasswd):
- return filter(lambda x: x[0]==login,
- map(lambda x: x.strip().split(':'),
- readLinesFile(filePasswd)))
+ return filter(lambda x: x[0] == login,
+ map(lambda x: x.strip().split(':'),
+ readLinesFile(filePasswd)))
- def isDomainUser(self,userName):
+ def isDomainUser(self, userName):
if userName:
try:
passwdUserData = self.getUserDataInFile(userName, "/etc/passwd")
- except:
+ except Exception:
return False
if passwdUserData:
passwdUserData = passwdUserData[0]
try:
- cacheUserData = self.getUserDataInFile(userName,
+ cacheUserData = self.getUserDataInFile(
+ userName,
"/var/lib/calculate/calculate-client/cache/passwd")
- except:
+ except Exception:
return False
if cacheUserData:
cacheUserData = cacheUserData[0]
@@ -73,70 +83,65 @@ class DomainInfoHelper:
return False
-class VariableUrDomainSet(ReadonlyVariable,DomainInfoHelper):
+class VariableUrDomainSet(ReadonlyVariable, DomainInfoHelper):
"""
Flag for determining domain user or local
"""
type = "bool"
- def getUserDataInFile(self, login, filePasswd):
- return filter(lambda x: x[0]==login,
- map(lambda x: x.strip().split(':'),
- readLinesFile(filePasswd)))
+ def getUserDataInFile(self, login, file_passwd):
+ return filter(lambda x: x[0] == login,
+ map(lambda x: x.strip().split(':'),
+ readLinesFile(file_passwd)))
def get(self):
return "on" if self.isDomainUser(self.Get('ur_login')) else "off"
+
class VariableClDesktopXsession(ReadonlyVariable):
"""
User current X session
"""
+ session_list = ("kde", "gnome", "mate", "xfce", "plasma")
+
def get(self):
envXsessionFile = "/etc/env.d/90xsession"
- xsession = os.environ.get("XSESSION",None)
- desktopSession = os.environ.get("DESKTOP_SESSION",None)
+ xsession = os.environ.get("XSESSION", None)
+ desktopSession = os.environ.get("DESKTOP_SESSION", None)
if not xsession:
- if os.path.exists(envXsessionFile):
- xsession = \
- map(lambda x:x.partition("=")[2].strip("'\""),
- filter(lambda x:x.startswith("XSESSION="),
- filter(lambda x:not x.startswith("#"),
- open(envXsessionFile,"r"))))
- if xsession:
- xsession = xsession[-1]
+ xsession = getValueFromConfig(envXsessionFile, "XSESSION")
if xsession:
- if desktopSession and \
- any(x in desktopSession.lower()
- for x in ("kde","xfce","gnome")):
+ if (desktopSession and any(x in desktopSession.lower()
+ for x in self.session_list)):
xsession = desktopSession
- if "kde" in xsession.lower():
- return "kde"
- elif "gnome" in xsession.lower():
- return "gnome"
- elif "xfce" in xsession.lower():
- return "xfce"
+ xsession = xsession.lower()
+ for session in self.session_list:
+ if session in xsession:
+ return session
else:
- return xsession.lower()
+ return xsession
return ""
+
class VariableClDesktopGstData(ReadonlyVariable):
"""
GStreamer data
"""
+
def get(self):
# try import gst
+ copyargv = sys.argv
+ sys.argv = []
+ olderr = os.dup(sys.stderr.fileno())
try:
- copyargv = sys.argv
- sys.argv = []
- olderr = os.dup(sys.stderr.fileno())
os.close(sys.stderr.fileno())
import gst
import gst.interfaces
except ImportError:
gst = None
finally:
- sys.argv= copyargv
- os.dup2(olderr,sys.stderr.fileno())
+ sys.argv = copyargv
+ os.dup2(olderr, sys.stderr.fileno())
if gst is None:
return {}
@@ -149,48 +154,55 @@ class VariableClDesktopGstData(ReadonlyVariable):
outdata['device_name'] = alsamixer.get_property("device-name")
outdata['long_name'] = alsamixer.get_factory().get_longname()
outdata['internal_name'] = filter(str.isalnum,
- "%s (%s)"%(outdata['device_name'],
- outdata['long_name']))
+ "%s (%s)" % (
+ outdata['device_name'],
+ outdata['long_name']))
outdata['channels'] = []
for t in alsamixer.list_tracks():
if t.flags & gst.interfaces.MIXER_TRACK_OUTPUT:
if t.flags & gst.interfaces.MIXER_TRACK_MASTER or \
- any(x in t.label
- for x in ("Wave","Front","LFE","Center",
- "Head","Side","Speaker",
- "Surround","PCM")):
+ any(x in t.label
+ for x in ("Wave", "Front", "LFE", "Center",
+ "Head", "Side", "Speaker",
+ "Surround", "PCM")):
outdata['channels'].append(t.label)
if t.flags & gst.interfaces.MIXER_TRACK_MASTER:
outdata['master_channel'] = t.label
- except:
+ except Exception:
pass
return outdata
+
class VariableClDesktopGstCard(ReadonlyVariable):
"""
Internal card name for xfce mixer
"""
+
def get(self):
- return self.Get('cl_desktop_gst_data').get('internal_name','')
+ return self.Get('cl_desktop_gst_data').get('internal_name', '')
+
class VariableClDesktopGstMasterchannel(ReadonlyVariable):
"""
Master track name
"""
+
def get(self):
- return self.Get('cl_desktop_gst_data').get('master_channel','')
+ return self.Get('cl_desktop_gst_data').get('master_channel', '')
class VariableClDesktopXfceMixer(ReadonlyVariable):
"""
List of channel for xfce-perchannel mixer
"""
+
def get(self):
return "\n".join(
- map(lambda x:' '%x,
- self.Get('cl_desktop_gst_data').get('channels',[])))
+ map(lambda x: ' ' % x,
+ self.Get('cl_desktop_gst_data').get('channels', [])))
+
-class VariableClDesktopOnlineData(ReadonlyTableVariable,DomainInfoHelper):
+class VariableClDesktopOnlineData(ReadonlyTableVariable, DomainInfoHelper):
"""
Information about online users
"""
@@ -202,52 +214,53 @@ class VariableClDesktopOnlineData(ReadonlyTableVariable,DomainInfoHelper):
reDisplay = re.compile(r"^\(?:(\d+\.?\d*)")
- def _getDisplay(self,*args):
+ def _getDisplay(self, *args):
"""
Get DISPLAY from args
"""
- for arg in map(self.reDisplay.search,args):
+ for arg in map(self.reDisplay.search, args):
if arg:
return arg.group(1)
return ""
- def get_user_uid(self,username):
+ def get_user_uid(self, username):
try:
return str(pwd.getpwnam(username).pw_uid)
- except:
+ except Exception:
return ""
- def get(self,hr=False):
- xSession = 0
- foundTwoSession = False
+ def get(self, hr=False):
+ # TODO: need to KISS rewrite
resWho = process("who")
xData = [[]]
if resWho.success():
listProcessing = lambda x: (x[0], x[1], x[-1]) \
- if len(x)>=5 else []
+ if len(x) >= 5 else []
xData = groupby(
- sorted(
- filter(lambda x: x[0]!="root",
- map(lambda x: (x[0],self._getDisplay(x[1],x[2])),
- filter(lambda x: x and\
- (x[2].startswith("(:") or \
- x[1].startswith(":")),
- map(lambda x: listProcessing(\
- filter(lambda y: y, x.split())),
- resWho)))),
- key=lambda x:x[0]),
- lambda x:x[0])
- xData = map(lambda x:(x[0][0],x[0][1],
- self.get_user_uid(x[0][0]),
- "on" if self.isDomainUser(x[0][0]) else "off",
- len(x)),
- map(lambda x:list(x[1]),
- xData))
+ sorted(
+ filter(lambda x: x[0] != "root",
+ map(lambda x: (x[0], self._getDisplay(x[1], x[2])),
+ filter(lambda x: x and \
+ (x[2].startswith("(:") or
+ x[1].startswith(":")),
+ map(lambda x: listProcessing(
+ filter(lambda y: y, x.split())),
+ resWho)))),
+ key=lambda x: x[0]),
+ lambda x: x[0])
+ xData = map(lambda x: (x[0][0], x[0][1],
+ self.get_user_uid(x[0][0]),
+ "on" if self.isDomainUser(
+ x[0][0]) else "off",
+ len(x)),
+ map(lambda x: list(x[1]),
+ xData))
return xData
setValue = Variable.setValue
-class VariableClDesktopOnlineUser(FieldValue,ReadonlyVariable):
+
+class VariableClDesktopOnlineUser(FieldValue, ReadonlyVariable):
"""
Логин пользователя
"""
@@ -255,7 +268,8 @@ class VariableClDesktopOnlineUser(FieldValue,ReadonlyVariable):
source_variable = "cl_desktop_online_data"
column = 0
-class VariableClDesktopOnlineDisplay(FieldValue,ReadonlyVariable):
+
+class VariableClDesktopOnlineDisplay(FieldValue, ReadonlyVariable):
"""
Display пользователя
"""
@@ -263,7 +277,8 @@ class VariableClDesktopOnlineDisplay(FieldValue,ReadonlyVariable):
source_variable = "cl_desktop_online_data"
column = 1
-class VariableClDesktopOnlineUid(FieldValue,ReadonlyVariable):
+
+class VariableClDesktopOnlineUid(FieldValue, ReadonlyVariable):
"""
UID пользователя
"""
@@ -271,7 +286,8 @@ class VariableClDesktopOnlineUid(FieldValue,ReadonlyVariable):
source_variable = "cl_desktop_online_data"
column = 2
-class VariableClDesktopOnlineDomainSet(FieldValue,ReadonlyVariable):
+
+class VariableClDesktopOnlineDomainSet(FieldValue, ReadonlyVariable):
"""
Является ли пользователь доменным
"""
@@ -279,7 +295,8 @@ class VariableClDesktopOnlineDomainSet(FieldValue,ReadonlyVariable):
source_variable = "cl_desktop_online_data"
column = 3
-class VariableClDesktopOnlineCount(FieldValue,ReadonlyVariable):
+
+class VariableClDesktopOnlineCount(FieldValue, ReadonlyVariable):
"""
Количество сеансов пользователя
"""
@@ -287,6 +304,7 @@ class VariableClDesktopOnlineCount(FieldValue,ReadonlyVariable):
source_variable = "cl_desktop_online_data"
column = 4
+
class VariableClDesktopLogin(VariableUrLogin):
"""
User Login
@@ -299,20 +317,21 @@ class VariableClDesktopLogin(VariableUrLogin):
else:
return VariableUrLogin.choice(self)
- def check(self,value):
+ def check(self, value):
"""Does user exist"""
- if not value in self.choice() and self.Get('cl_action') == 'logout':
+ if value not in self.choice() and self.Get('cl_action') == 'logout':
raise VariableError(_("No X session user found"))
if value == "":
raise VariableError(_("Please specify the user name"))
try:
pwd.getpwnam(value).pw_gid
- except:
- raise VariableError(_("User %s does not exist")%value)
+ except Exception:
+ raise VariableError(_("User %s does not exist") % value)
def get(self):
return ""
+
class VariableUrMountDirs(ReadonlyVariable):
"""
Примонтированные директории в профиле пользователя
@@ -324,26 +343,30 @@ class VariableUrMountDirs(ReadonlyVariable):
if not homeDir:
return []
dirStart, dirEnd = path.split(homeDir)
- mountProfileDir = path.join(dirStart, ".%s" %dirEnd)
- mountRemoteProfileDir = path.join(dirStart, ".%s.remote" %dirEnd)
-
- directories = filter(lambda x:x != homeDir,
- filter(lambda x: (x.startswith(homeDir) or
- x.startswith(mountProfileDir) or
- x.startswith(mountRemoteProfileDir)),
- map(lambda x: x.split(" ")[1],
- readLinesFile('/proc/mounts'))))
- #if isMount(homeDir):
+ mountProfileDir = path.join(dirStart, ".%s" % dirEnd)
+ mountRemoteProfileDir = path.join(dirStart, ".%s.remote" % dirEnd)
+
+ directories = filter(lambda x: x != homeDir,
+ filter(lambda x: (x.startswith(homeDir) or
+ x.startswith(mountProfileDir) or
+ x.startswith(
+ mountRemoteProfileDir)),
+ map(lambda x: x.split(" ")[1],
+ readLinesFile('/proc/mounts'))))
+ # if isMount(homeDir):
# directories.append(homeDir)
- return sorted(directories,reverse=True)
+ return sorted(directories, reverse=True)
+
class VariableUrPassword(ReadonlyVariable):
"""
Пароль пользователя, получаемый из ключей ядра
"""
+
def get(self):
return getKey(self.Get('ur_login')) or ""
+
class VariableClDesktopUpdateProfileSet(Variable):
"""
Нужно ли выполнять обновление профиля пользователя на основании
@@ -353,19 +376,20 @@ class VariableClDesktopUpdateProfileSet(Variable):
def get(self):
lastTimestamp = templateFunction.getLastElog()
- iniEnv = path.join(self.Get('ur_home_path'),'.calculate/ini.env')
+ iniEnv = path.join(self.Get('ur_home_path'), '.calculate/ini.env')
userIni = iniParser(iniEnv)
- userTimestamp = userIni.getVar('main','elog')
+ userTimestamp = userIni.getVar('main', 'elog')
if userTimestamp:
userTimestamp = userTimestamp.encode('utf-8')
- profileSetup = userIni.getVar('main','profile_setup')
+ profileSetup = userIni.getVar('main', 'profile_setup')
login_setup = profileSetup == 'on'
if (self.Get('ur_domain_set') == 'on' or login_setup or
- not path.exists(iniEnv) or userTimestamp != lastTimestamp):
+ not path.exists(iniEnv) or userTimestamp != lastTimestamp):
return 'on'
else:
return 'off'
+
class VariableClDesktopForceSetupSet(Variable):
"""
Принудительно выполнить обновление пользовательского профиля
@@ -379,37 +403,43 @@ class VariableClDesktopForceSetupSet(Variable):
self.label = _("Force configuration")
self.help = _("force configuration")
+
class VariableClDesktopFacePath(Variable):
"""Путь к стандартным иконкам пользователей"""
value = "/usr/share/pixmaps/faces"
+
class VariableClDesktopFaceList(Variable):
"""Список доступных иконок по умолчанию для пользователей"""
type = "list"
def get(self):
return sorted(
- filter(lambda x:x.endswith('.png'),
- listDirectory(self.Get('cl_desktop_face_path'))))
+ filter(lambda x: x.endswith('.png'),
+ listDirectory(self.Get('cl_desktop_face_path'))))
+
class VariableClDesktopHashFace(Variable):
"""Номер иконки пользователя
Номер вычисляется по контрольной сумму md5 логина пользователя
"""
+
def get(self):
login = self.Get('ur_login')
icon_list = self.Get('cl_desktop_face_list')
if icon_list:
return path.join(self.Get('cl_desktop_face_path'),
- icon_list[sum(map(lambda x:ord(x),
- hashlib.md5(login).digest()))%len(icon_list)])
+ icon_list[sum(map(lambda x: ord(x),
+ hashlib.md5(
+ login).digest())) % len(
+ icon_list)])
else:
return ""
+
class VariableClDesktopFastloginPath(ReadonlyVariable):
"""
Путь до каталога в котором указаны пользователи быстрого входа в сеанс
"""
value = "/var/lib/calculate/calculate-desktop/fastlogin"
-
diff --git a/pym/desktop/wsdl_desktop.py b/pym/desktop/wsdl_desktop.py
index 1391503..e3b1350 100644
--- a/pym/desktop/wsdl_desktop.py
+++ b/pym/desktop/wsdl_desktop.py
@@ -1,6 +1,6 @@
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
-# Copyright 2012-2013 Calculate Ltd. http://www.calculate-linux.org
+# Copyright 2012-2015 Calculate Ltd. http://www.calculate-linux.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,16 +13,18 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-import sys, time, os
+import sys
-from calculate.lib.datavars import VariableError,DataVarsError,DataVars
+from calculate.lib.datavars import VariableError, DataVarsError
from calculate.core.server.func import WsdlBase
from desktop import DesktopError
-from utils.cl_desktop import ClDesktopLogoutAction,ClDesktopAction
+from utils.cl_desktop import ClDesktopLogoutAction, ClDesktopAction
import desktop
-from calculate.lib.cl_lang import setLocalTranslate,getLazyLocalTranslate
-setLocalTranslate('cl_desktop3',sys.modules[__name__])
+from calculate.lib.cl_lang import setLocalTranslate, getLazyLocalTranslate
+
+_ = lambda x: x
+setLocalTranslate('cl_desktop3', sys.modules[__name__])
__ = getLazyLocalTranslate(_)
@@ -32,69 +34,70 @@ class Wsdl(WsdlBase):
# вывести пользователя из сеанса
#
{
- # идентификатор метода
- 'method_name':"desktop_logout",
- # категория метода
- 'category':__('Desktop'),
- # заголовок метода
- 'title':__("User Logout"),
- # иконка для графической консоли
- 'image':'system-log-out',
- # метод присутствует в графической консоли
- 'gui':True,
- # консольная команда
- 'command':'cl-desktop-logout',
- # права для запуска метода
- 'rights':['userconfigure'],
- # объект содержащий модули для действия
- 'logic':{'Desktop':desktop.Desktop},
- # описание действия
- 'action':ClDesktopLogoutAction,
- # объект переменных
- 'datavars':"desktop",
- 'native_error':(VariableError,DataVarsError,
- DesktopError),
- # значения по умолчанию для переменных этого метода
- 'setvars':{'cl_action!':'logout'},
- # описание груп (список лямбда функций)
- 'groups':[
- lambda group:group(_("User logout"),
- normal=('cl_desktop_login',),
- next_label=_("Execute"))]},
+ # идентификатор метода
+ 'method_name': "desktop_logout",
+ # категория метода
+ 'category': __('Desktop'),
+ # заголовок метода
+ 'title': __("User Logout"),
+ # иконка для графической консоли
+ 'image': 'system-log-out',
+ # метод присутствует в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-desktop-logout',
+ # права для запуска метода
+ 'rights': ['userconfigure'],
+ # объект содержащий модули для действия
+ 'logic': {'Desktop': desktop.Desktop},
+ # описание действия
+ 'action': ClDesktopLogoutAction,
+ # объект переменных
+ 'datavars': "desktop",
+ 'native_error': (VariableError, DataVarsError,
+ DesktopError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'logout'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("User logout"),
+ normal=('cl_desktop_login',),
+ next_label=_("Execute"))]},
#
# настроить пользовательский сеанс
#
{
- # идентификатор метода
- 'method_name':"desktop",
- # категория метода
- 'category':__('Desktop'),
- # заголовок метода
- 'title':__("User Account Configuration"),
- # иконка для графической консоли
- 'image':'user-desktop,preferences-desktop',
- # метод присутствует в графической консоли
- 'gui':True,
- # консольная команда
- 'command':'cl-desktop',
- # права для запуска метода
- 'rights':['userconfigure'],
- # объект содержащий модули для действия
- 'logic':{'Desktop':desktop.Desktop},
- # описание действия
- 'action':ClDesktopAction,
- # объект переменных
- 'datavars':"desktop",
- 'native_error':(VariableError,DataVarsError,
- DesktopError),
- # значения по умолчанию для переменных этого метода
- 'setvars':{'cl_action!':'desktop',
- 'cl_protect_use_set!':'off'},
- # описание груп (список лямбда функций)
- 'groups':[
- lambda group:group(_("User account configuration"),
- normal=('ur_login',),
- expert=('cl_desktop_force_setup_set','cl_verbose_set',
- 'cl_templates_locate'),
- next_label=_("Execute"))]},
- ]
+ # идентификатор метода
+ 'method_name': "desktop",
+ # категория метода
+ 'category': __('Desktop'),
+ # заголовок метода
+ 'title': __("User Account Configuration"),
+ # иконка для графической консоли
+ 'image': 'user-desktop,preferences-desktop',
+ # метод присутствует в графической консоли
+ 'gui': True,
+ # консольная команда
+ 'command': 'cl-desktop',
+ # права для запуска метода
+ 'rights': ['userconfigure'],
+ # объект содержащий модули для действия
+ 'logic': {'Desktop': desktop.Desktop},
+ # описание действия
+ 'action': ClDesktopAction,
+ # объект переменных
+ 'datavars': "desktop",
+ 'native_error': (VariableError, DataVarsError,
+ DesktopError),
+ # значения по умолчанию для переменных этого метода
+ 'setvars': {'cl_action!': 'desktop',
+ 'cl_protect_use_set!': 'off'},
+ # описание груп (список лямбда функций)
+ 'groups': [
+ lambda group: group(_("User account configuration"),
+ normal=('ur_login',),
+ expert=('cl_desktop_force_setup_set',
+ 'cl_verbose_set',
+ 'cl_templates_locate'),
+ next_label=_("Execute"))]},
+ ]