active_window_x 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,30 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # binding for Atom on X11
6
+ class Atom < Xid
7
+ @@cache = {}
8
+
9
+ def initialize display, second
10
+ if second.kind_of? Numeric
11
+ super
12
+ elsif second.kind_of? String
13
+ super
14
+ @id = display.intern_atom second
15
+ if @id == Xlib::None
16
+ raise ArgumentError, 'invalid an atom name: #{second}'
17
+ end
18
+ else
19
+ raise ArgumentError, 'expect Numeric or String with the second argument'
20
+ end
21
+ end
22
+
23
+ alias :intern :id
24
+
25
+ def name
26
+ @display.atom_name @id
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,44 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # binding for XClientMessageEvent on X11
6
+ class ClientMessageEvent < Event
7
+
8
+ # the number of last request processed by server
9
+ attr_reader :serial
10
+
11
+ # true if this came from a SendEvent request
12
+ attr_reader :send_event
13
+
14
+ # Display the event was read from
15
+ attr_reader :display
16
+
17
+ # the window whose associated property was changed
18
+ attr_reader :window
19
+
20
+ # an atom that indicates how the data should be interpreted
21
+ # by the receiving client
22
+ attr_reader :message_type
23
+
24
+ # 8, 16, or 32 and specifies whether the data should be viewed
25
+ # as a list of bytes, shorts, or longs
26
+ attr_reader :format
27
+
28
+ # a union that contains the members b, s, and l. The b, s, and l members
29
+ # represent data of twenty 8-bit values, ten 16-bit values,
30
+ # and five 32-bit values
31
+ attr_reader :data
32
+
33
+ def initialize display, raw
34
+ super
35
+ @serial = raw.serial
36
+ @send_event = (raw.send_event != 0)
37
+ @display = display
38
+ @window = Window.new display, raw.window
39
+ @message_type = Atom.new display, raw.message_type
40
+ @format = raw.message_type
41
+ @data = raw.data
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,79 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # binding for Display on X11
6
+ class Display
7
+
8
+ # raw class of Display
9
+ attr_reader :raw
10
+
11
+ # a boolean which be true if this display was closed
12
+ attr_reader :closed
13
+ alias :closed? :closed
14
+
15
+ def initialize arg=nil
16
+ @raw =
17
+ if arg.nil? or arg.kind_of? String
18
+ Xlib::x_open_display arg
19
+ elsif arg.kind_of? Xlib::Display
20
+ arg
21
+ else
22
+ raise ArgumentError, 'expect nil, String or Xlib::Display'
23
+ end
24
+ @closed = false
25
+ @root_window = nil
26
+ @cache = {}
27
+ end
28
+
29
+ def close
30
+ Xlib::x_close_display @raw
31
+ @closed = true
32
+ end
33
+
34
+ def root_window
35
+ @root_window ||= RootWindow.new(self, Xlib::default_root_window(@raw))
36
+ end
37
+
38
+ # return IO to select and poll a XEvent with timeout
39
+ def connection
40
+ @conn ||= IO.new(Xlib::connection_number @raw)
41
+ end
42
+
43
+ # return the number of events that have been received from the X server
44
+ def pending
45
+ Xlib::x_pending @raw
46
+ end
47
+
48
+ def active_window
49
+ root_window.active_window
50
+ end
51
+
52
+ def intern_atom name
53
+ if @cache.has_key? name
54
+ @cache[name]
55
+ else
56
+ @cache[name] = Xlib::x_intern_atom @raw, name, false
57
+ end
58
+ end
59
+
60
+ def atom_name id
61
+ if @cache.has_key? id
62
+ @cache[id]
63
+ else
64
+ @cache[id] = Xlib::x_get_atom_name @raw, id
65
+ end
66
+ end
67
+
68
+ def next_event
69
+ xevent = Xlib::x_next_event @raw
70
+
71
+ case xevent.type
72
+ when Xlib::PropertyNotify; PropertyEvent.new self, xevent
73
+ when Xlib::ClientMessage; ClientMessageEvent.new self, xevent
74
+ else Event.new self, xevent
75
+ end
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,15 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # event type
6
+ attr_reader :type
7
+
8
+ # binding for XEvent on X11
9
+ class Event
10
+ def initialize display, raw
11
+ @type = raw.type
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,119 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # listen event changing active window
6
+ class EventListener
7
+
8
+ DEFAULT_TIMEOUT = 0.5
9
+
10
+ # current active window
11
+ attr_reader :active_window
12
+
13
+ # true if #start loop continued
14
+ # false if #start loop did not continue
15
+ # nil if #start loop was not started
16
+ # set false if you want to terminate #start loop when next timeout or event receiving
17
+ attr_accessor :continue
18
+
19
+ def initialize name=nil, timeout=DEFAULT_TIMEOUT, &block
20
+ @display = Display.new name
21
+ @default_timeout = timeout
22
+
23
+ @root = @display.root_window
24
+ @aw_atom = Atom.new @display, '_NET_ACTIVE_WINDOW'
25
+ @name_atom = Atom.new @display, 'WM_NAME'
26
+ @delete_atom = Atom.new @display, 'WM_DELETE_WINDOW'
27
+ @conn = @display.connection
28
+ @active_window = @root.active_window
29
+
30
+ @active_window.select_input Xlib::PropertyChangeMask if @active_window
31
+ @root.select_input Xlib::PropertyChangeMask
32
+
33
+ if block_given?
34
+ start @default_timeout, &block
35
+ end
36
+ end
37
+
38
+ # event listener loop
39
+ #
40
+ # ActiveWindowX::EventListener.new.start do |e|
41
+ # puts e.type, e.window.id
42
+ # end
43
+ def start timeout=@default_timeout
44
+
45
+ if not block_given?
46
+ raise ArgumentError, 'expect to give a block'
47
+ end
48
+
49
+ @continue = true
50
+ begin
51
+ while @continue
52
+ event = listen timeout
53
+ next if not event
54
+
55
+ if window_closed?(event.window)
56
+ event.window = @root.active_window
57
+ end
58
+ yield event if event.type
59
+ end
60
+ ensure
61
+ destroy
62
+ end
63
+ end
64
+
65
+ def destroy
66
+ @display.close if @display.closed?
67
+ end
68
+
69
+ # receive a event
70
+ #
71
+ # return value:
72
+ # a ActiveWindowX::EventListener::Event if an event was send within _timeout_ sec
73
+ # nil if timeout
74
+ def listen timeout=nil
75
+ if @display.pending == 0 and
76
+ select([@conn], [], [], timeout) == nil
77
+ # ope on timeout
78
+ return nil
79
+ end
80
+
81
+ type = nil
82
+ active_window = nil
83
+
84
+ event = @display.next_event
85
+ if event.atom == @aw_atom
86
+ type = :active_window
87
+ active_window = @root.active_window
88
+ elsif event.atom == @name_atom
89
+ type = :title
90
+ active_window = event.window
91
+ end
92
+
93
+ if type == :active_window and @active_window != active_window
94
+ @active_window.select_input(Xlib::NoEventMask) if @active_window
95
+ @active_window = active_window
96
+ @active_window.select_input(Xlib::PropertyChangeMask) if @active_window
97
+ end
98
+
99
+ Event.new type, active_window
100
+ end
101
+
102
+ def window_closed? w
103
+ return false if w.nil?
104
+ begin
105
+ w.prop_raw 'WM_STATE'
106
+ false
107
+ rescue Xlib::XErrorEvent
108
+ true
109
+ end
110
+ end
111
+
112
+ class Event
113
+ attr_accessor :type, :window
114
+ def initialize type, window
115
+ @type = type; @window = window
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,46 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # binding for XPropertyEvent on X11
6
+ class PropertyEvent < Event
7
+
8
+ # the number of last request processed by server
9
+ attr_reader :serial
10
+
11
+ # true if this came from a SendEvent request
12
+ attr_reader :send_event
13
+
14
+ # Display the event was read from
15
+ attr_reader :display
16
+
17
+ # the window whose associated property was changed
18
+ attr_reader :window
19
+
20
+ # the property's atom and indicates which property was changed or desired
21
+ attr_reader :atom
22
+
23
+ # the server time when the property was changed
24
+ attr_reader :time
25
+
26
+ # * PropertyNewValue when a property of the window is changed using
27
+ # XChangeProperty or XRotateWindowProperties (even when adding zero-length
28
+ # data using XChangeProperty) and when replacing all or part of a property
29
+ # with identical data using XChangeProperty or XRotateWindowProperties.
30
+ # * PropertyDelete when a property of the window is deleted using
31
+ # XDeleteProperty or, if the delete argument is True, XGetWindowProperty
32
+ attr_reader :state
33
+
34
+ def initialize display, raw
35
+ super
36
+ @serial = raw.serial
37
+ @send_event = (raw.send_event != 0)
38
+ @display = display
39
+ @window = Window.new display, raw.window
40
+ @atom = Atom.new display, raw.atom
41
+ @time = raw.time
42
+ @state = raw.state
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,20 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ require "active_window_x/window"
4
+
5
+ module ActiveWindowX
6
+
7
+ # binding for a root Window on X11
8
+ class RootWindow < Window
9
+
10
+ def active_window
11
+ prop_val = prop '_NET_ACTIVE_WINDOW'
12
+ if prop_val.nil? or prop_val.first == Xlib::None
13
+ nil
14
+ else
15
+ Window.new(@display, prop_val.first)
16
+ end
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveWindowX
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,123 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # binding for Window on X11
6
+ class Window < Xid
7
+
8
+ # a buffer for #x_get_window_property
9
+ READ_BUFF_LENGTH = 1024
10
+
11
+ def x_query_tree
12
+ Xlib::x_query_tree @display.raw, @id
13
+ end
14
+
15
+ # a return value of XQueryTree
16
+ # which is the root window for a display contains this window
17
+ def root
18
+ (r = x_query_tree[0]) and Window.new(@display, r)
19
+ end
20
+
21
+ # a return value of XQueryTree
22
+ # which is nil, if this window is RootWindow, or a Window.
23
+ def parent
24
+ (r = x_query_tree[1]) and Window.new(@display, r)
25
+ end
26
+
27
+ # a return value of XQueryTree
28
+ # which is an Array of Window
29
+ def children
30
+ x_query_tree[2].map{|w| Window.new(@display, w)}
31
+ end
32
+
33
+ # window title (current web page title in browser, current command or dir in terminal app, etc.)
34
+ def title
35
+ title = prop('_NET_WM_NAME')
36
+ title or prop('WM_NAME')
37
+ end
38
+
39
+ # window name (terminal, google-chrome, etc.)
40
+ def app_name
41
+ val = app_class_prop
42
+ val and val[0]
43
+ end
44
+
45
+ # window class (Terminal, Google-chrome, etc.)
46
+ # TODO write the difference of app_name and app_class
47
+ def app_class
48
+ val = app_class_prop
49
+ val and val[1]
50
+ end
51
+
52
+ def app_class_prop
53
+ val = prop('WM_CLASS')
54
+ val and val.split("\0")
55
+ end
56
+
57
+ def pid
58
+ val = prop('_NET_WM_PID')
59
+ val and val.first
60
+ end
61
+
62
+ def command
63
+ id = pid
64
+ return nil if id.nil?
65
+
66
+ path = "/proc/#{id}/cmdline"
67
+ return nil unless File.readable_real? path
68
+
69
+ File.read path
70
+ end
71
+
72
+ # window property getter with easy way for XGetWindowProperty
73
+ # which return nil, if the specified property name does not exist,
74
+ # a String or a Array of Number
75
+ def prop atom
76
+ val, format, nitems = prop_raw atom
77
+ case format
78
+ when 32; val.unpack("l!#{nitems}")
79
+ when 16; val.unpack("s#{nitems}")
80
+ when 8; val[0, nitems]
81
+ when 0; nil
82
+ end
83
+ end
84
+
85
+ # window property getter with easy way for XGetWindowProperty
86
+ # which return [propety_value, format, number_of_items]
87
+ def prop_raw atom
88
+ if atom.kind_of?(Numeric) or atom.kind_of?(String)
89
+ atom = Atom.new @display, atom
90
+ elsif not atom.kind_of? Atom
91
+ raise ArgumentError, "expect Numeric, String or #{Atom.name}"
92
+ end
93
+ actual_type, actual_format, nitems, bytes_after, val =
94
+ Xlib::x_get_window_property @display.raw, @id, atom.id, 0, READ_BUFF_LENGTH, false, Xlib::AnyPropertyType
95
+ return [val, actual_format, nitems]
96
+ end
97
+
98
+ # Array of the property atom ID(Numeric) list for this window
99
+ def prop_atom_ids
100
+ r = Xlib::x_list_properties @display.raw, @id
101
+ r.nil? ? [] : r
102
+ end
103
+
104
+ # Array of the property atom list for this window
105
+ def prop_atoms
106
+ prop_atom_ids.map{|i| Atom.new @display, i}
107
+ end
108
+
109
+ def select_input mask
110
+ Xlib::x_select_input @display.raw, @id, mask
111
+ end
112
+
113
+ def set_wm_protocols msgs
114
+ atoms =
115
+ if msgs.kind_of? Atom then [msgs.id]
116
+ elsif msgs.kind_of? Array then msgs.map {|m| m.id }
117
+ else raise ArgumentError, 'expect Atom or Array of Atom'
118
+ end
119
+ Xlib::x_set_wm_protocols @display.raw, @id, atoms
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,29 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ module ActiveWindowX
4
+
5
+ # binding for XID on X11
6
+ class Xid
7
+ # a display which has this XID
8
+ attr_reader :display
9
+
10
+ # raw XID (#define Window unsinged long)
11
+ attr_reader :id
12
+
13
+ def initialize display, id
14
+ if display.kind_of? Display
15
+ @display = display
16
+ elsif display.kind_of? Xlib::Display
17
+ @display = Display.new display
18
+ else
19
+ raise ArgumentError, "expect #{Display.name} or #{Xlib::Display.name}"
20
+ end
21
+ @id ||= id
22
+ end
23
+
24
+ def == xid
25
+ xid.kind_of?(Xid) and (xid.id == @id)
26
+ end
27
+ end
28
+
29
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding:undecided-unix; mode:ruby; -*-
2
+
3
+ require "active_window_x/version"
4
+ require "active_window_x/xlib"
5
+ require "active_window_x/display"
6
+ require "active_window_x/xid"
7
+ require "active_window_x/window"
8
+ require "active_window_x/root_window"
9
+ require "active_window_x/atom"
10
+ require "active_window_x/event"
11
+ require "active_window_x/property_event"
12
+ require "active_window_x/client_message_event"
13
+ require "active_window_x/event_listener"
14
+
15
+ module ActiveWindowX
16
+ module Xlib; end
17
+
18
+ class Display; end
19
+
20
+ class XID; end
21
+ class Window < Xid; end
22
+ class RootWindow < Window; end
23
+ class Atom < Xid; end
24
+
25
+ class Event; end
26
+ class PropertyEvent < Event; end
27
+ class ClientMessageEvent < Event; end
28
+
29
+ class EventListener; end
30
+ end
@@ -0,0 +1,30 @@
1
+ # dump all properties of window on changing active window
2
+
3
+ require 'active_window_x'
4
+
5
+ @listener = ActiveWindowX::EventListener.new
6
+
7
+ # when pressing Ctrl-C
8
+ trap :INT do
9
+ @listener.destroy
10
+ exit true
11
+ end
12
+
13
+ @listener.start do |e|
14
+ next until e.window
15
+
16
+ w = e.window
17
+ puts <<__OUTPUT__ if e.type == :active_window
18
+ ######### change active window #########
19
+ id: #{w.id}
20
+ title: #{w.title}
21
+ name: #{w.app_name}
22
+ class: #{w.app_class}
23
+ pid: #{w.pid}
24
+ command: #{w.command}
25
+ __OUTPUT__
26
+ puts <<__OUTPUT__ if e.type == :title
27
+ ######### change title #########
28
+ title: #{w.title}
29
+ __OUTPUT__
30
+ end
data/sample/simple.rb ADDED
@@ -0,0 +1,6 @@
1
+
2
+ require 'active_window_x'
3
+
4
+ ActiveWindowX::EventListener.new do |e|
5
+ puts "#{e.type}:\t#{e.window}"
6
+ end
data/spec/atom_spec.rb ADDED
@@ -0,0 +1,27 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ require 'active_window_x'
4
+
5
+ include ActiveWindowX
6
+
7
+ describe Atom do
8
+ before do
9
+ @raw_display = mock Xlib::Display
10
+ @display = mock Display
11
+ @display.stub(:raw){@raw_display}
12
+ @display.stub(:kind_of?).with(Display).and_return(true)
13
+ @id = 123
14
+ @atom = Atom.new @display, @id
15
+ end
16
+
17
+ describe '#name' do
18
+ before do
19
+ @name = 'FOOO'
20
+ @display.should_receive(:atom_name).twice.with(@id).and_return(@name)
21
+ end
22
+ it 'should return a String as an atom name' do
23
+ @atom.name.should == @name
24
+ @atom.name.should == @name
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,63 @@
1
+ # -*- coding:utf-8; mode:ruby; -*-
2
+
3
+ require 'active_window_x'
4
+
5
+ include ActiveWindowX
6
+
7
+ describe Display do
8
+ before do
9
+ @display_raw = mock Xlib::Display
10
+ Xlib.stub(:x_open_display).and_return(@display_raw)
11
+ @display = Display.new nil
12
+ @root_id = 9999
13
+ Xlib.stub(:default_root_window).with(@display_raw).and_return(@root_id)
14
+ Xlib.stub(:x_select_input).and_return(1)
15
+ end
16
+
17
+ describe '#root_window' do
18
+ it 'should return the root window' do
19
+ r = @display.root_window
20
+ r.id.should == @root_id
21
+ r.should be_a RootWindow
22
+ end
23
+ end
24
+
25
+ describe '#next_event' do
26
+ before do
27
+ @root = @display.root_window
28
+ @root.select_input Xlib::PropertyChangeMask
29
+ @event = mock Xlib::XPropertyEvent
30
+ @event.stub(:type){@type}
31
+ @event.stub(:serial){1000}
32
+ @event.stub(:send_event){0}
33
+ @event.stub(:time){2000}
34
+ @event.stub(:state){nil}
35
+ @window_id = 222
36
+ @event.stub(:window){@window_id}
37
+ @atom_id = 333
38
+ @event.stub(:atom){@atom_id}
39
+ Xlib.should_receive(:x_next_event).and_return(@event)
40
+ end
41
+ context 'with PropertyChangeMask' do
42
+ before do
43
+ @type = Xlib::PropertyNotify
44
+ end
45
+ it 'should return a PropertyEvent' do
46
+ ev = @display.next_event
47
+ ev.type.should == @event.type
48
+ ev.window.id.should == @event.window
49
+ ev.atom.id.should == @event.atom
50
+ end
51
+ end
52
+ context 'with other event type' do
53
+ before do
54
+ @type = 0
55
+ end
56
+ it 'should return a PropertyEvent' do
57
+ ev = @display.next_event
58
+ ev.type.should == @event.type
59
+ end
60
+ end
61
+ end
62
+
63
+ end