在Mac OS X和Windows中获取Python的根对话框?
我想知道怎么在我的Python应用程序中弹出一个权限提升的对话框?我希望在Windows上能出现用户账户控制(UAC)的对话框,而在Mac上能出现密码认证的对话框。
简单来说,我的应用程序有一部分需要更高的权限,我需要通过图形界面来获取这些权限。我正在使用wxPython。有什么建议吗?
4 个回答
我在Mac OS X上也遇到了同样的问题。我有一个可行的解决方案,但不是最优的。我会在这里解释我的解决方案,并继续寻找更好的方法。
在程序开始时,我会通过执行以下代码来检查我是否是管理员:
def _elevate():
"""Elevate user permissions if needed"""
if platform.system() == 'Darwin':
try:
os.setuid(0)
except OSError:
_mac_elevate()
如果我不是管理员,os.setuid(0)会失败,这时会触发_mac_elevate(),它会通过osascript帮助我以管理员身份重新启动我的程序。osascript可以用来执行苹果脚本和其他一些操作。我是这样使用它的:
def _mac_elevate():
"""Relaunch asking for root privileges."""
print "Relaunching with root permissions"
applescript = ('do shell script "./my_program" '
'with administrator privileges')
exit_code = subprocess.call(['osascript', '-e', applescript])
sys.exit(exit_code)
这个方法的问题在于,如果我像上面那样使用subprocess.call,当前的进程会继续运行,这样就会有两个我的应用程序实例在运行,导致在Dock上出现两个图标。如果我改用subprocess.Popen,并让没有权限的进程立刻结束,那么我就无法使用退出代码,也无法获取标准输出和错误输出流,无法把这些信息传递给启动原始进程的终端。
我知道这篇帖子有点旧,但我写了以下内容来解决我的问题(在Linux和OS X上以管理员身份运行python脚本)。
我写了一个bash脚本,用来以管理员权限执行bash/python脚本(在Linux和OS X系统上都能用):
#!/bin/bash
if [ -z "$1" ]; then
echo "Specify executable"
exit 1
fi
EXE=$1
available(){
which $1 >/dev/null 2>&1
}
platform=`uname`
if [ "$platform" == "Darwin" ]; then
MESSAGE="Please run $1 as root with sudo or install osascript (should be installed by default)"
else
MESSAGE="Please run $1 as root with sudo or install gksu / kdesudo!"
fi
if [ `whoami` != "root" ]; then
if [ "$platform" == "Darwin" ]; then
# Apple
if available osascript
then
SUDO=`which osascript`
fi
else # assume Linux
# choose either gksudo or kdesudo
# if both are avilable check whoch desktop is running
if available gksudo
then
SUDO=`which gksudo`
fi
if available kdesudo
then
SUDO=`which kdesudo`
fi
if ( available gksudo && available kdesudo )
then
if [ $XDG_CURRENT_DESKTOP = "KDE" ]; then
SUDO=`which kdesudo`;
else
SUDO=`which gksudo`
fi
fi
# prefer polkit if available
if available pkexec
then
SUDO=`which pkexec`
fi
fi
if [ -z $SUDO ]; then
if available zenity; then
zenity --info --text "$MESSAGE"
exit 0
elif available notify-send; then
notify-send "$MESSAGE"
exit 0
elif available xmessage notify-send; then
xmessage -buttons Ok:0 "$MESSAGE"
exit 0
else
echo "$MESSAGE"
fi
fi
fi
if [ "$platform" == "Darwin" ]
then
$SUDO -e "do shell script \"$*\" with administrator privileges"
else
$SUDO $@
fi
基本上,我的系统设置是这样的:我在bin目录里保持子文件夹(比如在/usr/local/bin里有一个叫pyscripts的文件夹),然后创建指向可执行文件的符号链接。这对我有三个好处:
(1) 如果我有不同的版本,我可以通过更改符号链接轻松切换执行哪个版本,这样可以让bin目录更整洁(例如,/usr/local/bin/gcc-versions/4.9/,/usr/local/bin/gcc-versions/4.8/,/usr/local/bin/gcc指向gcc-versions/4.8/gcc)。
(2) 我可以保留脚本的扩展名(这对IDE中的语法高亮很有帮助),但可执行文件不包含扩展名,因为我喜欢这样(例如,svn-tools指向pyscripts/svn-tools.py)。
(3) 我下面会解释的原因:
我把脚本命名为“run-as-root-wrapper”,并放在一个非常常见的路径下(例如/usr/local/bin),这样python就不需要特别的方式来找到它。然后我有以下的run_command.py模块:
import os
import sys
from distutils.spawn import find_executable
#===========================================================================#
def wrap_to_run_as_root(exe_install_path, true_command, expand_path = True):
run_as_root_path = find_executable("run-as-root-wrapper")
if(not run_as_root_path):
return False
else:
if(os.path.exists(exe_install_path)):
os.unlink(exe_install_path)
if(expand_path):
true_command = os.path.realpath(true_command)
true_command = os.path.abspath(true_command)
true_command = os.path.normpath(true_command)
f = open(exe_install_path, 'w')
f.write("#!/bin/bash\n\n")
f.write(run_as_root_path + " " + true_command + " $@\n\n")
f.close()
os.chmod(exe_install_path, 0755)
return True
在我的实际python脚本中,我有以下函数:
def install_cmd(args):
exe_install_path = os.path.join(args.prefix,
os.path.join("bin", args.name))
if(not run_command.wrap_to_run_as_root(exe_install_path, sys.argv[0])):
os.symlink(os.path.realpath(sys.argv[0]), exe_install_path)
所以如果我有一个叫TrackingBlocker.py的脚本(这是我用来修改/etc/hosts文件,将已知的跟踪域重定向到127.0.0.1的实际脚本),当我调用“sudo /usr/local/bin/pyscripts/TrackingBlocker.py --prefix /usr/local --name ModifyTrackingBlocker install”(参数通过argparse模块处理)时,它会安装“/usr/local/bin/ModifyTrackingBlocker”,这是一个执行的bash脚本:
/usr/local/bin/run-as-root-wrapper /usr/local/bin/pyscripts/TrackingBlocker.py [args]
例如:
ModifyTrackingBlocker add tracker.ads.com
执行:
/usr/local/bin/run-as-root-wrapper /usr/local/bin/pyscripts/TrackingBlocker.py add tracker.ads.com
然后会显示需要的身份验证对话框,以获取添加权限:
127.0.0.1 tracker.ads.com
到我的hosts文件中(这个文件只有超级用户可以写入)。
如果你想简化或修改它,只让某些命令以root身份运行,你可以简单地把这个添加到你的脚本中(加上上面提到的必要导入 + import subprocess):
def run_as_root(command, args, expand_path = True):
run_as_root_path = find_executable("run-as-root-wrapper")
if(not run_as_root_path):
return 1
else:
if(expand_path):
command = os.path.realpath(command)
command = os.path.abspath(command)
command = os.path.normpath(command)
cmd = []
cmd.append(run_as_root_path)
cmd.append(command)
cmd.extend(args)
return subprocess.call(' '.join(cmd), shell=True)
使用上面的内容(在run_command模块中):
>>> ret = run_command.run_as_root("/usr/local/bin/pyscripts/TrackingBlocker.py", ["status", "display"])
>>> /etc/hosts is blocking approximately 16147 domains
在Windows系统上,你不能直接弹出用户账户控制(UAC)对话框,除非你启动一个新的进程。而且,你也不能用CreateProcess来启动这个新进程。
要显示UAC对话框,你需要运行一个有合适清单文件的应用程序。比如,你可以参考这个链接,了解如何用py2exe来实现:在Vista中以管理员身份运行编译的Python(py2exe)。
另外,你也可以通过编程的方式,使用win32 API中的ShellExecute来调用runas命令。你可以通过ctypes来实现这个功能,ctypes是Python 2.5及以上版本的标准库之一。具体可以参考这个链接:http://python.net/crew/theller/ctypes/。
抱歉,我对Mac系统不太了解。如果你能详细说明你在Windows上想要实现什么,我可能能提供更具体的帮助。