Блокировать сочетания клавиш Unity, когда определенное приложение активно

5

Великолепные интегрированные среды разработки JetBrains (IDEA и др.) Назначают практически все возможные сочетания клавиш для какой-либо функции. Несмотря на то, что время от времени это подавляет, оно также способствует эффективному использованию.

Моя проблема в том, что Unity также назначает некоторые из этих ярлыков, и они имеют приоритет. Один особенно раздражающий пример - CTRL+ ALT+ L. Проблема была исследована ранее здесь .

Однако ни один из подходов не является удовлетворительным.

  1. Отключение системных ярлыков в глобальном масштабе снижает общую производительность системы.
  2. Переключение на другую раскладку клавиш в IDEA будет чертовски неприятным для меня, когда я разрабатываю на разных платформах (и приходится выбирать разные сопоставления).

Есть ли способ отключить системные ярлыки только тогда, когда определенное приложение активно, то есть работает и в фокусе?

Я бы хотел запускать скрипт каждый раз, когда запускаю приложение.

Рафаэль
источник
1
связанные: askubuntu.com/questions/754884/…
Byte Commander
@ByteCommander Выглядит очень актуально, спасибо. Вы хотите расширить это в ответ?
Рафаэль
1
Нет, я не думаю, что это стоит ответа. Похоже, что связанный ответ применим только к отключению клавиши [Super]. Я ткнул оригинального автора этого ответа и уведомил его о вашем вопросе, хотя, возможно, он зайдет позже :)
Byte Commander

Ответы:

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

Как пользоваться

  1. Скрипт нуждается в xdotool:

    sudo apt-get install xdotool
  2. Скопируйте скрипт в пустой файл, сохраните его как disable_shortcuts.py

  3. В заголовке скрипта заменить в строке:

    app = "gedit"

    "gedit" вашим приложением, что означает: имя процесса, которому принадлежит окно.

  4. Протестируйте скрипт с помощью команды:

    python3 /path/to/disable_shortcuts.py
  5. Если все работает нормально, добавьте его в Startup Applications: Dash> Startup Applications> Add. Добавьте команду:

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

Добавление нескольких ярлыков для отключения

В качестве примера я добавил ярлык, который вы упомянули: 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"]
        ]
    # ---

Пример добавления (отключения) ярлыка выхода из системы:

  1. Откройте окно терминала, выполните команду dconf watch /
  2. Откройте Системные настройки> «Клавиатура»> «Ярлыки»> «Система»
  3. Переустановите ярлык на себя. В терминале вы можете увидеть gsettingsключ, который принадлежит ярлыку:

    введите описание изображения здесь

  4. Теперь мы должны добавить найденный ключ (в несколько ином виде):

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

    ... к списку "ключей" в нашей функции:

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

Теперь CTRL+ ALT+ Lи CTRL+ ALT+ Deleteотключены, если ваше приложение находится впереди.

объяснение

Как уже упоминалось, ярлыки, как те, которые вы упоминаете, устанавливаются в dconfбазе данных. В примере CTRL+ ALT+ Lключ для установки или редактирования ярлыка:

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

Чтобы отключить ключ, команда:

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

Чтобы сбросить ключ к его значению по умолчанию:

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

Сценарий выглядит один раз в секунду, если:

  • ваше приложение работает вообще
  • если это так, он смотрит, если какое-либо из его окон активно
  • снова (только), если это так, он отключает ярлыки, перечисленные в

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

    ... в ожидании следующего изменения в состоянии.

Если активное окно больше не является вашим приложением, ключи, указанные в списке, сбрасываются на значения по умолчанию.

Запись

Как уже упоминалось ранее, дополнительная нагрузка на процессор сценария ничтожна. Вы можете очень хорошо запустить его при запуске, как описано в разделе «Как использовать».


Влияние на несколько приложений

Как обсуждалось в комментариях, в конкретном случае OP полезно применять ярлыки отключения для группы приложений, все они находятся в одном каталоге.

Ниже версия, чтобы применить это ко всем приложениям, выход которых

pgrep -f 

будет включать в себя определенный каталог. В моем примере я установил /optкаталог, поэтому, если активное окно является одним из приложений /opt, установленные ярлыки будут отключены.


перенос окна одного из приложений в / opt на передний план отключит ярлык выхода из системы

введите описание изображения здесь

повторное включение ярлыка, если другое окно получает фокус

введите описание изображения здесь


Сценарий

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

