Bloquear atalhos de teclado Unity quando um determinado aplicativo estiver ativo

5

Os grandes IDEs JetBrains (IDEA et al.) atribuem praticamente todos os atalhos de teclado concebíveis para alguma função. Embora moderadamente opressivo, às vezes, também contribui para o uso eficiente.

Meu problema é que o Unity também atribui alguns desses atalhos e eles têm precedência. Um exemplo particularmente irritante é CTRL + ALT + L . A questão foi explorada antes aqui .

No entanto, nenhuma das abordagens é satisfatória.

  1. Desativar os atalhos do sistema globalmente impede minha produtividade geral com o sistema.
  2. Mudar para um mapa de teclado diferente no IDEA me confundirá quando eu desenvolver em diferentes plataformas (e tiver que escolher diferentes mapeamentos).

Existe uma maneira de desativar os atalhos do sistema somente quando um determinado aplicativo está ativo, por exemplo, em execução e em foco?

Eu estaria disposto a executar um script toda vez que eu iniciar o aplicativo.

    
por Raphael 21.12.2016 / 09:50

2 respostas

10

Como desabilitar automaticamente vários atalhos (específicos) se (e enquanto) a janela de um aplicativo específico estiver ativa

O script abaixo desabilitará atalhos de chave específicos quando a janela de um aplicativo arbitrário estiver ativa.

Embora você tenha mencionado "" eu estaria disposto a executar um script toda vez que eu lançasse o aplicativo. ", não há razão para matar o script depois, é extremamente baixo em suco.

O script

#!/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

Como usar

  1. O script precisa de xdotool :

    sudo apt-get install xdotool
    
  2. Copie o script em um arquivo vazio, salve-o como disable_shortcuts.py

  3. Na cabeça do script, substitua na linha:

    app = "gedit"
    

    "gedit" pelo seu aplicativo, ou seja: o nome do processo que possui a janela.

  4. Teste o script com o comando:

    python3 /path/to/disable_shortcuts.py
    
  5. Se tudo funcionar bem, adicione-o aos aplicativos de inicialização: Dash & gt; Aplicativos de inicialização & gt; Adicionar. Adicione o comando:

    /bin/bash -c "sleep 15 && python3 /path/to/disable_shortcuts.py"
    

Adicionando mais atalhos a serem desativados

Como exemplo, adicionei o atalho que você mencionou: CTRL + ALT + L . Os atalhos são definidos no banco de dados dconf e podem ser definidos ou desativados usando gsettings .

No script, essas gsettings entradas são definidas na função: setkeys()

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

Um exemplo para adicionar (desabilitar) o atalho de logout:

  1. Abra uma janela de terminal, execute o comando dconf watch /
  2. Abra as configurações do sistema & gt; "Teclado" & gt; "Atalhos" & gt; "Sistema"
  3. Redefina o atalho para si mesmo. No terminal, você pode ver a chave gsettings que pertence ao atalho:

  4. Agora temos que adicionar a chave encontrada (em uma aparência ligeiramente diferente):

    ["org.gnome.settings-daemon.plugins.media-keys", "logout"]
    

    ... para a lista de "chaves" em nossa função:

    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"],
            ]
    

Agora ambos CTRL + ALT + L e CTRL + ALT + Excluir serão desativados se seu aplicativo estiver na frente.

Explicação

Como mencionado, os atalhos, como os que você mencionou, são definidos no banco de dados dconf . No exemplo CTRL + ALT + L , a chave para definir ou editar o schortcut é:

org.gnome.settings-daemon.plugins.media-keys screensaver

Para desativar a chave, o comando é:

gsettings set org.gnome.settings-daemon.plugins.media-keys screensaver ""

Para redefinir a chave para o valor padrão:

gsettings reset org.gnome.settings-daemon.plugins.media-keys screensaver

