mirror of https://github.com/k0kubun/xremap
Full scratch
parent
caea9a1c2f
commit
603be104f4
@ -1,7 +0,0 @@
|
||||
/xremap
|
||||
/mruby
|
||||
tags
|
||||
TAGS
|
||||
src/x11_constants_keysymdef.inc
|
||||
src/x11_constants_X.inc
|
||||
src/x11_constants_XF86keysym.inc
|
@ -1,35 +0,0 @@
|
||||
current_dir := $(shell pwd)
|
||||
CSRCS := $(wildcard tools/xremap/*.[ch])
|
||||
MRBSRCS := $(wildcard mrblib/xremap/*.rb)
|
||||
MRBCSRCS := $(wildcard src/*.[ch])
|
||||
# Using master to apply https://github.com/mruby/mruby/pull/3192
|
||||
REVISION=0ff3ae1fbaed62010c54c43235e29cdc85da2f78
|
||||
DESTDIR := /usr/local/bin
|
||||
.PHONY: all clean install
|
||||
|
||||
all: xremap
|
||||
|
||||
clean:
|
||||
rm -rf mruby/build/host src/*.inc
|
||||
|
||||
install: xremap
|
||||
mv xremap $(DESTDIR)/xremap
|
||||
|
||||
xremap: mruby/build/host/bin/xremap
|
||||
cp mruby/build/host/bin/xremap xremap
|
||||
|
||||
mruby:
|
||||
git clone https://github.com/mruby/mruby
|
||||
git -C mruby reset --hard $(REVISION)
|
||||
|
||||
src/x11_constants_keysymdef.inc:
|
||||
cat /usr/include/X11/keysymdef.h | ruby -e 'puts STDIN.read.split("\n").select {|l| l.match(/\A(#define XK_|#ifdef|#endif)/) }.map{|l| l.match(/\A#define XK_/) ? %Q[ define_x11_const(#{l.split(" ")[1]});] : l }.join("\n")' > src/x11_constants_keysymdef.inc
|
||||
|
||||
src/x11_constants_X.inc:
|
||||
cat /usr/include/X11/X.h | ruby -e 'puts STDIN.read.split("\n").select {|l| l.start_with?("#")}[2..-2].map{|l| l.start_with?("#define") ? %Q[ define_x11_const(#{l.split(" ")[1]});] : l}' > src/x11_constants_X.inc
|
||||
|
||||
src/x11_constants_XF86keysym.inc:
|
||||
cat /usr/include/X11/XF86keysym.h | ruby -e 'puts STDIN.read.split("\n").select {|l| l.match(/\A(#define XF86XK_)/) }.map{|l| l.match(/\A#define XF*86XK_/) ? %Q[ define_x11_const(#{l.split(" ")[1]});] : l }.join("\n")' > src/x11_constants_XF86keysym.inc
|
||||
|
||||
mruby/build/host/bin/xremap: mruby build_config.rb src/x11_constants_keysymdef.inc src/x11_constants_X.inc src/x11_constants_XF86keysym.inc $(CSRCS) $(MRBSRCS) $(MRBCSRCS)
|
||||
cd mruby && MRUBY_CONFIG="$(current_dir)/build_config.rb" make
|
@ -1,120 +0,0 @@
|
||||
# xremap
|
||||
|
||||
Dynamic key remapper for X Window System
|
||||
|
||||
## Description
|
||||
|
||||
xremap is a key remapper for X Window System.
|
||||
With xremap's Ruby DSL, you can simply write configuration of key bindings.
|
||||
|
||||
```rb
|
||||
remap 'C-b', to: 'Left'
|
||||
```
|
||||
|
||||
And you can configure application-specific key bindings,
|
||||
which is dynamically applied based on a current window.
|
||||
|
||||
```rb
|
||||
window class_only: 'slack' do
|
||||
remap 'Alt-k', to: 'Alt-Up'
|
||||
remap 'Alt-j', to: 'Alt-Down'
|
||||
end
|
||||
```
|
||||
|
||||
While xremap's configuration is written in Ruby, you can run xremap without Ruby installation
|
||||
because it embeds mruby to evaluate configuration.
|
||||
|
||||
## Installation
|
||||
|
||||
### Build dependencies
|
||||
|
||||
- ruby
|
||||
- bison
|
||||
- libx11-dev
|
||||
|
||||
While ruby is not runtime dependency for xremap, mruby embedded in xremap requires ruby to build.
|
||||
|
||||
### From source code
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/k0kubun/xremap
|
||||
$ cd xremap
|
||||
$ make
|
||||
$ sudo make install # or `make DESTDIR=~/bin install`
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ xremap /path/to/config
|
||||
```
|
||||
|
||||
See [examples](./examples) to write config file.
|
||||
|
||||
### Emacs-like bindings
|
||||
|
||||
```rb
|
||||
window class_not: 'urxvt' do
|
||||
remap 'C-b', to: 'Left'
|
||||
remap 'C-f', to: 'Right'
|
||||
remap 'C-p', to: 'Up'
|
||||
remap 'C-n', to: 'Down'
|
||||
|
||||
remap 'M-b', to: 'Ctrl-Left'
|
||||
remap 'M-f', to: 'Ctrl-Right'
|
||||
|
||||
remap 'C-a', to: 'Home'
|
||||
remap 'C-e', to: 'End'
|
||||
|
||||
remap 'C-k', to: ['Shift-End', 'Ctrl-x']
|
||||
|
||||
remap 'C-d', to: 'Delete'
|
||||
remap 'M-d', to: 'Ctrl-Delete'
|
||||
end
|
||||
```
|
||||
|
||||
### Simulate macOS's command key
|
||||
|
||||
Following configuration works fine with above Emacs-like bindings.
|
||||
|
||||
```rb
|
||||
%w[a z x c v w t].each do |key|
|
||||
remap "Alt-#{key}", to: "C-#{key}"
|
||||
end
|
||||
```
|
||||
|
||||
### Application launcher
|
||||
|
||||
You can start an application by a shortcut key.
|
||||
See [examples/window\_switcher](examples/window_switcher.rb) too.
|
||||
|
||||
```rb
|
||||
remap 'C-o', to: execute('nocturn')
|
||||
remap 'C-u', to: execute('google-chrome-stable')
|
||||
remap 'C-h', to: execute('urxvt')
|
||||
```
|
||||
|
||||
### Application-specific key bindings
|
||||
|
||||
See xremap's stdout to find a window class name of your application.
|
||||
|
||||
```rb
|
||||
window class_only: 'slack' do
|
||||
remap 'Alt-n', to: 'Ctrl-k'
|
||||
remap 'Alt-k', to: 'Alt-Up'
|
||||
remap 'Alt-j', to: 'Alt-Down'
|
||||
remap 'Ctrl-Alt-k', to: 'Alt-Shift-Up'
|
||||
remap 'Ctrl-Alt-j', to: 'Alt-Shift-Down'
|
||||
end
|
||||
```
|
||||
|
||||
## Note
|
||||
|
||||
xremap is designed to have similar functionality with
|
||||
[Karabiner](https://github.com/tekezo/Karabiner) and
|
||||
[karabiner-dsl](https://github.com/k0kubun/karabiner-dsl)
|
||||
for Linux environments.
|
||||
|
||||
## Author
|
||||
|
||||
Takashi Kokubun
|
@ -1,26 +0,0 @@
|
||||
MRuby::Build.new do |conf|
|
||||
toolchain :gcc
|
||||
|
||||
conf.gembox 'default'
|
||||
conf.gem File.expand_path(File.dirname(__FILE__))
|
||||
|
||||
conf.instance_eval do
|
||||
# Allow showing backtrace.
|
||||
@mrbc.compile_options += ' -g'
|
||||
end
|
||||
|
||||
conf.cc do |cc|
|
||||
cc.include_paths += %w(/opt/X11/include)
|
||||
|
||||
# Never support Visual C++.
|
||||
# https://github.com/mruby/mruby/blob/1.2.0/CONTRIBUTING.md#comply-with-c99-isoiec-98991999
|
||||
(cc.flags.first.is_a?(String) ? cc.flags : cc.flags.first).reject! do |flag|
|
||||
flag == '-Wdeclaration-after-statement'
|
||||
end
|
||||
end
|
||||
|
||||
conf.linker do |linker|
|
||||
linker.libraries += %w(X11)
|
||||
linker.library_paths += %w(/opt/X11/lib)
|
||||
end
|
||||
end
|
@ -1,5 +0,0 @@
|
||||
# Remap Asus ROG keyboards to Fn+Arrow keys to more useful bindings
|
||||
remap 'XF86XK_AudioPrev', to: 'Home'
|
||||
remap 'XF86XK_AudioNext', to: 'End'
|
||||
remap 'XF86XK_AudioPlay', to: 'Page_Down'
|
||||
remap 'XF86XK_AudioStop', to: 'Page_Up'
|
@ -1,31 +0,0 @@
|
||||
window class_not: 'urxvt' do
|
||||
# emacs-like bindings
|
||||
remap 'C-b', to: 'Left'
|
||||
remap 'C-f', to: 'Right'
|
||||
remap 'C-p', to: 'Up'
|
||||
remap 'C-n', to: 'Down'
|
||||
|
||||
remap 'M-b', to: 'Ctrl-Left'
|
||||
remap 'M-f', to: 'Ctrl-Right'
|
||||
|
||||
remap 'C-a', to: 'Home'
|
||||
remap 'C-e', to: 'End'
|
||||
|
||||
remap 'C-k', to: ['Shift-End', 'Ctrl-x']
|
||||
|
||||
remap 'C-d', to: 'Delete'
|
||||
remap 'M-d', to: 'Ctrl-Delete'
|
||||
|
||||
remap 'M-w', to: 'Ctrl-c'
|
||||
remap 'C-y', to: 'Ctrl-v'
|
||||
remap 'C-w', to: 'Ctrl-x'
|
||||
|
||||
remap 'C-v', to: 'Page_Down'
|
||||
remap 'M-v', to: 'Page_Up'
|
||||
|
||||
remap 'C-s', to: 'Ctrl-f'
|
||||
|
||||
# actually these are vim insert mode bindings, but compatible with shell
|
||||
remap 'C-u', to: ['Shift-Home', 'Ctrl-x']
|
||||
remap 'C-w', to: ['Ctrl-Shift-Left', 'Ctrl-x']
|
||||
end
|
@ -1,3 +0,0 @@
|
||||
remap 'C-o', to: execute('nocturn')
|
||||
remap 'C-u', to: execute('google-chrome-stable')
|
||||
remap 'C-h', to: execute('urxvt')
|
@ -1,9 +0,0 @@
|
||||
%w[a z x c v w t].each do |key|
|
||||
remap "Alt-#{key}", to: "C-#{key}"
|
||||
end
|
||||
|
||||
window class_only: 'google-chrome' do
|
||||
%w[f l].each do |key|
|
||||
remap "Alt-#{key}", to: "C-#{key}"
|
||||
end
|
||||
end
|
@ -1,3 +0,0 @@
|
||||
include_config 'emacs_like'
|
||||
include_config 'slack'
|
||||
include_config 'window_switcher'
|
@ -1,7 +0,0 @@
|
||||
window class_only: 'slack' do
|
||||
remap 'Alt-n', to: 'Ctrl-k'
|
||||
remap 'Alt-k', to: 'Alt-Up'
|
||||
remap 'Alt-j', to: 'Alt-Down'
|
||||
remap 'Ctrl-Alt-k', to: 'Alt-Shift-Up'
|
||||
remap 'Ctrl-Alt-j', to: 'Alt-Shift-Down'
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
# 1. Copy this to ~/.config/systemd/user/xremap.service
|
||||
# 2. systemctl --user enable xremap
|
||||
#
|
||||
# Note that you need to set proper $DISPLAY on your environment.
|
||||
|
||||
[Unit]
|
||||
Description=xremap
|
||||
|
||||
[Service]
|
||||
KillMode=process
|
||||
ExecStart=/usr/bin/xremap /home/k0kubun/.xremap
|
||||
Type=simple
|
||||
Restart=always
|
||||
|
||||
# Update DISPLAY to be the same as `echo $DISPLAY` on your graphical terminal.
|
||||
Environment=DISPLAY=:1
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
@ -1,8 +0,0 @@
|
||||
define :activate do |wm_class, command|
|
||||
execute("wmctrl -x -a #{wm_class.shellescape} || #{command.shellescape}")
|
||||
end
|
||||
|
||||
# Check WM_CLASS by wmctrl -x -l
|
||||
remap 'C-o', to: activate('nocturn.Nocturn', '/usr/share/nocturn/Nocturn')
|
||||
remap 'C-u', to: activate('google-chrome.Google-chrome', '/opt/google/chrome/chrome')
|
||||
remap 'C-h', to: activate('urxvt.URxvt', 'urxvt')
|
@ -1,14 +0,0 @@
|
||||
MRuby::Gem::Specification.new('xremap') do |spec|
|
||||
spec.license = 'MIT'
|
||||
spec.author = 'Takashi Kokubun'
|
||||
spec.summary = 'Dynamic key remapper for X Window System'
|
||||
spec.bins = ['xremap']
|
||||
|
||||
spec.add_dependency 'mruby-eval', core: 'mruby-eval'
|
||||
|
||||
spec.add_dependency 'mruby-env', mgem: 'mruby-env'
|
||||
spec.add_dependency 'mruby-io', mgem: 'mruby-io'
|
||||
spec.add_dependency 'mruby-process', mgem: 'mruby-process'
|
||||
spec.add_dependency 'mruby-onig-regexp', mgem: 'mruby-onig-regexp'
|
||||
spec.add_dependency 'mruby-shellwords', mgem: 'mruby-shellwords'
|
||||
end
|
@ -1,26 +0,0 @@
|
||||
module Xremap
|
||||
class ActiveWindow
|
||||
# @param [Fixnum] current_window
|
||||
attr_reader :current_window
|
||||
|
||||
# @param [Xremap::Display] display
|
||||
def initialize(display)
|
||||
@display = display
|
||||
@current_window = fetch_active_window
|
||||
end
|
||||
|
||||
def changed?
|
||||
next_window = fetch_active_window
|
||||
@current_window != next_window
|
||||
ensure
|
||||
@current_window = next_window
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_active_window
|
||||
sleep ENV.fetch('XREMAP_DELAY', '0.1').to_f
|
||||
XlibWrapper.fetch_active_window(@display)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,63 +0,0 @@
|
||||
module Xremap
|
||||
class Config
|
||||
# FIXME: :to_keys should be :to_actions, and Key and Execute should be adapted.
|
||||
Remap = Struct.new(:from_key, :to_keys)
|
||||
Execute = Struct.new(:command, :action)
|
||||
|
||||
class Key < Struct.new(:keysym, :modifier, :action)
|
||||
def initialize(*)
|
||||
super
|
||||
self.action ||= :input
|
||||
end
|
||||
end
|
||||
|
||||
class Window < Struct.new(:class_only, :class_not)
|
||||
def initialize(*)
|
||||
super
|
||||
self.class_only = self.class_only ? Array(self.class_only) : []
|
||||
self.class_not = self.class_not ? Array(self.class_not) : []
|
||||
end
|
||||
end
|
||||
|
||||
AnyWindow = Window.new
|
||||
|
||||
# @param [String] filename
|
||||
def self.load(filename)
|
||||
unless File.exist?(filename)
|
||||
raise "Config file does not exist!: #{filename.inspect}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
config_dir = File.dirname(File.expand_path(filename))
|
||||
config = self.new(config_dir)
|
||||
ConfigDSL.new(config).instance_eval(File.read(filename))
|
||||
config
|
||||
end
|
||||
|
||||
attr_reader :remaps_by_window, :config_dir
|
||||
|
||||
def initialize(config_dir)
|
||||
@remaps_by_window = Hash.new { |h, k| h[k] = [] }
|
||||
@config_dir = config_dir
|
||||
end
|
||||
|
||||
def remaps_for(display, window)
|
||||
klass = XlibWrapper.fetch_window_class(display, window)
|
||||
remaps_by_window[AnyWindow] + class_specific_remaps(klass)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def class_specific_remaps(klass)
|
||||
@remaps_by_window.select do |window, _|
|
||||
if !window.class_only.empty?
|
||||
window.class_only.include?(klass)
|
||||
elsif !window.class_not.empty?
|
||||
!window.class_not.include?(klass)
|
||||
else
|
||||
false
|
||||
end
|
||||
end.map { |_, remaps| remaps }.flatten
|
||||
end
|
||||
end
|
||||
end
|
@ -1,65 +0,0 @@
|
||||
module Xremap
|
||||
class ConfigDSL
|
||||
# @param [Xremap::Config] config
|
||||
def initialize(config, win = Config::AnyWindow)
|
||||
@config = config
|
||||
@window = win
|
||||
end
|
||||
|
||||
def remap(from_str, options = {})
|
||||
# Array() doesn't work for Config::Execute somehow.
|
||||
to_strs = options.fetch(:to)
|
||||
to_strs = [to_strs] unless to_strs.is_a?(Array)
|
||||
|
||||
@config.remaps_by_window[@window] << Config::Remap.new(
|
||||
compile_exp(from_str),
|
||||
to_strs.map { |str| compile_exp(str) }
|
||||
)
|
||||
end
|
||||
|
||||
def window(options = {}, &block)
|
||||
win = Config::Window.new(options[:class_only], options[:class_not])
|
||||
ConfigDSL.new(@config, win).instance_exec(&block)
|
||||
end
|
||||
|
||||
def execute(str)
|
||||
Config::Execute.new(str, :execute)
|
||||
end
|
||||
|
||||
def press(str)
|
||||
key = compile_exp(str)
|
||||
key.action = :press
|
||||
key
|
||||
end
|
||||
|
||||
def release(str)
|
||||
key = compile_exp(str)
|
||||
key.action = :release
|
||||
key
|
||||
end
|
||||
|
||||
def define(name, &block)
|
||||
ConfigDSL.define_method(name, &block)
|
||||
end
|
||||
|
||||
def include_config(filename)
|
||||
path = File.expand_path(filename, @config.config_dir)
|
||||
path << '.rb' unless path.start_with?('.rb')
|
||||
raise "config file not found!: #{path.inspect}" unless File.exist?(path)
|
||||
instance_eval(File.read(path))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def compile_exp(exp)
|
||||
case exp
|
||||
when Config::Key, Config::Execute
|
||||
exp
|
||||
when String
|
||||
KeyExpression.compile(exp)
|
||||
else
|
||||
raise "unexpected expression: #{exp.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,39 +0,0 @@
|
||||
module Xremap
|
||||
class EventHandler
|
||||
# @param [Xremap::Config] config
|
||||
# @param [Xremap::Display] display
|
||||
def initialize(config, display)
|
||||
@active_window = ActiveWindow.new(display)
|
||||
@grab_manager = GrabManager.new(config, display)
|
||||
@key_remap_compiler = KeyRemapCompiler.new(config, display)
|
||||
remap_keys
|
||||
end
|
||||
|
||||
def handle_key_press(keycode, state)
|
||||
handler = @key_press_handlers[keycode][state]
|
||||
if handler
|
||||
handler.call
|
||||
else
|
||||
$stderr.puts "Handler not found!: #{[keycode, state, @key_press_handlers].inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def handle_property_notify
|
||||
if @active_window.changed?
|
||||
remap_keys
|
||||
end
|
||||
end
|
||||
|
||||
def handle_mapping_notify
|
||||
remap_keys
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def remap_keys
|
||||
window = @active_window.current_window
|
||||
@key_press_handlers = @key_remap_compiler.compile_for(window)
|
||||
@grab_manager.grab_keys_for(window)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,25 +0,0 @@
|
||||
module Xremap
|
||||
class GrabManager
|
||||
# @param [Xremap::Config] config
|
||||
# @param [Xremap::Display] display
|
||||
def initialize(config, display)
|
||||
@config = config
|
||||
@display = display
|
||||
end
|
||||
|
||||
def grab_keys_for(window)
|
||||
XlibWrapper.ungrab_keys(@display)
|
||||
|
||||
# guard segmentation fault
|
||||
return if window == 0
|
||||
|
||||
@config.remaps_for(@display, window).each do |remap|
|
||||
from = remap.from_key
|
||||
XlibWrapper.grab_key(@display, from.keysym, from.modifier)
|
||||
end
|
||||
|
||||
# TODO: remove this log
|
||||
puts "remapped for class: #{XlibWrapper.fetch_window_class(@display, window).inspect}"
|
||||
end
|
||||
end
|
||||
end
|
@ -1,49 +0,0 @@
|
||||
module Xremap
|
||||
module KeyExpression
|
||||
class << self
|
||||
# @param [String] exp
|
||||
# @return [Xremap::Config::Key] key
|
||||
def compile(exp)
|
||||
keyexp, modifiers = split_into_key_and_mods(exp)
|
||||
Config::Key.new(to_keysym(keyexp), modifier_mask(modifiers))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def split_into_key_and_mods(exp)
|
||||
modifiers = []
|
||||
while exp.match(/\A(?<modifier>(C|Ctrl|M|Alt|Shift|Super|Win))-/)
|
||||
modifier = Regexp.last_match[:modifier]
|
||||
modifiers << modifier
|
||||
exp = exp.sub(/\A#{modifier}-/, '')
|
||||
end
|
||||
[exp, modifiers]
|
||||
end
|
||||
|
||||
def modifier_mask(modifiers)
|
||||
mask = X11::NoModifier
|
||||
modifiers.each do |modifier|
|
||||
case modifier
|
||||
when 'C', 'Ctrl'
|
||||
mask |= X11::ControlMask
|
||||
when 'M', 'Alt'
|
||||
mask |= X11::Mod1Mask
|
||||
when 'Super', 'Win'
|
||||
mask |= X11::Mod4Mask
|
||||
when 'Shift'
|
||||
mask |= X11::ShiftMask
|
||||
end
|
||||
end
|
||||
mask
|
||||
end
|
||||
|
||||
def to_keysym(keyexp)
|
||||
if keyexp.start_with?('XF86XK_')
|
||||
X11.const_get(keyexp)
|
||||
else
|
||||
X11.const_get("XK_#{keyexp}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -1,55 +0,0 @@
|
||||
module Xremap
|
||||
class KeyRemapCompiler
|
||||
def initialize(config, display)
|
||||
@config = config
|
||||
@display = display
|
||||
puts "Config loaded: #{@config.inspect}"
|
||||
end
|
||||
|
||||
# @return [Hash] : keycode(Fixnum) -> state(Fixnum) -> handler(Proc)
|
||||
def compile_for(window)
|
||||
result = Hash.new { |h, k| h[k] = {} }
|
||||
|
||||
# guard segmentation fault
|
||||
return result if window == 0
|
||||
|
||||
set_handlers_for(result, window)
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_handlers_for(result, window)
|
||||
@config.remaps_for(@display, window).each do |remap|
|
||||
from = remap.from_key
|
||||
tos = remap.to_keys
|
||||
|
||||
actions = remap.to_keys.map do |to|
|
||||
case to.action
|
||||
when :input
|
||||
Proc.new { XlibWrapper.input_key(@display, to.keysym, to.modifier) }
|
||||
when :press
|
||||
Proc.new { XlibWrapper.press_key(@display, to.keysym, to.modifier) }
|
||||
when :release
|
||||
Proc.new { XlibWrapper.release_key(@display, to.keysym, to.modifier) }
|
||||
when :execute
|
||||
Proc.new { system("nohup /bin/sh -c #{to.command.shellescape} >/dev/null 2>&1 &") }
|
||||
else
|
||||
raise "unexpected action: #{to.action.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
result[to_keycode(from.keysym)][from.modifier] =
|
||||
if actions.length == 1
|
||||
actions.first
|
||||
else
|
||||
Proc.new { actions.each { |action| action.call } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to_keycode(keysym)
|
||||
XlibWrapper.keysym_to_keycode(@display, keysym)
|
||||
end
|
||||
end
|
||||
end
|
@ -1,45 +0,0 @@
|
||||
#include <X11/Xlib.h>
|
||||
#include "mruby.h"
|
||||
#include "mruby/data.h"
|
||||
|
||||
struct mrb_display {
|
||||
Display *display;
|
||||
};
|
||||
|
||||
static void
|
||||
mrb_display_free(mrb_state *mrb, void *ptr)
|
||||
{
|
||||
struct mrb_display *display = (struct mrb_display *)ptr;
|
||||
if (display != NULL) {
|
||||
mrb_free(mrb, display);
|
||||
}
|
||||
}
|
||||
|
||||
struct mrb_data_type mrb_display_type = { "Display", mrb_display_free };
|
||||
|
||||
mrb_value
|
||||
mrb_wrap_x_display(mrb_state *mrb, Display *display)
|
||||
{
|
||||
struct RClass *mXremap = mrb_module_get(mrb, "Xremap");
|
||||
struct RClass *cDisplay = mrb_class_get_under(mrb, mXremap, "Display");
|
||||
|
||||
struct mrb_display *display_ptr = (struct mrb_display *)mrb_malloc(mrb, sizeof(struct mrb_display));
|
||||
display_ptr->display = display;
|
||||
mrb_value display_obj = mrb_obj_value(mrb_data_object_alloc(mrb, cDisplay, NULL, &mrb_display_type));
|
||||
DATA_TYPE(display_obj) = &mrb_display_type;
|
||||
DATA_PTR(display_obj) = display_ptr;
|
||||
return display_obj;
|
||||
}
|
||||
|
||||
Display*
|
||||
extract_x_display(mrb_state *mrb, mrb_value display_obj)
|
||||
{
|
||||
struct mrb_display *display_ptr = (struct mrb_display *)mrb_get_datatype(mrb, display_obj, &mrb_display_type);
|
||||
return display_ptr->display;
|
||||
}
|
||||
|
||||
void
|
||||
mrb_xremap_display_init(mrb_state *mrb, struct RClass *mXremap)
|
||||
{
|
||||
mrb_define_class_under(mrb, mXremap, "Display", mrb->object_class);
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/XF86keysym.h>
|
||||
#include <X11/X.h>
|
||||
#include "mruby.h"
|
||||
|
||||
void
|
||||
mrb_xremap_x11_constants_init(mrb_state *mrb, struct RClass *mXremap)
|
||||
{
|
||||
struct RClass *mX11 = mrb_define_module_under(mrb, mXremap, "X11");
|
||||
# define define_x11_const(name) mrb_define_const(mrb, mX11, #name, mrb_fixnum_value(name))
|
||||
|
||||
// original constant.
|
||||
mrb_define_const(mrb, mX11, "NoModifier", mrb_fixnum_value(0));
|
||||
#include "x11_constants_keysymdef.inc"
|
||||
#include "x11_constants_X.inc"
|
||||
#include "x11_constants_XF86keysym.inc"
|
||||
}
|
@ -1,206 +0,0 @@
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#include "mruby.h"
|
||||
|
||||
extern Display* extract_x_display(mrb_state *mrb, mrb_value display_obj);
|
||||
|
||||
mrb_value
|
||||
mrb_xw_fetch_window_class(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_int window;
|
||||
mrb_get_args(mrb, "oi", &display_obj, &window);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
Atom net_wm_name = XInternAtom(display, "WM_CLASS", True);
|
||||
|
||||
XTextProperty prop;
|
||||
|
||||
// XGetTextProperty can return 0 when xmonad's workspace is selected, while it usually returns 1.
|
||||
// This prevents SEGV in such a situation.
|
||||
if (XGetTextProperty(display, window, &prop, net_wm_name) == 0) {
|
||||
return display_obj;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
if (XGetTextProperty(display, window, &prop, net_wm_name)) { break; }
|
||||
|
||||
unsigned int nchildren;
|
||||
Window root, parent, *children;
|
||||
|
||||
if (!XQueryTree(display, window, &root, &parent, &children, &nchildren)) { break; }
|
||||
if (children) { XFree(children); }
|
||||
window = parent;
|
||||
}
|
||||
|
||||
mrb_value ret;
|
||||
if (prop.nitems > 0 && prop.value) {
|
||||
if (prop.encoding == XA_STRING) {
|
||||
ret = mrb_str_new_cstr(mrb, (char *)prop.value);
|
||||
} else {
|
||||
char **l = NULL;
|
||||
int count;
|
||||
XmbTextPropertyToTextList(display, &prop, &l, &count);
|
||||
if (count > 0 && *l) {
|
||||
ret = mrb_str_new_cstr(mrb, *l);
|
||||
} else {
|
||||
ret = mrb_str_new_cstr(mrb, "");
|
||||
}
|
||||
XFreeStringList(l);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
Window
|
||||
get_focused_window(Display *display)
|
||||
{
|
||||
Window window;
|
||||
int focus_state;
|
||||
|
||||
XGetInputFocus(display, &window, &focus_state);
|
||||
return window;
|
||||
}
|
||||
|
||||
XKeyEvent
|
||||
create_key_event(Display *display, Window window, KeySym keysym, unsigned int modifiers, int type)
|
||||
{
|
||||
return (XKeyEvent){
|
||||
.display = display,
|
||||
.window = window,
|
||||
.root = XDefaultRootWindow(display),
|
||||
.subwindow = None,
|
||||
.time = CurrentTime,
|
||||
.x = 1,
|
||||
.y = 1,
|
||||
.x_root = 1,
|
||||
.y_root = 1,
|
||||
.same_screen = True,
|
||||
.keycode = XKeysymToKeycode(display, keysym),
|
||||
.state = modifiers,
|
||||
.type = type,
|
||||
};
|
||||
}
|
||||
|
||||
void
|
||||
send_press_event(Display *display, Window window, KeySym keysym, unsigned int modifiers)
|
||||
{
|
||||
XKeyEvent event = create_key_event(display, window, keysym, modifiers, KeyPress);
|
||||
XSendEvent(display, window, True, KeyPressMask, (XEvent *)&event);
|
||||
}
|
||||
|
||||
void
|
||||
send_release_event(Display *display, Window window, KeySym keysym, unsigned int modifiers)
|
||||
{
|
||||
XKeyEvent event = create_key_event(display, window, keysym, modifiers, KeyRelease);
|
||||
XSendEvent(display, window, True, KeyReleaseMask, (XEvent *)&event);
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_xw_press_key(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_int keysym, modifiers;
|
||||
mrb_get_args(mrb, "oii", &display_obj, &keysym, &modifiers);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
Window window = get_focused_window(display);
|
||||
|
||||
send_press_event(display, window, keysym, modifiers);
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_xw_release_key(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_int keysym, modifiers;
|
||||
mrb_get_args(mrb, "oii", &display_obj, &keysym, &modifiers);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
Window window = get_focused_window(display);
|
||||
|
||||
send_release_event(display, window, keysym, modifiers);
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_xw_input_key(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_int keysym, modifiers;
|
||||
mrb_get_args(mrb, "oii", &display_obj, &keysym, &modifiers);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
Window window = get_focused_window(display);
|
||||
|
||||
send_press_event(display, window, keysym, modifiers);
|
||||
send_release_event(display, window, keysym, modifiers);
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_xw_keysym_to_keycode(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_int keysym;
|
||||
mrb_get_args(mrb, "oi", &display_obj, &keysym);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
return mrb_fixnum_value(XKeysymToKeycode(display, keysym));
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_xw_fetch_active_window(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_int keycode, state;
|
||||
mrb_get_args(mrb, "o", &display_obj, &keycode, &state);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
return mrb_fixnum_value(get_focused_window(display));
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_xw_grab_key(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_int keycode, state;
|
||||
mrb_get_args(mrb, "oii", &display_obj, &keycode, &state);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
XGrabKey(display, XKeysymToKeycode(display, keycode), state, XDefaultRootWindow(display), True, GrabModeAsync, GrabModeAsync);
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
mrb_value
|
||||
mrb_xw_ungrab_keys(mrb_state *mrb, mrb_value self)
|
||||
{
|
||||
mrb_value display_obj;
|
||||
mrb_get_args(mrb, "o", &display_obj);
|
||||
|
||||
Display *display = extract_x_display(mrb, display_obj);
|
||||
XUngrabKey(display, AnyKey, AnyModifier, XDefaultRootWindow(display));
|
||||
|
||||
return mrb_nil_value();
|
||||
}
|
||||
|
||||
void
|
||||
mrb_xremap_xlib_wrapper_init(mrb_state *mrb, struct RClass *mXremap)
|
||||
{
|
||||
struct RClass *cXlibWrapper = mrb_define_class_under(mrb, mXremap, "XlibWrapper", mrb->object_class);
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "input_key", mrb_xw_input_key, MRB_ARGS_REQ(3));
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "press_key", mrb_xw_press_key, MRB_ARGS_REQ(3));
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "release_key", mrb_xw_release_key, MRB_ARGS_REQ(3));
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "keysym_to_keycode", mrb_xw_keysym_to_keycode, MRB_ARGS_REQ(2));
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "fetch_active_window", mrb_xw_fetch_active_window, MRB_ARGS_REQ(1));
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "fetch_window_class", mrb_xw_fetch_window_class, MRB_ARGS_REQ(2));
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "grab_key", mrb_xw_grab_key, MRB_ARGS_REQ(3));
|
||||
mrb_define_class_method(mrb, cXlibWrapper, "ungrab_keys", mrb_xw_ungrab_keys, MRB_ARGS_REQ(1));
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
#include "mruby.h"
|
||||
|
||||
extern void mrb_xremap_xlib_wrapper_init(mrb_state *mrb, struct RClass *mXremap);
|
||||
extern void mrb_xremap_display_init(mrb_state *mrb, struct RClass *mXremap);
|
||||
extern void mrb_xremap_x11_constants_init(mrb_state *mrb, struct RClass *mXremap);
|
||||
|
||||
void
|
||||
mrb_xremap_gem_init(mrb_state *mrb)
|
||||
{
|
||||
struct RClass *mXremap = mrb_define_module(mrb, "Xremap");
|
||||
|
||||
mrb_xremap_xlib_wrapper_init(mrb, mXremap);
|
||||
mrb_xremap_display_init(mrb, mXremap);
|
||||
mrb_xremap_x11_constants_init(mrb, mXremap);
|
||||
mrb_gc_arena_restore(mrb, 0);
|
||||
}
|
||||
|
||||
void
|
||||
mrb_xremap_gem_final(mrb_state *mrb)
|
||||
{
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <mruby.h>
|
||||
|
||||
mrb_value
|
||||
load_config(mrb_state *mrb, char *filename)
|
||||
{
|
||||
struct RClass *mXremap = mrb_module_get(mrb, "Xremap");
|
||||
struct RClass *cConfig = mrb_class_get_under(mrb, mXremap, "Config");
|
||||
|
||||
mrb_value config = mrb_funcall(mrb, mrb_obj_value(cConfig), "load", 1, mrb_str_new_cstr(mrb, filename));
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
return config;
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
#include <X11/Xlib.h>
|
||||
#include <mruby.h>
|
||||
|
||||
extern mrb_value mrb_wrap_x_display(mrb_state *mrb, Display *display);
|
||||
|
||||
mrb_value
|
||||
new_event_handler(mrb_state *mrb, mrb_value config, Display *display)
|
||||
{
|
||||
struct RClass *mXremap = mrb_module_get(mrb, "Xremap");
|
||||
struct RClass *cEventHandler = mrb_class_get_under(mrb, mXremap, "EventHandler");
|
||||
mrb_value display_obj = mrb_wrap_x_display(mrb, display);
|
||||
return mrb_funcall(mrb, mrb_obj_value(cEventHandler), "new", 2, config, display_obj);
|
||||
}
|
||||
|
||||
void
|
||||
handle_key_press(mrb_state *mrb, mrb_value event_handler, unsigned int keycode, unsigned int state)
|
||||
{
|
||||
mrb_funcall(mrb, event_handler, "handle_key_press", 2,
|
||||
mrb_fixnum_value(keycode), mrb_fixnum_value(state));
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_property_notify(mrb_state *mrb, mrb_value event_handler)
|
||||
{
|
||||
mrb_funcall(mrb, event_handler, "handle_property_notify", 0);
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
handle_mapping_notify(mrb_state *mrb, mrb_value event_handler)
|
||||
{
|
||||
mrb_funcall(mrb, event_handler, "handle_mapping_notify", 0);
|
||||
if (mrb->exc) {
|
||||
mrb_print_error(mrb);
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <signal.h>
|
||||
#include <inttypes.h>
|
||||
#include <mruby.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include "xremap.h"
|
||||
|
||||
void
|
||||
print_client_message_event(XClientMessageEvent *event)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"received ClientMesssage(message_type=%" PRIu32 " format=%d data=%#lx, %#lx, %#lx, %#lx, %#lx)",
|
||||
(uint32_t)event->message_type,
|
||||
event->format,
|
||||
(unsigned long)event->data.l[0],
|
||||
(unsigned long)event->data.l[1],
|
||||
(unsigned long)event->data.l[2],
|
||||
(unsigned long)event->data.l[3],
|
||||
(unsigned long)event->data.l[4]);
|
||||
|
||||
}
|
||||
|
||||
int
|
||||
error_handler(Display *display, XErrorEvent *event)
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
if (!XGetErrorText(display, event->error_code, buffer, sizeof(buffer))) {
|
||||
buffer[0] = '\0';
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"error detected! XErrorEvent(serial=%ld error_code=%d request_code=%d minor_code=%d text=%s)\n",
|
||||
event->serial,
|
||||
event->error_code,
|
||||
event->request_code,
|
||||
event->minor_code,
|
||||
buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
event_loop(Display *display, mrb_state *mrb, mrb_value event_handler)
|
||||
{
|
||||
XEvent event;
|
||||
while (1) {
|
||||
XNextEvent(display, &event);
|
||||
switch (event.type) {
|
||||
case KeyPress:
|
||||
handle_key_press(mrb, event_handler, event.xkey.keycode, event.xkey.state);
|
||||
break;
|
||||
case KeyRelease:
|
||||
// ignore. Is it necessary to handle this?
|
||||
break;
|
||||
case PropertyNotify:
|
||||
handle_property_notify(mrb, event_handler);
|
||||
break;
|
||||
case MappingNotify:
|
||||
handle_mapping_notify(mrb, event_handler);
|
||||
break;
|
||||
case ClientMessage:
|
||||
print_client_message_event((XClientMessageEvent*)&event);
|
||||
default:
|
||||
fprintf(stderr, "unexpected event detected! (%d)\n", event.type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2) {
|
||||
fprintf(stderr, "Usage: xremap <file>\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
mrb_state *mrb = mrb_open();
|
||||
mrb_value config = load_config(mrb, argv[1]);
|
||||
|
||||
Display *display = XOpenDisplay(NULL);
|
||||
if (!display) {
|
||||
fprintf(stderr, "Failed to open connection with X server!\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
XSetErrorHandler(error_handler);
|
||||
XSelectInput(display, XDefaultRootWindow(display), KeyPressMask | PropertyChangeMask);
|
||||
|
||||
mrb_value event_handler = new_event_handler(mrb, config, display);
|
||||
event_loop(display, mrb, event_handler);
|
||||
|
||||
XCloseDisplay(display);
|
||||
return 0;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
#include <mruby.h>
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
// config.c
|
||||
mrb_value load_config(mrb_state *mrb, char *filename);
|
||||
|
||||
// event_handler.c
|
||||
mrb_value new_event_handler(mrb_state *mrb, mrb_value config, Display *display);
|
||||
void handle_key_press(mrb_state *mrb, mrb_value event_handler, unsigned int state, unsigned int keycode);
|
||||
void handle_property_notify(mrb_state *mrb, mrb_value event_handler);
|
||||
void handle_mapping_notify(mrb_state *mrb, mrb_value event_handler);
|
Loading…
Reference in New Issue