当某个应用程序处于活动状态时阻止 Unity 键盘快捷键

Rap*_*ael 5 shortcut-keys unity intellij

出色的 JetBrains IDE(IDEA 等人)为某些功能分配了几乎所有可以想到的键盘快捷键。虽然有时有点压倒性,但它也有助于有效使用。

我的问题是 Unity 也分配了其中一些快捷方式,并且它们具有优先权。一个特别烦人的例子是CTRL+ ALT+ L。这个问题之前已经在这里探讨过。

然而,这两种方法都不是令人满意的。

  1. 全局关闭系统快捷方式会妨碍我使用系统的整体工作效率。
  2. 当我在不同的平台上开发(并且必须选择不同的映射)时,在 IDEA 中切换到不同的键盘映射会让我感到困惑。

有没有办法仅在某个应用程序处于活动状态(即正在运行并处于焦点)时关闭系统快捷方式?

每次启动应用程序时,我都愿意运行脚本。

Jac*_*ijm 10

如果(并且只要)特定应用程序的窗口处于活动状态,如何自动禁用多个(特定)快捷方式

当任意应用程序的窗口处于活动状态时,下面的脚本将禁用特定的快捷键。

虽然你提到“每次启动应用程序我都愿意运行一个脚本。”,之后没有理由杀死脚本,它的汁液极少。

剧本

#!/usr/bin/env python3
import subprocess
import time
import os

app = "gedit"

f = os.path.join(os.environ["HOME"], "keylist")

def run(cmd):
    subprocess.Popen(cmd)

def get(cmd):
    try:
        return subprocess.check_output(cmd).decode("utf-8").strip()
    except:
        pass

def getactive():
    return get(["xdotool", "getactivewindow"])

def setkeys(val):
    # --- add the keys to be disabled below  
    keys = [
         ["org.gnome.settings-daemon.plugins.media-keys", "logout"],
         ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"],
        ]
    # ---
    writelist = []
    if not val:
        try:
            values = open(f).read().splitlines()
        except FileNotFoundError:
            values = []
        for i, key in enumerate(keys):
            try:
                cmd = ["gsettings", "set"]+key+[values[i]]
            except IndexError:
                cmd = ["gsettings", "reset"]+key
            run(cmd)
    else:
        for key in keys:
            cmd = ["gsettings", "set"]+key+["['']"]
            read =  get(["gsettings", "get"]+key)
            writelist.append(read)
            run(cmd)

    if writelist:
        open(f, "wt").write("\n".join(writelist))

front1 = None

while True:
    time.sleep(1)
    pid = get(["pgrep", app])
    if pid:
        try:
            active = get(["xdotool", "getactivewindow"])
            relevant = get(["xdotool", "search", "--all", "--pid", pid]).splitlines()
            front2 = active in relevant
        except AttributeError:
            front2 = front1           
    else:
        front2 = False
    if front2 != front1:
        if front2:
            setkeys(True)
        else:
            setkeys(False)

    front1 = front2
Run Code Online (Sandbox Code Playgroud)

如何使用

  1. 脚本需要xdotool

    sudo apt-get install xdotool
    
    Run Code Online (Sandbox Code Playgroud)
  2. 将脚本复制到一个空文件中,另存为 disable_shortcuts.py

  3. 在脚本的头部,替换为:

    app = "gedit"
    
    Run Code Online (Sandbox Code Playgroud)

    您的应用程序的“gedit”,意思是:拥有窗口的进程名称。

  4. 通过以下命令测试运行脚本:

    python3 /path/to/disable_shortcuts.py
    
    Run Code Online (Sandbox Code Playgroud)
  5. 如果一切正常,请将其添加到启动应用程序:Dash > 启动应用程序 > 添加。添加命令:

    /bin/bash -c "sleep 15 && python3 /path/to/disable_shortcuts.py"
    
    Run Code Online (Sandbox Code Playgroud)

添加更多要禁用的快捷方式

