active_window_x 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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