Как пользоваться

  1. Как и первый скрипт, xdotoolнеобходимо установить:

    sudo apt-get install xdotool
  2. Скопируйте скрипт в пустой файл, сохраните его как disable_shortcuts.py

  3. В заголовке скрипта заменить в строке:

    appdir = "/opt"

    "/ opt" в каталоге, в котором находятся ваши приложения.

  4. Протестируйте скрипт с помощью команды:

    python3 /path/to/disable_shortcuts.py
  5. Если все работает нормально, добавьте его в Startup Applications: Dash> Startup Applications> Add. Добавьте команду:

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

Добавление других ярлыков в список работает точно так же, как в версии 1 скрипта.

Это работает на всех приложениях?

В своем ответе вы упоминаете:

xprop не показывает PID для всех окон. Неудачный пример: секундомер.

Окна с pid 0 (например, окна tkinter, включая Idle) не имеют идентификатора окна в выводе xprop -id. Idleне имеет никаких конфликтующих сочетаний клавиш, хотя по моему опыту. Если вы запустили какое-либо приложение с pid 0, которое потребовало бы отключения определенных ярлыков, пожалуйста, укажите.

В этом случае возможным побегом будет преобразовать вывод

xdotool getactivewindow

в шестнадцатеричный формат wmctrlиспользует формат , а затем ищет соответствующие pidв выходных данных

wmctrl -lp

Хотя с самого начала это казалось наиболее очевидным, я не использовал его в сценарии, чтобы максимально облегчить его выполнение.

Якоб Влейм
источник
@ Рафаэль нет, это не правильно. Второй и первый отличаются, так как первый предназначен для того, как вы изначально задали свой вопрос, а второй - для дополнительных требований в комментариях. Ты пробовал?
Джейкоб Влейм
@Raphael, некоторые из ваших исправлений включают нежелательное использование прямого редактирования dconf. Кроме того, он «исправляет» проблемы, которые вы не упомянули в своем вопросе. Прокомментирую позже. Без обид, но имо было бы достойным действием принять (этот) ответ, если он лежит в основе вашего решения.
Джейкоб Влейм
@ Рафаэль меняет вопрос после того, как ответы были опубликованы, основываясь на исходном вопросе, ну, в общем, не сделано. В комментарии я уже упоминал, почему редактирование dconf напрямую не является желаемым способом. / org / gnome / desktop / wm / keybindings / begin-move в gsettings (16.04)
Джейкоб Влейм
Привет @ Рафаэль, проблема с перечисленными значениями (как упомянуто в 1-м комментарии) исправлена. Кроме того, сценарии теперь также адаптируют ярлыки пользовательских наборов и сбрасывают их. Резервное копирование кажется мне несколько избыточным, так как простой запуск сценария установит для всех сочетаний клавиш ранее установленные ярлыки. как объяснено, использование dconf нежелательно и также не нужно с момента исправления.
Джейкоб Влейм
Наконец-то я нашел способ попробовать новую версию (версии с несколькими приложениями). К сожалению, я получаю ошибки .
Рафаэль
7

Основываясь на (более старой версии) ответа Якоба Влейма, я написал эту версию, которая решает следующие дополнительные проблемы:

  1. Уважает изменения, которые пользователь вносит во время работы скрипта.
  2. Не сбрасывает значения, которые пользователь установил на значения по умолчанию.
  3. Сохраняет резервную копию настроек на случай, если скрипт закроется, когда ярлыки отключены.
  4. Ручки gsettingsи dconfярлыки. (Это может быть не проблема.)

Открытые проблемы:


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

Примечания:

  • Обратите внимание на различные ключевые форматы для gsettingsсоотв. dconf,
  • gsettingsКлавиши же появляются , dconfно сделанные изменения не имеют никакого эффекта. Как правило, добавляйте ключи, найденные с использованием метода Джейкоба, как gsettingsи те, которые вы должны были вручную отследить dconfкак таковые.
  • Запустите файл резервной копии как сценарий на случай, если ярлыки испортились, например, из-за того, что сценарий завершился, пока ярлыки отключены.
Рафаэль
источник
Я собрал несколько полезных ярлыков для использования IDE JetBrains здесь .
Рафаэль
1
Как упоминалось ранее, dconfне следует использовать, если gsettingsдоступно, что имеет место во всех ярлыках, которые вы упоминаете. gsettingsзащищает согласованность dconfбазы данных. Проблема использовала «» для перечисленных значений. исправлено в ответе.
Якоб Влейм
Что касается комментария Джейкоба, да: все ярлыки, с которыми я пытался (с моей версией сценария) работать с gsettings. Я оставляю код как есть, но пользователи могут предпочесть gsettingsформат.
Рафаэль