举个例子,我加你提到的快捷方式:CTRL+ ALT+ L。快捷方式在dconf数据库中设置,可以使用gsettings.

在脚本中,这些gsettings条目在函数中设置:setkeys()

def setkeys(val):
    # --- add the keys to be disabled below
    keys = [
        ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"]
        ]
    # ---
Run Code Online (Sandbox Code Playgroud)

添加(禁用)注销快捷方式的示例:

  1. 打开终端窗口,运行命令 dconf watch /
  2. 打开系统设置>“键盘”>“快捷方式”>“系统”
  3. 将快捷方式重新设置为自身。在终端中,您可以看到gsettings属于快捷方式的键:

    在此处输入图片说明

  4. 现在我们必须添加找到的键(外观略有不同):

    ["org.gnome.settings-daemon.plugins.media-keys", "logout"]
    
    Run Code Online (Sandbox Code Playgroud)

    ...到我们函数中的“键”列表:

    ["org.gnome.settings-daemon.plugins.media-keys", "logout"]
    
    Run Code Online (Sandbox Code Playgroud)

现在,这两个CTRL+ ALT+LCTRL+ ALT+ Delete,如果你的应用是在前面被禁用。

解释

如前所述,快捷方式,就像您提到的那样,是在dconf数据库中设置的。在该示例中CTRL+ ALT+ L,关键组或编辑的schortcut是:

org.gnome.settings-daemon.plugins.media-keys screensaver
Run Code Online (Sandbox Code Playgroud)

禁用密钥,命令是:

def setkeys(val):
    # --- add the keys to be disabled below
    keys = [
        ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"],
         ["org.gnome.settings-daemon.plugins.media-keys", "logout"],
        ]
Run Code Online (Sandbox Code Playgroud)

要将密钥重置为其默认值:

org.gnome.settings-daemon.plugins.media-keys screensaver
Run Code Online (Sandbox Code Playgroud)

在以下情况下,脚本每秒查看一次:

  • 您的应用程序完全运行
  • 如果是,则查看是否有任何窗口处于活动状态
  • 再次(仅)如果是这样,它会禁用快捷方式,列在

    gsettings set org.gnome.settings-daemon.plugins.media-keys screensaver ""
    
    Run Code Online (Sandbox Code Playgroud)

    ...等待下一次状态变化。

如果活动窗口不再是您的应用程序之一,列表中提到的键将重置为默认值。

笔记

如前所述,脚本处理器的额外负担是虚无的。您可以很好地在启动时运行它,如“如何使用”中所述。


影响多个应用程序

正如评论中所讨论的,在 OP 的特定情况下,在一应用程序上应用禁用快捷方式很有用,所有应用程序都位于一个目录中。

下面的版本将其应用于所有应用程序的输出

gsettings reset org.gnome.settings-daemon.plugins.media-keys screensaver
Run Code Online (Sandbox Code Playgroud)

将包含一个特定的目录。在我的示例中,我设置了/opt目录,因此如果活动窗口是 中的任何应用程序之一/opt,则设置的快捷方式将被禁用。


将 /opt 中的一个应用程序的窗口放在前面将禁用注销快捷方式

在此处输入图片说明

如果另一个窗口获得焦点,则重新启用快捷方式

在此处输入图片说明


剧本

# --- add the keys to be disabled below
keys = [
    ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"],
     ["org.gnome.settings-daemon.plugins.media-keys", "logout"],
   ]
Run Code Online (Sandbox Code Playgroud)

