Full scratch

pull/39/head
Takashi Kokubun 2 years ago
parent caea9a1c2f
commit 603be104f4
No known key found for this signature in database
GPG Key ID: 6FFC433B12EE23DD

7
.gitignore vendored

@ -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…
Cancel
Save