O script parece uma vez por segundo se:

  • seu aplicativo é executado em todos
  • se assim for, parece que alguma de suas janelas está ativa
  • novamente (somente) se assim for, desativa os atalhos, listados em

    # --- add the keys to be disabled below
    keys = [
        ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"],
         ["org.gnome.settings-daemon.plugins.media-keys", "logout"],
       ]
    

    ... aguardando a próxima mudança no estado.

Se a janela ativa não for mais um dos seus aplicativos, as chaves mencionadas na lista serão redefinidas para o padrão.

Nota

Como mencionado anteriormente, o fardo adicional para o processador do script é nulo. Você poderia muito bem executá-lo na inicialização, conforme explicado em "Como usar".

Afetando vários aplicativos

Como discutido nos comentários, no caso específico do OP, é útil aplicar atalhos de desativação em um grupo de aplicativos, todos residindo em um diretório.

Abaixo de uma versão para aplicar isso em todas as aplicações das quais a saída de

pgrep -f 

incluirá um diretório específico. No meu exemplo, defino o diretório /opt , portanto, se a janela ativa for uma das aplicações em /opt , os atalhos do conjunto serão desativados.


trazer uma janela de uma das aplicações em / opt to front desativará o atalho de logout

reativar o atalho se outra janela receber foco


O script

#!/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

Como usar

  1. Como o primeiro script, xdotool precisa ser instalado:

    sudo apt-get install xdotool
    
  2. Copie o script em um arquivo vazio, salve-o como disable_shortcuts.py

  3. Na cabeça do script, substitua na linha:

    appdir = "/opt"
    

    "/ opt" pelo diretório de seus aplicativos.

  4. Teste o script com o comando:

    python3 /path/to/disable_shortcuts.py
    
  5. Se tudo funcionar bem, adicione-o aos aplicativos de inicialização: Dash & gt; Aplicativos de inicialização & gt; Adicionar. Adicione o comando:

    /bin/bash -c "sleep 15 && python3 /path/to/disable_shortcuts.py"
    

A adição de outros atalhos à lista funciona de forma semelhante à versão 1 do script.

Funciona em todos os aplicativos?

Na sua resposta, você menciona:

  

xprop não revela PIDs para todas as janelas. Exemplo de falha: cronômetro.

O Windows com pid 0 (como janelas tkinter, incluindo Idle), não tem identificação de janela na saída de xprop -id .O Idle não tem nenhum atalho conflitante, embora na minha experiência. Se você encontrar algum aplicativo com pid 0 que exija desabilitar atalhos específicos, por favor mencione.

Nesse caso, uma possível fuga seria converter a saída de

xdotool getactivewindow

para hexadecimal, o formato wmctrl usa e, em seguida, procura o pid correspondente na saída de

wmctrl -lp

Embora isso parecesse a coisa mais óbvia para começar, eu não usei no script para manter o script o mais leve possível.

    
por Jacob Vlijm 21.12.2016 / 12:03
7

Baseado em (uma versão mais antiga) da resposta de Jacob Vlijm  Eu escrevi esta versão que resolve esses problemas adicionais:

  1. Honras que o usuário faz enquanto o script está sendo executado.
  2. Não redefine os valores que o usuário definiu para os padrões.
  3. Armazena um backup das configurações caso o script seja encerrado enquanto os atalhos estiverem desativados.
  4. Lida com gsettings e dconf atalhos. (Isso pode não ter sido um problema.)

Problemas abertos:

#!/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

Notas:

  • Observe os diferentes formatos de chave para gsettings resp. dconf .
  • gsettings chaves do aparecem em dconf , mas as alterações feitas não têm efeito. Como regra geral, adicione chaves encontradas usando método de Jacob como gsettings e aquelas que você teve que rastrear manualmente em dconf como tal.
  • Execute o arquivo de backup como script para o caso de os atalhos ficarem confusos. pelo script terminando enquanto os atalhos estão desativados.
por Raphael 22.12.2016 / 14:24