如何使用

  1. 和第一个脚本一样,xdotool需要安装:

    pgrep -f 
    
    Run Code Online (Sandbox Code Playgroud)
  2. 将脚本复制到一个空文件中,另存为 disable_shortcuts.py

  3. 在脚本的头部,替换为:

    #!/usr/bin/env python3
    import subprocess
    import time
    import os 
    
    appdir = "/opt"
    
    f = os.path.join(os.environ["HOME"], "keylist")
    
    def run(cmd):
        subprocess.call(cmd)
    
    def get(cmd):
        try:
            return subprocess.check_output(cmd).decode("utf-8").strip()
        except:
            pass
    
    def getactive():
        return get(["xdotool", "getactivewindow"])
    
    def setkeys(val):
        # --- add the keys to be disabled below  
        keys = [
             ["org.gnome.settings-daemon.plugins.media-keys", "logout"],
             ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"],
             ["org.gnome.desktop.wm.keybindings", "begin-move"],
            ]
        # ---
        writelist = []
        if not val:
            try:
                values = open(f).read().splitlines()
            except FileNotFoundError:
                values = []
            # for key in keys:
            for i, key in enumerate(keys):
                try:
                    cmd = ["gsettings", "set"]+key+[values[i]]
                except IndexError:
                    cmd = ["gsettings", "reset"]+key
                run(cmd)
        else:
            for key in keys:
                cmd = ["gsettings", "set"]+key+["['']"]
                read =  get(["gsettings", "get"]+key)
                writelist.append(read)
                run(cmd)
        if writelist:
            open(f, "wt").write("\n".join(writelist))
    
    front1 = None
    
    while True:
        time.sleep(1)
        # check if any of the apps runs at all
        checkpids = get(["pgrep", "-f", appdir])
        # if so:
        if checkpids:
            checkpids = checkpids.splitlines()
            active = getactive()
            # get pid frontmost (doesn't work on pid 0)
            match = [l for l in get(["xprop", "-id", active]).splitlines()\
                     if "_NET_WM_PID(CARDINAL)" in l]
            if match:
                # check if pid is of any of the relevant apps
                pid = match[0].split("=")[1].strip()
                front2 = True if pid in checkpids else False
            else:
                front2 = False
        else:
            front2 = False
        if front2 != front1:
            if front2:
                setkeys(True)
            else:
                setkeys(False)
        front1 = front2
    
    Run Code Online (Sandbox Code Playgroud)

    您的应用程序所在的目录中的“/opt”。

  4. 通过以下命令测试运行脚本:

    sudo apt-get install xdotool
    
    Run Code Online (Sandbox Code Playgroud)
  5. 如果一切正常,请将其添加到启动应用程序:Dash > 启动应用程序 > 添加。添加命令:

    appdir = "/opt"
    
    Run Code Online (Sandbox Code Playgroud)

向列表中添加其他快捷方式的工作方式与脚本的版本 1 完全相似。

它适用于所有应用程序吗?

在您的回答中,您提到:

xprop 不会显示所有窗口的 PID。失败的例子:秒表。

窗户用PID 0(如Tkinter的窗口,包括空闲),在输出窗口-没有ID xprop -idIdle虽然根据我的经验,没有任何冲突的捷径。如果您遇到任何需要禁用特定快捷方式的 pid 0 应用程序,请提及。

在这种情况下,可能的转义是转换输出

xdotool getactivewindow
Run Code Online (Sandbox Code Playgroud)

到十六进制,格式wmctrl使用,然后pid在输出中查找相应的

wmctrl -lp
Run Code Online (Sandbox Code Playgroud)

虽然这似乎是最明显的开始,但我没有在脚本中使用它来使脚本尽可能轻量级。


Rap*_*ael 7

基于(旧版本)Jacob Vlijm 的回答, 我写了这个版本来解决这些额外的问题:

  1. 尊重用户在脚本运行时所做的更改。
  2. 不会将用户设置的值重置为默认值。
  3. 存储设置的备份,以防脚本在禁用快捷方式时退出。
  4. 句柄gsettingsdconf快捷方式。(这可能不是问题。)

开放问题:


#!/usr/bin/env python3
import subprocess
import time
import os

# Path pattern to block
apppattern = "myprocess"

# Write a backup that can restore the settings at the
# start of the script.
# Leave empty to not write a backup.
backupfile = "~/.keymap_backup"

