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

Вопрос или проблема

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

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

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

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

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

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

Как автоматически отключать несколько (конкретных) сочетаний клавиш, если (и на сколько) активен тот или иной окон определенного приложения

Нижеприведенный скрипт отключит конкретные сочетания клавиш, когда активно окно произвольного приложения.

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

Скрипт

#!/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):
    # --- добавьте ниже клавиши для отключения  
    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. Если все работает хорошо, добавьте его в Автозапуск: Главное меню > Автозапуск > Добавить. Добавьте команду:

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

Добавление большего количества сочетаний для отключения

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

В скрипте, эти записи gsettings установлены в функции: setkeys()

def setkeys(val):
    # --- Добавьте ниже клавиши для отключения
    keys = [
        ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"]
        ]
    # ---

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

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

    enter image description here

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

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

    …в список “keys” в нашей функции:

    def setkeys(val):
        # --- Добавьте ниже клавиши для отключения
        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

Скрипт проверяет раз в секунду, если:

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

    # --- Добавьте ниже клавиши для отключения
    keys = [
        ["org.gnome.settings-daemon.plugins.media-keys", "screensaver"],
         ["org.gnome.settings-daemon.plugins.media-keys", "logout"],
       ]
    

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

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

Примечание

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


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

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

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

pgrep -f 

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

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

enter image description here

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

enter image description here

Скрипт

#!/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):
    # --- добавьте ниже клавиши для отключения  
    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. Если все работает хорошо, добавьте его в Автозапуск: Главное меню > Автозапуск > Добавить. Добавьте команду:

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

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

Работает ли это для всех приложений?

В вашем ответе вы пишете:

xprop не выдаёт PIDs для всех окон. Пример с ошибкой: stopwatch.

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

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

xdotool getactivewindow

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

wmctrl -lp

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

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

  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"])
    result = get(["xprop", "-id", xdoid])
    if not result:
        return None

    pidline = [l for l in result.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 is None:
            continue

        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, как такие.
  • Запустите резервный файл как скрипт в случае, если сочетания будут перепутаны, например, если сценарий завершится, пока сочетания отключены.

Ответ или решение

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

Теория

Проблема конфликтующих сочетаний клавиш между системой Unity и IDE JetBrains встречается довольно часто среди разработчиков, использующих Ubuntu. В большинстве случаев, Unity имеет глобальные сочетания клавиш, которые занимают приоритетное положение над предпочтениями пользователя в Intellij IDEA или других редакторах JetBrains. Это может оказаться серьезным препятствием для продуктивности, поскольку IDE JetBrains высоко ценится за свою гибкую настройку горячих клавиш, что позволяет специалистам IT эффективно выполнять свои задачи.

Одним из решений этой проблемы является временное отключение системных сочетаний клавиш, когда определенное приложение, в данном случае IDE JetBrains, активно. Основная цель заключается в том, чтобы система автоматически распознавала, когда нужно предоставлять приоритет сочетаниям клавиш IDE, без необходимости постоянного ручного переключения или изменений в системе.

Пример

Рассмотрим ситуацию, когда используется сочетание клавиш CTRL + ALT + L. В Unity это сочетание по умолчанию активирует экранызатор. Однако, если эта комбинация необходима для выполнения определенных действий в IDEA, возникает конфликт. Подобные проблемы обсуждаются на платформах вроде AskUbuntu, где пользователи ищут надежные способы решения, не жертвуя при этом общей производительностью ОС или удобством использования IDE на разных платформах.

С учетом вышеизложенных обстоятельств можно предложить скрипт на Python, который будет временно отключать конфликтующие системные сочетания клавиш, когда активна определенная программа. Этот скрипт использует инструменты xdotool и gsettings для управления состоянием клавиш в зависимости от активного окна.

Применение

Чтобы реализовать решение, вам нужно выполнить следующие шаги:

  1. Установить необходимые утилиты. Убедитесь, что на вашем компьютере установлена утилита xdotool, позволяющая управлять окнами X из командной строки.

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

  3. Настройка скрипта. Замените переменную app в начале скрипта на имя процесса, который будет фильтроваться, например, на idea для IntelliJ IDEA или аналогичного приложения, которое вы используете.

  4. Тестирование скрипта. Запустите скрипт через терминал для проверки работы.

    python3 /путь/до/disable_shortcuts.py
  5. Автоматический запуск. Если скрипт работает корректно, добавьте его в автоматический запуск при старте системы через "Startup Applications" в Dash.

    /bin/bash -c "sleep 15 && python3 /путь/до/disable_shortcuts.py"
  6. Добавление и изменение сочетаний. Если вам нужно добавить другие комбинации клавиш для отключения, выполните процедуру, описанную в скрипте, чтобы определить конфликтующие ключи в dconf, и добавьте их в функцию setkeys.

Заключение

Представленный подход дает возможность гибко управлять сочетаниями клавиш в зависимости от контекста использования приложений, повышая продуктивность работы на таких IDE, как JetBrains, без глобальных изменений в системе. Это позволяет совместить удобство использования ОС и мощь инструментов разработки, используя потенциальную мощность обоих в полной мере.

Такое решение особенно полезно для IT-специалистов и разработчиков, работающих на Ubuntu, которые ценят возможность настраивать свое рабочее пространство для повышения производительности. Основное преимущество заключается в автоматизации смены приоритетов сочетаний клавиш, что освобождает от необходимости вручную изменять системные настройки при каждом запуске IDE.

Оцените материал
Добавить комментарий

Капча загружается...