xrandr 0.0.2

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 22d9b48ee5fbeaf0c16d9d36826f9fd83bcaf292
4
+ data.tar.gz: d21fc1b476c1cd301cdc0fb3d1ecc0d090c799d8
5
+ SHA512:
6
+ metadata.gz: 8b2b346c588b1d3bc3dc254cb9eccd844ef2ffd84f971cf2202aae51ddeb59ef7c0839632be9c2924269a6f3554244dd27c102ce165d17091ff7e74ea0e1aa39
7
+ data.tar.gz: c7a03d27ff658f5739c86c88b0fab7bbaaa9597604dac087cedfc1ccc6643103bf5572d4676ec889665a97010ec57d75a1a86262ba4aac86435535734c77bf98
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .ruby-version
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in xrandr.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Fernando Martinez
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,87 @@
1
+ # xrandr
2
+
3
+ A ruby interface to [xrandr](http://www.x.org/wiki/Projects/XRandR/)
4
+
5
+ ## Install
6
+
7
+ `gem install xrandr`
8
+
9
+ (or use your preferred gemset/dependency management tool)
10
+
11
+ ## Usage
12
+
13
+ ### Outputs
14
+
15
+ Gives you the status of each of the outputs
16
+
17
+ ```ruby
18
+
19
+ Xrandr.new.outputs #=> [ <Xrandr::Output{ id: 1, name: 'LVDS1', connected: true, mode: '1920x1080' }>, <Xrandr::Output { id: 2, name: 'VGA1', connected: false }> ]
20
+
21
+ ```
22
+
23
+ Access specific output parameters
24
+
25
+ ```ruby
26
+ randr = Xrandr.new
27
+ vga = randr.output(1)
28
+
29
+ vga.connected #=> true
30
+ vga.name #=> 'VGA1'
31
+ vga.mode #=> '1920x1080'
32
+
33
+ ```
34
+
35
+ ```ruby
36
+ randr = Xrandr.new
37
+ vga = randr.output('VGA1')
38
+
39
+ vga.connected #=> true
40
+ vga.name #=> 'VGA1'
41
+ vga.mode #=> '1920x1080'
42
+
43
+ ```
44
+
45
+ ### Configure
46
+
47
+
48
+ ```ruby
49
+ xrandr = Xrandr.new
50
+
51
+ # setting global options
52
+ xrandr.configure(fb: '1920x1080', no_primary: true)
53
+
54
+
55
+ # setting per output options
56
+ xrandr.configure('VGA1', auto: true)
57
+
58
+ # also, can specify the output by index
59
+ xrandr.configure('VGA2', mode: '1920x1080', right_of: 'VGA1')
60
+
61
+
62
+ ```
63
+
64
+ After all outputs are configured, call `#apply!` to execute the command.
65
+
66
+ ```ruby
67
+
68
+ xrandr.apply!
69
+
70
+ ```
71
+
72
+ or call `#command`, to get the command line string that would be run
73
+ ```ruby
74
+
75
+ xrandr.command #=> "xrandr --fb 1920x1080 --no_primary --output VGA1 --auto --output LVDS1 --mode 1920x1080 --pos 1921x0"
76
+
77
+ ```
78
+
79
+ For all available configuration parameter see you can `man xrandr`
80
+
81
+ ## Contributing
82
+
83
+ Open an issue, lets talk.
84
+
85
+ ## License
86
+
87
+ See UNLICENSE
@@ -0,0 +1,147 @@
1
+ module Xrandr
2
+ VERSION = '0.0.2'
3
+
4
+ class Control
5
+ attr_reader :screens, :outputs, :command
6
+
7
+ def initialize(parser = Parser.new)
8
+ @screens, @outputs = * parser.parse
9
+ @command = 'xrandr'
10
+ end
11
+
12
+ def configure(output = nil, **options)
13
+ output = find_output(output) if output
14
+
15
+ command << " --output #{output.name}" if output
16
+ command << options.map do |option, value|
17
+ value = nil if value == true
18
+ " --#{option} #{value}".rstrip
19
+ end.join(' ')
20
+ end
21
+
22
+ def find_output(output)
23
+ if output.kind_of? Output
24
+ output
25
+ elsif output.kind_of? String
26
+ outputs.find {|o| o.name == output}
27
+ elsif output.kind_of? Integer
28
+ outputs[output]
29
+ else
30
+ raise ArgumentError, "Expecting a string, an integer or an Xrandr::Output instance"
31
+ end
32
+ end
33
+ alias_method :[], :find_output
34
+
35
+ def apply!
36
+ Kernel.system(command)
37
+ initialize
38
+ end
39
+ end
40
+
41
+ class Parser
42
+ attr_reader :data
43
+ def initialize(data = `xrandr --query`)
44
+ @data = data
45
+ end
46
+
47
+ def parse
48
+ screens_data, outputs_data = data.split("\n").group_by {|line| line.start_with?('Screen') }.values
49
+
50
+ [ parse_screens(screens_data), parse_outputs(outputs_data) ]
51
+ end
52
+
53
+ def parse_screens(data); []; end # TODO
54
+
55
+ def parse_outputs(data)
56
+ # join output info line with each of its modes
57
+ data = data.slice_when {|before, after| !after.start_with?(' ')}
58
+
59
+ data.map do |output_data|
60
+ parse_output(output_data)
61
+ end
62
+ end
63
+
64
+ def parse_output(data)
65
+ data, *modes = data
66
+
67
+ args = {
68
+ name: /[a-zA-Z0-9\-\_]+/,
69
+ connected: /connected|disconnected/,
70
+ primary: /primary/,
71
+ resolution: /\d+x\d+\+\d+\+\d+/,
72
+ info: /\([^\)]+\)/,
73
+ dimensions: /[0-9+]+mm x [0-9]+mm/,
74
+ }
75
+ .map {|token, regex| [token, data.scan(regex).first] }
76
+ .to_h
77
+
78
+ # split resolution and position values split all values, coherce to integers, split the array in halfs, assign each half)
79
+ args[:resolution], args[:position] = args[:resolution].split(/x|\+/).each_slice(2).map {|v| v.join('x') } if args[:resolution]
80
+
81
+ # Coherce parameters
82
+ args[:connected] = args[:connected] == 'connected'
83
+ args[:primary] = args[:primary] == 'primary'
84
+
85
+ # Parse modes
86
+ args[:modes] = parse_modes(modes)
87
+
88
+ Output.new args
89
+ end
90
+
91
+ def parse_modes(modes)
92
+ modes.map do |data|
93
+ parse_mode data
94
+ end
95
+ end
96
+
97
+ def parse_mode(data)
98
+ matches = data.lstrip.match(/^(?<resolution>\d+x\d+i?) +(?<rate>[\d\.]+)(?<current>[\* ])(?<preferred>[\+ ]).*/)
99
+
100
+ resolution = matches[:resolution].gsub 'i', '' if matches[:resolution]
101
+ args = {
102
+ resolution: resolution,
103
+ rate: matches[:rate],
104
+ current: matches[:current] == '*',
105
+ preferred: matches[:preferred] == '+',
106
+ }
107
+
108
+ Mode.new args
109
+ end
110
+ end
111
+
112
+ class Output
113
+ attr_reader :name, :connected, :primary, :resolution, :position, :info, :dimensions, :modes
114
+
115
+ def initialize(name:, connected:, primary: false, resolution: nil, position: nil, info: '', dimensions: '', modes: [])
116
+ raise ArgumentError, "must provide a name for the output" unless name
117
+ raise ArgumentError, "connected cant be nil" unless connected == true || connected == false
118
+ @name = name
119
+ @connected = connected
120
+ @primary = primary
121
+ @resolution = resolution
122
+ @position = position
123
+ @info = info
124
+ @dimensions = dimensions
125
+ @modes = modes
126
+ end
127
+
128
+ def current
129
+ modes.detect(&:current)
130
+ end
131
+
132
+ def preferred
133
+ modes.detect(&:preferred)
134
+ end
135
+ end
136
+
137
+ class Mode
138
+ attr_reader :resolution, :rate, :current, :preferred
139
+
140
+ def initialize(args={})
141
+ @resolution = args.fetch :resolution
142
+ @rate = args.fetch :rate
143
+ @current = args.fetch :current
144
+ @preferred = args.fetch :preferred
145
+ end
146
+ end
147
+ end
@@ -0,0 +1 @@
1
+ Dir['**/*_test.rb'].each &method(:load)
@@ -0,0 +1,82 @@
1
+ require_relative 'helper'
2
+ require_relative '../lib/xrandr'
3
+
4
+ module Xrandr
5
+ class XrandrTest < Minitest::Test
6
+ def outputs
7
+ [ Output.new(name: 'VGA1', connected: true), Output.new(name: 'VGA2', connected: true), Output.new(name: 'VGA3', connected: true) ]
8
+ end
9
+ end
10
+
11
+ class ControlTest < XrandrTest
12
+ def test_find_ouput_finds_an_output_by_name
13
+ parser = MockParser.new([], outputs)
14
+ xrandr = Control.new(parser)
15
+ output = outputs[2]
16
+
17
+ assert_equal outputs[2].name, xrandr.find_output(output).name
18
+ end
19
+
20
+ def test_find_ouput_finds_an_output_by_name
21
+ parser = MockParser.new([], outputs)
22
+ xrandr = Control.new(parser)
23
+
24
+ assert_equal outputs[1].name, xrandr.find_output('VGA2').name
25
+ end
26
+
27
+ def test_find_ouput_finds_an_output_by_index
28
+ parser = MockParser.new([], outputs)
29
+
30
+ xrandr = Control.new(parser)
31
+
32
+ assert_equal outputs[1].name, xrandr.find_output(1).name
33
+ end
34
+ end
35
+
36
+ class XrandrConfigureGlobalParametersTest < XrandrTest
37
+ def test_param_with_arguments
38
+ xrandr = Control.new MockParser.new([], [])
39
+ xrandr.configure fb: '2000x2000'
40
+
41
+ assert_equal 'xrandr --fb 2000x2000', xrandr.command
42
+ end
43
+
44
+ def test_switch_param
45
+ xrandr = Control.new MockParser.new([], [])
46
+ xrandr.configure noprimary: true
47
+
48
+ assert_equal 'xrandr --noprimary', xrandr.command
49
+ end
50
+ end
51
+
52
+ class XrandrConfigureOutputParametersTest < XrandrTest
53
+ def test_configure_output_by_name
54
+ xrandr = Control.new MockParser.new([], outputs)
55
+
56
+ xrandr.configure 'VGA1', mode: '2000x2000'
57
+
58
+ assert_equal 'xrandr --output VGA1 --mode 2000x2000', xrandr.command
59
+ end
60
+
61
+ def test_configure_output_by_index
62
+ xrandr = Control.new MockParser.new([], outputs)
63
+
64
+ xrandr.configure 0, primary: true
65
+
66
+ assert_equal 'xrandr --output VGA1 --primary', xrandr.command
67
+ end
68
+
69
+ def test_configure_output_param_with_a_switch_argument
70
+ xrandr = Control.new MockParser.new([], outputs)
71
+ xrandr.configure 'VGA1', primary: true
72
+
73
+ assert_equal 'xrandr --output VGA1 --primary', xrandr.command
74
+ end
75
+ end
76
+
77
+ MockParser = Struct.new(:screens, :outputs) do
78
+ def parse
79
+ [screens, outputs]
80
+ end
81
+ end
82
+ end
@@ -0,0 +1 @@
1
+ require 'minitest/autorun'
@@ -0,0 +1,18 @@
1
+ require_relative 'helper'
2
+ require_relative '../lib/xrandr'
3
+
4
+ module Xrandr
5
+ class OutputTest < Minitest::Test
6
+ def test_new_raises_if_no_name
7
+ assert_raises ArgumentError do
8
+ Output.new connected: false
9
+ end
10
+ end
11
+
12
+ def test_new_raises_if_no_connection_info
13
+ assert_raises ArgumentError do
14
+ Output.new name: 'o'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,132 @@
1
+ require_relative 'helper'
2
+ require_relative '../lib/xrandr'
3
+
4
+ module Xrandr
5
+
6
+ class Parser::ParseOutputTest < Minitest::Test
7
+ def test_parse_outputs_returns_an_array_of_outputs
8
+ o = [
9
+ "LVDS1 connected primary 1366x768+0+0 (normal left inverted right x axis y axis) 309mm x 174mm",
10
+ " 1366x768 60.07*+"," 1024x768 60.00 ",
11
+ " 800x600 60.32 56.25 ",
12
+ " 640x480 59.94 ",
13
+ "VGA1 connected 1920x1080+1366+0 (normal left inverted right x axis y axis) 309mm x 174mm",
14
+ " 1920x1080 60.07*+",
15
+ " 1024x768 60.00 ",
16
+ " 800x600 60.32 56.25 ",
17
+ " 640x480 59.94 ",
18
+ "DP1 disconnected (normal left inverted right x axis y axis)",
19
+ "DP2 disconnected (normal left inverted right x axis y axis)"
20
+ ]
21
+
22
+ outputs = Parser.new.parse_outputs o
23
+
24
+ assert 4, outputs.size
25
+ assert outputs[0].connected
26
+ assert outputs[1].connected
27
+ refute outputs[2].connected
28
+ refute outputs[3].connected
29
+ assert 3, outputs[0].modes.size
30
+ assert 4, outputs[1].modes.size
31
+ end
32
+
33
+ def test_parses_connected_output_lines
34
+ session = Parser.new
35
+ o = ["LVDS1 connected 1366x768+0+0 (normal left inverted right x axis y axis) 309mm x 174mm"," 1366x768 60.07*+"," 1024x768 60.00 "," 800x600 60.32 56.25 "," 640x480 59.94 "]
36
+
37
+ output = session.parse_output o
38
+
39
+ assert_equal "LVDS1", output.name
40
+ assert output.connected
41
+ assert_equal '1366x768', output.resolution
42
+ assert_equal '0x0', output.position
43
+ assert_equal '(normal left inverted right x axis y axis)', output.info
44
+ assert_equal '309mm x 174mm', output.dimensions
45
+ assert_equal 4, output.modes.size
46
+ refute output.primary
47
+ end
48
+
49
+ def test_parses_primary
50
+ session = Parser.new
51
+ o = ['LVDS1 connected primary 1366x768+0+0 (normal left inverted right x axis y axis) 309mm x 174mm'," 1366x768 60.07*+"," 1024x768 60.00 "," 800x600 60.32 56.25 "," 640x480 59.94 "]
52
+
53
+ output = session.parse_output o
54
+
55
+ assert_equal "LVDS1", output.name
56
+ assert output.connected
57
+ assert_equal '1366x768', output.resolution
58
+ assert_equal '0x0', output.position
59
+ assert_equal '(normal left inverted right x axis y axis)', output.info
60
+ assert_equal '309mm x 174mm', output.dimensions
61
+ assert output.primary
62
+ assert_equal 4, output.modes.size
63
+ end
64
+
65
+ def test_parses_disconnected_output_lines
66
+ session = Parser.new
67
+ output = session.parse_output ["DP1 disconnected (normal left inverted right x axis y axis)"]
68
+
69
+ refute output.connected
70
+ assert_equal 'DP1', output.name
71
+ assert_equal '(normal left inverted right x axis y axis)', output.info
72
+ assert_nil output.dimensions
73
+ assert_nil output.resolution
74
+ assert_nil output.position
75
+ assert_empty output.modes
76
+ refute output.primary
77
+ end
78
+ end
79
+
80
+ class Parser::ParseModeTest < Minitest::Test
81
+ def test_returns_a_mode
82
+ mode = Parser.new.parse_mode(" 1366x768 60.07*+")
83
+ assert_equal '1366x768', mode.resolution
84
+ assert_equal '60.07', mode.rate
85
+ assert mode.current
86
+ assert mode.preferred
87
+ end
88
+
89
+ def test_parses_preferred_attribute_correctly
90
+ mode = Parser.new.parse_mode(" 1366x768 60.07 +")
91
+ assert mode.preferred
92
+ mode = Parser.new.parse_mode(" 1366x768 60.07 ")
93
+ refute mode.preferred
94
+
95
+ mode = Parser.new.parse_mode(" 1366x768 60.07*+")
96
+ assert mode.preferred
97
+ mode = Parser.new.parse_mode(" 1366x768 60.07* ")
98
+ refute mode.preferred
99
+ end
100
+
101
+ def test_parses_current_attribute_correctly
102
+ mode = Parser.new.parse_mode(" 1366x768 60.07* ")
103
+ assert mode.current
104
+ mode = Parser.new.parse_mode(" 1366x768 60.07 ")
105
+ refute mode.current
106
+
107
+ mode = Parser.new.parse_mode(" 1366x768 60.07*+")
108
+ assert mode.current
109
+ mode = Parser.new.parse_mode(" 1366x768 60.07 +")
110
+ refute mode.current
111
+ end
112
+
113
+ def test_parse_modes_returns_a_collection_of_modes
114
+ session = Parser.new
115
+ modes = session.parse_modes [" 1024x768 60.00 +", " 1920x1080 60.00 ", " 1366x768 60.07* "]
116
+
117
+ assert_equal "1024x768", modes[0].resolution
118
+ assert_equal "1920x1080", modes[1].resolution
119
+ assert_equal "1366x768", modes[2].resolution
120
+ end
121
+
122
+ # some systems names the modes like 1920x1080i
123
+ def test_parse_modes_allows_i_suffix
124
+ session = Parser.new
125
+ modes = session.parse_modes [" 1024x768i 60.00 +", " 1920x1080i 60.00 ", " 1366x768i 60.07* "]
126
+
127
+ assert_equal "1024x768", modes[0].resolution
128
+ assert_equal "1920x1080", modes[1].resolution
129
+ assert_equal "1366x768", modes[2].resolution
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'xrandr'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "xrandr"
8
+ spec.version = Xrandr::VERSION
9
+ spec.authors = ["Fernando Martínez"]
10
+ spec.email = ["fernando.martinez@live.com.ar"]
11
+ spec.summary = %q{ A ruby wrapper for Xrandr }
12
+ spec.description = %q{ A ruby wrapper for Xrandr }
13
+ spec.homepage = "https://github.com/f-3r/xrandr"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "minitest", "~> 5.7"
23
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xrandr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Fernando Martínez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.7'
41
+ description: " A ruby wrapper for Xrandr "
42
+ email:
43
+ - fernando.martinez@live.com.ar
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".ruby-version"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - lib/xrandr.rb
54
+ - test/all.rb
55
+ - test/control_test.rb
56
+ - test/helper.rb
57
+ - test/output_test.rb
58
+ - test/parser_test.rb
59
+ - xrandr.gemspec
60
+ homepage: https://github.com/f-3r/xrandr
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.4.5
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: A ruby wrapper for Xrandr
84
+ test_files:
85
+ - test/all.rb
86
+ - test/control_test.rb
87
+ - test/helper.rb
88
+ - test/output_test.rb
89
+ - test/parser_test.rb