# Add the keys to be disabled below.
shortcuts = {
    "org.gnome.settings-daemon.plugins.media-keys/key" : "gsettings",
    "/org/gnome/desktop/wm/keybindings/key" : "dconf",
}

#
# Helper functions
#

# Run a command on the shell
def run(cmd):
    subprocess.Popen(cmd)

# Run a command on the shell and return the
# stripped result
def get(cmd):
    try:
        return subprocess.check_output(cmd).decode("utf-8").strip()
    except:
        pass

# Get the PID of the currently active window
def getactive():
    xdoid = get(["xdotool", "getactivewindow"])
    pidline = [l for l in get(["xprop", "-id", xdoid]).splitlines()\
                 if "_NET_WM_PID(CARDINAL)" in l]
    if pidline:
        pid = pidline[0].split("=")[1].strip()
    else:
        # Something went wrong
        print("Warning: Could not obtain PID of current window")
        pid = ""

    return pid

def readkey(key):
    if shortcuts[key] == "gsettings":
        return get(["gsettings", "get"] + key.split("/"))
    elif shortcuts[key] == "dconf":
        return get(["dconf", "read", key])

def writekey(key, val):
    if val == "": 
        val = "['']"
    if shortcuts[key] == "gsettings":        
        run(["gsettings", "set"] + key.split("/") + [val])
    elif shortcuts[key] == "dconf":
        run(["dconf", "write", key, val])

def resetkey(key):
    if shortcuts[key] == "gsettings":
        run(["gsettings", "reset"] + key.split("/"))
    elif shortcuts[key] == "dconf":
        run(["dconf", "reset", key])

# If val == True, disables all shortcuts.
# If val == False, resets all shortcuts.
def setkeys(flag):
    for key, val in shortcutmap.items():
        if flag == True:
            # Read current value again; user may change
            # settings, after all!
            shortcutmap[key] = readkey(key)
            writekey(key, "")            
        elif flag == False:
            if val:
                writekey(key, val)
            else:
                resetkey(key)

#
# Main script
#

# Store current shortcuts in case they are non-default
# Note: if the default is set, dconf returns an empty string!
# Optionally, create a backup script to restore the value in case
# this script crashes at an inopportune time.
shortcutmap = {}
if backupfile:
    f = open(os.path.expanduser(backupfile),'w+') 
    f.write('#!/bin/sh\n')

for key, val in shortcuts.items():
    if shortcuts[key] == "gsettings":
        shortcutmap[key] = get(["gsettings", "get"] + key.split("/"))

        if backupfile:
            if shortcutmap[key]:
                f.write("gsettings set " + " ".join(key.split("/")) + " " + 
                shortcutmap[key] + "\n")
            else:
                f.write("gsettings reset " + " ".join(key.split("/")) + "\n")
    elif shortcuts[key] == "dconf":
        shortcutmap[key] = get(["dconf", "read", key])

        if backupfile:
            if shortcutmap[key]:
                f.write("dconf write " + key + " " + shortcutmap[key] + "\n")
            else:
                f.write("dconf reset " + key + "\n")

if backupfile: f.close()

# Check every half second if the window changed form or to a 
# matching application.
front1 = None
while True:
    time.sleep(0.5)
    checkpids = get(["pgrep", "-f", apppattern])

    if checkpids:
        checkpids = checkpids.splitlines()
        activepid = getactive()
        #print(activepid)

        if activepid:
            front2 = True if activepid in checkpids else False
        else:
            front2 = False
    else:
        front2 = False

    if front2 != front1:
        #print("Matches: " + str(flag))
        if front2:
            setkeys(True)
        else:
            setkeys(False)
    front1 = front2
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 请注意gsettingsresp的不同密钥格式。dconf.
  • gsettings确实出现了,dconf但在那里所做的更改没有效果。作为一般规则,添加密钥发现使用雅各的方法gsettings和那些你不得不手动追查dconf如此。
  • 将备份文件作为脚本运行,以防快捷方式被弄乱,例如在禁用快捷方式时脚本终止。