在Mac OS X和Windows中获取Python的根对话框?

8 投票
4 回答
3476 浏览
提问于 2025-04-16 16:11

我想知道怎么在我的Python应用程序中弹出一个权限提升的对话框?我希望在Windows上能出现用户账户控制(UAC)的对话框,而在Mac上能出现密码认证的对话框。

简单来说,我的应用程序有一部分需要更高的权限,我需要通过图形界面来获取这些权限。我正在使用wxPython。有什么建议吗?

4 个回答

2

我在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,并让没有权限的进程立刻结束,那么我就无法使用退出代码,也无法获取标准输出和错误输出流,无法把这些信息传递给启动原始进程的终端。

4

我知道这篇帖子有点旧,但我写了以下内容来解决我的问题(在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
4

在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上想要实现什么,我可能能提供更具体的帮助。

撰写回答