Skip to content

Commit

Permalink
ENH: Allow running Kupfer without Keybinder
Browse files Browse the repository at this point in the history
Keybinder is the only way to configure global keybindings inside kupfer
itself, but it is old technology. In Wayland and futuristic
environments, it's not available.

If you don't use Keybinder, simply configure your window manager or
equivalent to add a global keybinding that starts the `kupfer`
executable.

In fact, Keybinder will crash if used under Wayland. The users that have
it installed anyway must run Kupfer with KUPFER_NO_KEYBINDER=1 set in
the environment.
  • Loading branch information
bluss committed Feb 22, 2017
1 parent 4584d14 commit db4a2eb
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 15 deletions.
3 changes: 3 additions & 0 deletions Documentation/Manpage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ plugins (installed in the program's python package).
If *KUPFER_NO_CACHE* is set, do not load from or write to any source
cache files.

If *KUPFER_NO_KEYBINDER* is set do not use ``Keybinder`` even if it is
installed.


.. vim: ft=rst tw=72
.. this document best viewed with::
Expand Down
9 changes: 5 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,11 @@ Keybinder Module

Keybinder is a library for global keyboard shortcuts.

(Temporarily: Keybinder is required to be present. We want to fix that).
You can use kupfer without the keybinder module, for example by
assigning a global keybinding to the ``kupfer`` binary, but it not the
recommended way.
You can use kupfer without the keybinder module, for example by assigning
a window manager keybinding to the ``kupfer`` binary.

If ``Keybinder`` gi bindings are installed, the library is used. If you must
disable it without uninstalling them then see the man page.

Documentation
=============
Expand Down
2 changes: 2 additions & 0 deletions kupfer/plugin/triggers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
from kupfer.ui import uievents
from kupfer.ui import getkey_dialog
from kupfer.core import commandexec
from kupfer import plugin_support

plugin_support.check_keybinding_support()

class Trigger (RunnableLeaf):
def get_actions(self):
Expand Down
7 changes: 7 additions & 0 deletions kupfer/plugin_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ class UserNamePassword (object):
if old_pykde4:
sys.modules['PyKDE4'] = old_pykde4

def check_keybinding_support():
"""
Check if we can make global keybindings
"""
from kupfer.ui import keybindings
if not keybindings.is_available():
raise ImportError(_("Dependency '%s' is not available") % "Keybinder")

def _plugin_configuration_error(plugin, err):
pretty.print_error(__name__, err)
Expand Down
37 changes: 28 additions & 9 deletions kupfer/ui/keybindings.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import gi
from gi.repository import GObject

from kupfer import pretty
from kupfer import config

KEYBINDING_DEFAULT = 1
KEYBINDING_MAGIC = 2

KEYRANGE_RESERVED = (3, 0x1000)
KEYRANGE_TRIGGERS = (0x1000, 0x2000)

import gi
gi.require_version("Keybinder", "3.0")
from gi.repository import Keybinder as keybinder

keybinder.init()
Keybinder = None
if config.has_capability("KEYBINDER"):
try:
gi.require_version("Keybinder", "3.0")
except ValueError:
pretty.print_debug(__name__, "Keybinder 3.0 not available in gi")
else:
from gi.repository import Keybinder
Keybinder.init()
else:
pretty.print_debug(__name__, "Keybinder disabled")

_keybound_object = None
def GetKeyboundObject():
Expand All @@ -34,7 +42,7 @@ class KeyboundObject (GObject.GObject):
def __init__(self):
super(KeyboundObject, self).__init__()
def _keybinding(self, target):
time = keybinder.get_current_event_time()
time = Keybinder.get_current_event_time()
self.emit("keybinding", target, "", time)
def emit_bound_key_changed(self, keystring, is_bound):
self.emit("bound-key-changed", keystring, is_bound)
Expand All @@ -54,12 +62,20 @@ def relayed_keys(self, sender, keystring, display, timestamp):

_currently_bound = {}

def is_available():
"""
Return True if keybindings are available.
"""
return Keybinder is not None

def get_all_bound_keys():
return list(filter(bool, list(_currently_bound.values())))

def get_current_event_time():
"Return current event time as given by keybinder"
return keybinder.get_current_event_time()
if Keybinder is None:
return 0
return Keybinder.get_current_event_time()

def _register_bound_key(keystr, target):
_currently_bound[target] = keystr
Expand All @@ -74,6 +90,9 @@ def bind_key(keystr, keybinding_target=KEYBINDING_DEFAULT):
"""
keybinding_target = int(keybinding_target)

if Keybinder is None:
return False

def callback(keystr):
return GetKeyboundObject()._keybinding(keybinding_target)

Expand All @@ -84,7 +103,7 @@ def callback(keystr):
succ = True
if keystr:
try:
succ = keybinder.bind(keystr, callback)
succ = Keybinder.bind(keystr, callback)
pretty.print_debug(__name__, "binding", repr(keystr))
GetKeyboundObject().emit_bound_key_changed(keystr, True)
except KeyError as exc:
Expand All @@ -93,7 +112,7 @@ def callback(keystr):
if succ:
old_keystr = get_currently_bound_key(keybinding_target)
if old_keystr and old_keystr != keystr:
keybinder.unbind(old_keystr)
Keybinder.unbind(old_keystr)
pretty.print_debug(__name__, "unbinding", repr(old_keystr))
GetKeyboundObject().emit_bound_key_changed(old_keystr, False)
_register_bound_key(keystr, keybinding_target)
Expand Down
11 changes: 9 additions & 2 deletions kupfer/ui/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def __init__(self):
self.keybindings_list_parent = builder.get_object('keybindings_list_parent')
self.gkeybindings_list_parent = builder.get_object('gkeybindings_list_parent')
source_list_parent = builder.get_object("source_list_parent")
button_reset_keys = builder.get_object("button_reset_keys")
self.sources_list_ctrl = SourceListController(source_list_parent)

setctl = settings.GetSettingsController()
Expand Down Expand Up @@ -208,11 +209,15 @@ def make_combobox_model(combobox):
self.dirlist_parent.add(self.dir_table)
self.read_directory_settings()

# keybindings list
# global keybindings list
self.keybind_table, self.keybind_store = _create_conf_keys_list()
self.keybindings_list_parent.add(self.keybind_table)
self.keybind_table.connect("row-activated", self.on_keybindings_row_activate)
# global keybindings list
button_reset_keys.set_sensitive(keybindings.is_available())
self.keybind_table.set_sensitive(keybindings.is_available())


# kupfer interface (accelerators) keybindings list
self.gkeybind_table, self.gkeybind_store = _create_conf_keys_list()
self.gkeybindings_list_parent.add(self.gkeybind_table)
self.gkeybind_table.connect("row-activated",
Expand Down Expand Up @@ -490,6 +495,8 @@ def plugin_sidebar_update(self, plugin_id):
errstr = re.sub(import_error_pat,
import_error_localized,
errmsg, count=1)
elif issubclass(etype, ImportError):
errstr = errmsg
else:
import traceback
errstr = "".join(traceback.format_exception(*exc_info))
Expand Down

0 comments on commit db4a2eb

Please sign in to comment.