Skip to content
Browse files

Initial commit

  • Loading branch information...
0 parents commit e9f2436969abebf1c2c4ca2add368b14301b6166 @ayanko committed Nov 9, 2011
6 .gitignore
@@ -0,0 +1,6 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+tmp/*
+lib/*.so
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in x11_client.gemspec
+gemspec
128 README.md
@@ -0,0 +1,128 @@
+## X11Client
+
+X11Client is C extension that manages X11 windows/events.
+
+It can be handy if you want to controll some programs running in Xvfb.
+
+## Dependencies
+
+* Xlib
+* Xtst
+* Xext
+
+## Installation
+
+ gem install x11_client
+
+## API
+
+* X11Client.new(display) - create new client for given display
+* X11Client#open_display - explicit open display
+* X11Client#close_display - explicit close display
+* X11Client#sync - sync display
+* X11Client#start(&block) - block execution and yield event attributes for each new event.
+* X11Client#stop(&block) - stop blocking loop
+* X11Client#get_window(window_id) - returns window attributes hash or nil for given window_id.
+* X11Client#mousemove(x,y) - move mouse pointer to (x,y) coordinates.
+* X11Client#mousedown(button) - down mouse buton
+* X11Client#mouseup(buton) - up mouse button
+* X11Client#mouseclick(button) - mousedown and mouseup
+
+## Mouse buttons
+
+* 1 - left
+* 2 - right
+* 3 - middle
+* 4 - wheel up
+* 5 - wheel down
+
+## X11 Window attributes
+
+* id - window id
+* root_id - root window id
+* x - X coordinate
+* y - Y coordinate
+* width - window width
+* height - window height
+* border_width - window border width
+* state - window state
+ * shown
+ * hidden
+ * unmapped
+
+## X11 Event attributes
+
+Supported events:
+
+* CreateWindowEvent
+* DestroyWindowEvent
+* MapEvent
+* UnmapEvent
+* VisibilityEvent
+
+### CreateWindowEvent attributes:
+
+* type
+* window_id
+* parent_id
+* x
+* y
+* width
+* height
+* border_width
+
+### DestroyWindowEvent attributes:
+
+* type
+* window_id
+
+### MapEvent attributes:
+
+* type
+* window_id
+
+### UnmapEvent attributes:
+
+* type
+* window_id
+
+### VisibilityEvent attributes:
+
+* type
+* window_id
+* state:
+ * unobscured
+ * partially_obscured
+ * fully_unobscured
+
+## Example
+
+ client = X11Client.new(":0")
+
+ client.start do |event|
+
+ case event['type']
+ when 'CreateWindowEvent'
+ window = client.get_window(event["window_id"])
+ next unless window
+
+ case window['class']
+ when /skype/i
+ case window['name']
+ when /End User License Agreement/i
+ # Accept Skype Aggreement
+ client.mousemove(window['x'] + 500, window['y'] + 340)
+ client.sync
+ sleep(1)
+ client.mouseclick(1)
+ client.stop
+ end
+ end
+
+ end
+
+ end
+
+## References
+
+* [The Xlib Programming Manual](http://tronche.com/gui/x/xlib/)
4 Rakefile
@@ -0,0 +1,4 @@
+require "bundler/gem_tasks"
+require 'rake/extensiontask'
+
+Rake::ExtensionTask.new('x11_client_ext')
7 ext/x11_client_ext/extconf.rb
@@ -0,0 +1,7 @@
+require 'mkmf'
+
+$CFLAGS << ' -Wall '
+
+$LDFLAGS << ' ' << `pkg-config --libs x11 xtst xext`
+
+create_makefile 'x11_client_ext'
12 ext/x11_client_ext/x11_client.h
@@ -0,0 +1,12 @@
+#ifndef X11_CLIENT_H
+#define X11_CLIENT_H
+
+#include "ruby.h"
+#include <X11/Xlib.h>
+
+typedef struct {
+ Display *display;
+ int loop;
+} X11Client;
+
+#endif
24 ext/x11_client_ext/x11_client_core.c
@@ -0,0 +1,24 @@
+#include "x11_client.h"
+#include "x11_client_utils.h"
+#include "x11_client_core.h"
+
+VALUE X11Client_initialize(VALUE self, VALUE display) {
+ Check_Type(display, T_STRING);
+ rb_iv_set(self, "@display", display);
+ rb_funcall(self, rb_intern("open_display"), 0, 0);
+ return self;
+}
+
+VALUE X11Client_alloc(VALUE klass) {
+ VALUE obj;
+ X11Client *client = ALLOC_N(X11Client, 1);
+ client->display = NULL;
+
+ obj = Data_Wrap_Struct(klass, 0, X11Client_free, client);
+ return obj;
+}
+
+void X11Client_free(X11Client *client) {
+ X11Client_free_display(client->display);
+ free(client);
+}
6 ext/x11_client_ext/x11_client_core.h
@@ -0,0 +1,6 @@
+#include "ruby.h"
+
+VALUE X11Client_initialize(VALUE self, VALUE display);
+VALUE X11Client_alloc(VALUE klass);
+void X11Client_free(X11Client *client);
+
43 ext/x11_client_ext/x11_client_display.c
@@ -0,0 +1,43 @@
+#include "x11_client.h"
+
+#include "x11_client_display.h"
+
+#include "x11_client_utils.h"
+
+static VALUE eX11ClientError = Qnil;
+
+VALUE X11Client_open_display(VALUE self) {
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ if(client->display) {
+ rb_raise(eX11ClientError, "Display already opened");
+ }
+
+ client->display = XOpenDisplay(RSTRING_PTR(rb_iv_get(self, "@display")));
+ if( client->display == NULL ) {
+ rb_raise(eX11ClientError, "Can't open display");
+ }
+
+ return self;
+}
+
+VALUE X11Client_close_display(VALUE self) {
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ if(client->display == NULL) {
+ rb_raise(eX11ClientError, "Display already closed");
+ }
+
+ X11Client_free_display(client->display);
+ return self;
+}
+
+VALUE X11Client_sync(VALUE self) {
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ XSync(client->display, False);
+ return self;
+}
6 ext/x11_client_ext/x11_client_display.h
@@ -0,0 +1,6 @@
+#include "ruby.h"
+
+VALUE X11Client_open_display(VALUE self);
+VALUE X11Client_close_display(VALUE self);
+VALUE X11Client_sync(VALUE self);
+
118 ext/x11_client_ext/x11_client_event.c
@@ -0,0 +1,118 @@
+#include "x11_client.h"
+#include "x11_client_event.h"
+
+VALUE X11Client_start(VALUE self) {
+ rb_need_block();
+
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+ client->loop = True;
+
+ XSelectInput(client->display, DefaultRootWindow(client->display), SubstructureNotifyMask);
+
+ XEvent any_event;
+ while ( client->loop ) {
+ XNextEvent(client->display, &any_event);
+
+ VALUE event = X11Client_get_event(any_event);
+
+ if(event && event != Qnil) {
+ rb_yield(event);
+ }
+ }
+ return Qnil;
+}
+
+VALUE X11Client_stop(VALUE self) {
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ XSelectInput(client->display, DefaultRootWindow(client->display), 0);
+ client->loop = False;
+ return Qnil;
+}
+
+VALUE X11Client_get_event(XEvent any_event) {
+ VALUE result = Qnil;
+
+ switch(any_event.type) {
+
+ case CreateNotify:
+ ;
+ XCreateWindowEvent *create_event = (XCreateWindowEvent *)&any_event;
+
+ result = rb_hash_new();
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("type"), rb_str_new2("CreateWindowEvent"));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("window_id"), LONG2FIX(create_event->window) );
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("parent_id"), LONG2FIX(create_event->parent));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("x"), INT2FIX(create_event->x));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("y"), INT2FIX(create_event->y));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("width"), INT2FIX(create_event->width));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("height"), INT2FIX(create_event->height));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("border_width"), INT2FIX(create_event->border_width));
+ break;
+
+ case DestroyNotify:
+ ;
+ XDestroyWindowEvent *destroy_event = (XDestroyWindowEvent *)&any_event;
+
+ result = rb_hash_new();
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("type"), rb_str_new2("DestroyWindowEvent"));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("window_id"), LONG2FIX(destroy_event->window) );
+ break;
+
+ case MapNotify:
+ ;
+ XMapEvent *map_event = (XMapEvent *)&any_event;
+
+ result = rb_hash_new();
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("type"), rb_str_new2("MapEvent"));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("window_id"), LONG2FIX(map_event->window) );
+ break;
+
+ case UnmapNotify:
+ ;
+ XUnmapEvent *unmap_event = (XUnmapEvent *)&any_event;
+
+ result = rb_hash_new();
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("type"), rb_str_new2("UnmapEvent"));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("window_id"), LONG2FIX(unmap_event->window) );
+ break;
+
+ case VisibilityNotify:
+ ;
+ XVisibilityEvent *visibility_event = (XVisibilityEvent *)&any_event;
+
+ result = rb_hash_new();
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("type"), rb_str_new2("VisibilityEvent"));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("window_id"), LONG2FIX(visibility_event->window) );
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("state"), X11Client_visibility_event_state(visibility_event));
+ break;
+
+ default:
+ result = Qnil;
+
+ }
+ return result;
+}
+
+VALUE X11Client_visibility_event_state(XVisibilityEvent *event) {
+ VALUE result;
+
+ switch(event->state) {
+ case VisibilityUnobscured:
+ rb_str_new2("unobscured");
+ break;
+ case VisibilityPartiallyObscured:
+ rb_str_new2("partially_obscured");
+ break;
+ case VisibilityFullyObscured:
+ rb_str_new2("fully_unobscured");
+ break;
+ default:
+ result = Qnil;
+ }
+
+ return result;
+}
+
7 ext/x11_client_ext/x11_client_event.h
@@ -0,0 +1,7 @@
+#include "ruby.h"
+#include "X11/Xlib.h"
+
+VALUE X11Client_start(VALUE self);
+VALUE X11Client_stop(VALUE self);
+VALUE X11Client_get_event(XEvent any_event);
+VALUE X11Client_visibility_event_state(XVisibilityEvent *event);
34 ext/x11_client_ext/x11_client_ext.c
@@ -0,0 +1,34 @@
+#include "x11_client.h"
+
+#include "x11_client_core.h"
+#include "x11_client_display.h"
+#include "x11_client_event.h"
+#include "x11_client_mouse.h"
+#include "x11_client_window.h"
+
+static VALUE eX11ClientError = Qnil;
+
+static VALUE cX11Client = Qnil;
+
+void Init_x11_client_ext() {
+ eX11ClientError = rb_define_class("X11ClientError", rb_eStandardError);
+
+ cX11Client = rb_define_class("X11Client", rb_cObject);
+
+ rb_define_alloc_func(cX11Client, X11Client_alloc);
+ rb_define_method(cX11Client, "initialize", X11Client_initialize, 1);
+
+ rb_define_method(cX11Client, "open_display", X11Client_open_display, 0);
+ rb_define_method(cX11Client, "close_display", X11Client_close_display, 0);
+ rb_define_method(cX11Client, "sync", X11Client_sync, 0);
+
+ rb_define_method(cX11Client, "start", X11Client_start, 0);
+ rb_define_method(cX11Client, "stop", X11Client_stop, 0);
+
+ rb_define_method(cX11Client, "get_window", X11Client_get_window, 1);
+
+ rb_define_method(cX11Client, "mousemove", X11Client_mousemove, 2);
+ rb_define_method(cX11Client, "mousedown", X11Client_mousedown, 1);
+ rb_define_method(cX11Client, "mouseup", X11Client_mouseup, 1);
+ rb_define_method(cX11Client, "mouseclick", X11Client_mouseclick, 1);
+}
37 ext/x11_client_ext/x11_client_mouse.c
@@ -0,0 +1,37 @@
+#include "x11_client.h"
+#include "x11_client_mouse.h"
+
+#include <X11/extensions/XTest.h>
+#include <unistd.h>
+
+VALUE X11Client_mousemove(VALUE self, VALUE x, VALUE y) {
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ XTestFakeMotionEvent(client->display, 0, NUM2INT(x), NUM2INT(y), CurrentTime);
+ return self;
+}
+
+VALUE X11Client_mousedown(VALUE self, VALUE button) {
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ XTestFakeButtonEvent(client->display, NUM2INT(button), True, CurrentTime);
+ return self;
+}
+
+VALUE X11Client_mouseup(VALUE self, VALUE button) {
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ XTestFakeButtonEvent(client->display, NUM2INT(button), False, CurrentTime);
+ return self;
+}
+
+VALUE X11Client_mouseclick(VALUE self, VALUE button) {
+ X11Client_mousedown(self, button);
+ sleep(0.01);
+ X11Client_mouseup(self, button);
+ return self;
+}
+
6 ext/x11_client_ext/x11_client_mouse.h
@@ -0,0 +1,6 @@
+#include "ruby.h"
+
+VALUE X11Client_mousemove(VALUE self, VALUE x, VALUE y);
+VALUE X11Client_mousedown(VALUE self, VALUE button);
+VALUE X11Client_mouseup(VALUE self, VALUE button);
+VALUE X11Client_mouseclick(VALUE self, VALUE button);
7 ext/x11_client_ext/x11_client_utils.c
@@ -0,0 +1,7 @@
+#include "x11_client_utils.h"
+
+void X11Client_free_display(Display *display) {
+ if(display) {
+ XCloseDisplay(display);
+ }
+}
3 ext/x11_client_ext/x11_client_utils.h
@@ -0,0 +1,3 @@
+#include <X11/Xlib.h>
+
+void X11Client_free_display(Display *display);
71 ext/x11_client_ext/x11_client_window.c
@@ -0,0 +1,71 @@
+#include "x11_client.h"
+
+#include "x11_client_window.h"
+
+#include "X11/Xutil.h"
+
+VALUE X11Client_get_window(VALUE self, VALUE window_id) {
+ VALUE result = Qnil;
+
+ X11Client *client;
+ Data_Get_Struct(self, X11Client, client);
+
+ Window window = NUM2LONG(window_id);
+ char* window_name = '\0';
+ XWindowAttributes attributes;
+ XClassHint classhint;
+
+ XSetErrorHandler(X11Client_IgnoreBadWindowHandler);
+
+ if(XGetWindowAttributes(client->display, window, &attributes)) {
+ result = rb_hash_new();
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("id"), LONG2FIX(window));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("x"), INT2FIX(attributes.x));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("y"), INT2FIX(attributes.y));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("width"), INT2FIX(attributes.width));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("height"), INT2FIX(attributes.height));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("border_width"), INT2FIX(attributes.border_width));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("root_id"), LONG2FIX(attributes.root));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("state"), X11Client_window_state(attributes));
+
+ if (XFetchName(client->display, window, &window_name)) {
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("name"), rb_str_new2(window_name));
+ XFree(window_name);
+ }
+
+ if (XGetClassHint(client->display, window, &classhint)) {
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("class"), rb_str_new2(classhint.res_class));
+ rb_funcall(result, rb_intern("[]="), 2, rb_str_new2("class_name"), rb_str_new2(classhint.res_name));
+ XFree(classhint.res_name);
+ XFree(classhint.res_class);
+ }
+ }
+
+ XSetErrorHandler(NULL);
+
+ return result;
+}
+
+VALUE X11Client_window_state(XWindowAttributes attributes) {
+ VALUE state = Qnil;
+ switch(attributes.map_state) {
+ case IsViewable:
+ state = rb_str_new2("shown");
+ break;
+ case IsUnviewable:
+ state = rb_str_new2("hidden");
+ break;
+ case IsUnmapped:
+ state = rb_str_new2("unmapped");
+ break;
+ }
+
+ return state;
+}
+
+int X11Client_IgnoreBadWindowHandler(Display *display, XErrorEvent *error) {
+ if (error->request_code != BadWindow) {
+ XSetErrorHandler(NULL);
+ }
+ return 1;
+}
3 ext/x11_client_ext/x11_client_window.h
@@ -0,0 +1,3 @@
+VALUE X11Client_get_window(VALUE self, VALUE window_id);
+VALUE X11Client_window_state(XWindowAttributes attributes);
+int X11Client_IgnoreBadWindowHandler(Display *display, XErrorEvent *error);
4 lib/x11_client.rb
@@ -0,0 +1,4 @@
+require "x11_client_ext"
+
+class X11Client
+end
21 x11_client.gemspec
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+
+Gem::Specification.new do |s|
+ s.name = "x11_client"
+ s.version = "0.0.1"
+ s.authors = ["Andriy Yanko"]
+ s.email = ["andriy.yanko@gmail.com"]
+ s.homepage = "https://github.com/ayanko/x11_client"
+ s.summary = %q{X11 client}
+ s.description = %q{X11 client}
+
+ s.rubyforge_project = "x11_client"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.extensions = ["ext/x11_client_ext/extconf.rb"]
+end

0 comments on commit e9f2436

Please sign in to comment.
Something went wrong with that request. Please try again.