rmonitor 1.0.0
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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +24 -0
- data/LICENSE +20 -0
- data/README.md +205 -0
- data/bin/rmonitor +112 -0
- data/lib/rmonitor.rb +23 -0
- data/lib/rmonitor/devices.rb +29 -0
- data/lib/rmonitor/helpers/dsl_helpers.rb +38 -0
- data/lib/rmonitor/helpers/profile_helpers.rb +48 -0
- data/lib/rmonitor/helpers/xrandr_read_helpers.rb +75 -0
- data/lib/rmonitor/helpers/xrandr_write_helpers.rb +55 -0
- data/lib/rmonitor/profiles.rb +51 -0
- data/lib/rmonitor/version.rb +3 -0
- data/rmonitor.gemspec +43 -0
- data/spec/lib/helpers/dsl_helper_spec.rb +29 -0
- data/spec/lib/helpers/profile_helpers_spec.rb +138 -0
- data/spec/lib/helpers/xrandr_read_helpers_spec.rb +259 -0
- data/spec/lib/helpers/xrandr_write_helpers_spec.rb +158 -0
- data/spec/support/device_helper.rb +3 -0
- data/spec/support/profile_helper.rb +3 -0
- data/spec/support/string_helpers.rb +25 -0
- metadata +83 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
class RMonitor
|
2
|
+
module ProfileHelpers
|
3
|
+
def invokable?(devices, profile, verbose = false)
|
4
|
+
necessary_devices_present = necessary_devices_present?(devices, profile)
|
5
|
+
user_defined_rules_satisfied = user_defined_rules_satisfied?(profile)
|
6
|
+
|
7
|
+
if necessary_devices_present && !user_defined_rules_satisfied && verbose
|
8
|
+
method = (profile[:options][:not_if] || profile[:options][:only_if]).name
|
9
|
+
|
10
|
+
puts "#{profile[:name].inspect} deemed not invokable due to #{method.inspect}."
|
11
|
+
end
|
12
|
+
|
13
|
+
necessary_devices_present && user_defined_rules_satisfied
|
14
|
+
end
|
15
|
+
|
16
|
+
def necessary_devices_present?(devices, profile)
|
17
|
+
profile[:devices].all? do |wanted_device|
|
18
|
+
device = devices.find { |d| d[:name] == wanted_device[:name] }
|
19
|
+
|
20
|
+
device and
|
21
|
+
has_matching_configuration(device,
|
22
|
+
wanted_device[:mode],
|
23
|
+
wanted_device[:rate])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def user_defined_rules_satisfied?(profile)
|
28
|
+
if profile[:options][:only_if]
|
29
|
+
profile[:options][:only_if].call
|
30
|
+
elsif profile[:options][:not_if]
|
31
|
+
!profile[:options][:not_if].call
|
32
|
+
else
|
33
|
+
true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def best_matching_configuration(device, mode, rate)
|
38
|
+
device[:configurations].find do |configuration|
|
39
|
+
(!mode or configuration[:mode] == mode) &&
|
40
|
+
(!rate or configuration[:rate] == rate)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def has_matching_configuration(device, mode, rate)
|
45
|
+
!best_matching_configuration(device, mode, rate).nil?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class RMonitor
|
2
|
+
module XRandRReadHelpers
|
3
|
+
def split_blocks(devices_data)
|
4
|
+
block = /
|
5
|
+
^[^\s] .+? \n (?=[^\s]|\Z)
|
6
|
+
/mx
|
7
|
+
|
8
|
+
devices_data.scan(block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def collect_devices(blocks)
|
12
|
+
blocks.reject do |block|
|
13
|
+
block.match(/\AScreen/)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def extract_name(block)
|
18
|
+
block.split.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_pos(block)
|
22
|
+
if /\+(?<x_pos>\d+)\+(?<y_pos>\d+)/ =~ block
|
23
|
+
"#{x_pos}x#{y_pos}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def extract_enabled(block)
|
28
|
+
block.match(/\d+x\d+\+\d+\+\d+/)
|
29
|
+
end
|
30
|
+
|
31
|
+
def extract_connected(block)
|
32
|
+
block.match(/(?<!dis)connected/)
|
33
|
+
end
|
34
|
+
|
35
|
+
def extract_configuration(block)
|
36
|
+
# Consider each line except the first
|
37
|
+
block.split("\n")[1..-1].each do |configuration_line|
|
38
|
+
|
39
|
+
# See if it contains any current configurations
|
40
|
+
if /(?<rate>\d+\.\d+)\*/ =~ configuration_line
|
41
|
+
|
42
|
+
# Extract the mode (resolution)
|
43
|
+
/(?<mode>\d+x\d+)/ =~ configuration_line
|
44
|
+
return {
|
45
|
+
:mode => mode,
|
46
|
+
:rate => rate,
|
47
|
+
}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def extract_configurations(block)
|
55
|
+
configurations = []
|
56
|
+
|
57
|
+
# Consider each line except the first
|
58
|
+
block.split("\n")[1..-1].each do |configuration_block|
|
59
|
+
|
60
|
+
# Extract the mode (resolution)
|
61
|
+
/(?<mode>\d+x\d+)/ =~ configuration_block
|
62
|
+
|
63
|
+
# Extract each supported frame rate of that mode (resolution)
|
64
|
+
configuration_block.scan(/\d+\.\d+/).each do |rate|
|
65
|
+
configurations << {
|
66
|
+
:mode => mode,
|
67
|
+
:rate => rate,
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
configurations
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class RMonitor
|
2
|
+
module XRandRWriteHelpers
|
3
|
+
def turn_off(device)
|
4
|
+
"--output #{device} --off"
|
5
|
+
end
|
6
|
+
|
7
|
+
def turn_on(device, options)
|
8
|
+
on = '--output %s --mode %s --rate %s' % [
|
9
|
+
device,
|
10
|
+
options[:mode],
|
11
|
+
options[:rate],
|
12
|
+
]
|
13
|
+
|
14
|
+
if options[:pos]
|
15
|
+
on << ' --pos ' << options[:pos]
|
16
|
+
elsif options[:left_of]
|
17
|
+
on << ' --left-of ' << options[:left_of]
|
18
|
+
elsif options[:right_of]
|
19
|
+
on << ' --right-of ' << options[:right_of]
|
20
|
+
elsif options[:above]
|
21
|
+
on << ' --above ' << options[:above]
|
22
|
+
elsif options[:below]
|
23
|
+
on << ' --below ' << options[:below]
|
24
|
+
elsif options[:same_as]
|
25
|
+
on << ' --same-as ' << options[:same_as]
|
26
|
+
end
|
27
|
+
|
28
|
+
if options[:rotate]
|
29
|
+
allowed_options = %w(normal inverted left right)
|
30
|
+
|
31
|
+
if allowed_options.include?(options[:rotate])
|
32
|
+
on << ' --rotate ' << options[:rotate]
|
33
|
+
else
|
34
|
+
raise XRandRArgumentError.new("Invalid argument for --rotate")
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
if options[:reflect]
|
39
|
+
allowed_options = %w(normal x y xy)
|
40
|
+
|
41
|
+
if allowed_options.include?(options[:reflect])
|
42
|
+
on << ' --reflect ' << options[:reflect]
|
43
|
+
else
|
44
|
+
raise XRandRArgumentError.new("Invalid argument for --reflect")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
if options[:primary]
|
49
|
+
on << ' --primary'
|
50
|
+
end
|
51
|
+
|
52
|
+
on
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rmonitor/helpers/dsl_helpers'
|
2
|
+
require 'rmonitor/helpers/profile_helpers'
|
3
|
+
require 'rmonitor/helpers/xrandr_write_helpers'
|
4
|
+
|
5
|
+
class RMonitor
|
6
|
+
class Profiles
|
7
|
+
include DSLHelpers
|
8
|
+
extend ProfileHelpers
|
9
|
+
extend XRandRWriteHelpers
|
10
|
+
|
11
|
+
def self.parse(config_data)
|
12
|
+
profile_builder = ProfileBuilder.new
|
13
|
+
profile_builder.instance_eval(config_data)
|
14
|
+
profile_builder.profiles
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.to_xrandr(devices, profile)
|
18
|
+
xrandr = 'xrandr'
|
19
|
+
|
20
|
+
# Devices that are currently enabled, but not contained in the profile
|
21
|
+
to_disable = devices.select { |d| d[:enabled ]}.map { |d| d[:name] } -
|
22
|
+
profile[:devices].map { |d| d[:name] }
|
23
|
+
|
24
|
+
unless to_disable.empty?
|
25
|
+
to_disable.each do |name|
|
26
|
+
xrandr << ' ' << turn_off(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
xrandr << ' && xrandr'
|
30
|
+
end
|
31
|
+
|
32
|
+
if profile[:options] and profile[:options][:dpi]
|
33
|
+
xrandr << ' --dpi ' << profile[:options][:dpi].to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
# The devices contained in the profile are to be turned on and configured
|
37
|
+
profile[:devices].each do |wanted_device|
|
38
|
+
device = devices.find { |d| d[:name] == wanted_device[:name] }
|
39
|
+
|
40
|
+
configuration = best_matching_configuration(device,
|
41
|
+
wanted_device[:mode],
|
42
|
+
wanted_device[:rate])
|
43
|
+
|
44
|
+
xrandr << ' ' << turn_on(device[:name],
|
45
|
+
configuration.merge(wanted_device))
|
46
|
+
end
|
47
|
+
|
48
|
+
xrandr
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/rmonitor.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'lib', 'rmonitor', 'version')
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'rmonitor'
|
5
|
+
s.version = RMonitor::VERSION
|
6
|
+
s.license = 'MIT'
|
7
|
+
s.date = '2014-08-14'
|
8
|
+
|
9
|
+
s.summary = 'A tool for creating monitor profiles that are easily invoked.'
|
10
|
+
s.description = 'RMonitor is a tool for creating monitor profiles that are easily invoked. This is useful when you often find yourself in situations with different monitor configurations.'
|
11
|
+
|
12
|
+
s.authors = ['Jonas Amundsen']
|
13
|
+
s.email = ['jonasba+gem@gmail.com']
|
14
|
+
|
15
|
+
s.executables = 'rmonitor'
|
16
|
+
|
17
|
+
s.files = %w[
|
18
|
+
bin/rmonitor
|
19
|
+
lib/rmonitor/helpers/dsl_helpers.rb
|
20
|
+
lib/rmonitor/helpers/profile_helpers.rb
|
21
|
+
lib/rmonitor/helpers/xrandr_read_helpers.rb
|
22
|
+
lib/rmonitor/helpers/xrandr_write_helpers.rb
|
23
|
+
lib/rmonitor/devices.rb
|
24
|
+
lib/rmonitor/profiles.rb
|
25
|
+
lib/rmonitor/version.rb
|
26
|
+
lib/rmonitor.rb
|
27
|
+
spec/lib/helpers/dsl_helper_spec.rb
|
28
|
+
spec/lib/helpers/profile_helpers_spec.rb
|
29
|
+
spec/lib/helpers/xrandr_read_helpers_spec.rb
|
30
|
+
spec/lib/helpers/xrandr_write_helpers_spec.rb
|
31
|
+
spec/support/device_helper.rb
|
32
|
+
spec/support/profile_helper.rb
|
33
|
+
spec/support/string_helpers.rb
|
34
|
+
.rspec
|
35
|
+
Gemfile
|
36
|
+
Gemfile.lock
|
37
|
+
LICENSE
|
38
|
+
README.md
|
39
|
+
rmonitor.gemspec
|
40
|
+
]
|
41
|
+
|
42
|
+
s.add_development_dependency('rspec')
|
43
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rmonitor/helpers/dsl_helpers'
|
2
|
+
|
3
|
+
describe RMonitor::DSLHelpers::ProfileBuilder do
|
4
|
+
context "with :only_if being present" do
|
5
|
+
it "should replace the symbol with the respective method proc" do
|
6
|
+
profile_parser = RMonitor::DSLHelpers::ProfileBuilder.new
|
7
|
+
|
8
|
+
profile_parser.define_singleton_method(:user_defined_rule) do
|
9
|
+
# No operation
|
10
|
+
end
|
11
|
+
|
12
|
+
profile_parser.profile("dummy profile", :only_if => :user_defined_rule) do end
|
13
|
+
profile_parser.profiles.first[:options][:only_if].is_a?(Method).should be_true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "with :not_if being present" do
|
18
|
+
it "should replace the symbol with the respective method proc" do
|
19
|
+
profile_parser = RMonitor::DSLHelpers::ProfileBuilder.new
|
20
|
+
|
21
|
+
profile_parser.define_singleton_method(:user_defined_rule) do
|
22
|
+
# No operation
|
23
|
+
end
|
24
|
+
|
25
|
+
profile_parser.profile("dummy profile", :not_if => :user_defined_rule) do end
|
26
|
+
profile_parser.profiles.first[:options][:not_if].is_a?(Method).should be_true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'rmonitor/devices'
|
2
|
+
require 'rmonitor/helpers/profile_helpers'
|
3
|
+
|
4
|
+
describe RMonitor::ProfileHelpers do
|
5
|
+
include RMonitor::ProfileHelpers
|
6
|
+
|
7
|
+
describe :invokable? do
|
8
|
+
after do
|
9
|
+
rspec_reset
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with necessary devices not being present" do
|
13
|
+
it "should return false" do
|
14
|
+
stub!(:necessary_devices_present?).and_return(false)
|
15
|
+
stub!(:user_defined_rules_satisfied?).and_return(true)
|
16
|
+
|
17
|
+
invokable?(nil, nil).should be_false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with user defined rules not being satisfied" do
|
22
|
+
it "should return false" do
|
23
|
+
stub!(:necessary_devices_present?).and_return(true)
|
24
|
+
stub!(:user_defined_rules_satisfied?).and_return(false)
|
25
|
+
|
26
|
+
invokable?(nil, nil).should be_false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "with necessary devices being present and user defined rules being satisfied" do
|
31
|
+
it "should return true" do
|
32
|
+
stub!(:necessary_devices_present?).and_return(true)
|
33
|
+
stub!(:user_defined_rules_satisfied?).and_return(true)
|
34
|
+
|
35
|
+
invokable?(nil, nil).should be_true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe :user_defined_rules_satisfied? do
|
41
|
+
context "with :only_if being present" do
|
42
|
+
it "should return what the user defined rule is returning" do
|
43
|
+
profile = {
|
44
|
+
:options => {
|
45
|
+
:only_if => Proc.new { true }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
user_defined_rules_satisfied?(profile).should be_true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "with :not_if being present" do
|
54
|
+
it "should return the opposite of what the user defined rule is returning" do
|
55
|
+
profile = {
|
56
|
+
:options => {
|
57
|
+
:not_if => Proc.new { true }
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
user_defined_rules_satisfied?(profile).should be_false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe :best_matching_configuration do
|
67
|
+
context "no matching configuration" do
|
68
|
+
it "should return nil" do
|
69
|
+
device = parse_device <<-D.strip_heredoc
|
70
|
+
HDMI1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 477mm x 268mm
|
71
|
+
1920x1080 60.0*+
|
72
|
+
1280x1024 75.0 60.0
|
73
|
+
1152x864 75.0
|
74
|
+
1024x768 75.1 60.0
|
75
|
+
800x600 75.0 60.3
|
76
|
+
640x480 75.0 60.0
|
77
|
+
720x400 70.1
|
78
|
+
D
|
79
|
+
|
80
|
+
best_matching_configuration(device, "1280x768", nil).should == nil
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "one matching configuration" do
|
85
|
+
it "should return it" do
|
86
|
+
device = parse_device <<-D.strip_heredoc
|
87
|
+
HDMI1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 477mm x 268mm
|
88
|
+
1920x1080 60.0*+
|
89
|
+
1280x1024 75.0 60.0
|
90
|
+
1152x864 75.0
|
91
|
+
1024x768 75.1 60.0
|
92
|
+
800x600 75.0 60.3
|
93
|
+
640x480 75.0 60.0
|
94
|
+
720x400 70.1
|
95
|
+
D
|
96
|
+
|
97
|
+
best_matching_configuration(device, "1920x1080", nil).should == { :mode => "1920x1080",
|
98
|
+
:rate => "60.0" }
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "more matching configurations due to no rate specified" do
|
103
|
+
it "should return the 'best' configuration" do
|
104
|
+
device = parse_device <<-D.strip_heredoc
|
105
|
+
HDMI1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 477mm x 268mm
|
106
|
+
1920x1080 60.0*+
|
107
|
+
1280x1024 75.0 60.0
|
108
|
+
1152x864 75.0
|
109
|
+
1024x768 75.1 60.0
|
110
|
+
800x600 75.0 60.3
|
111
|
+
640x480 75.0 60.0
|
112
|
+
720x400 70.1
|
113
|
+
D
|
114
|
+
|
115
|
+
best_matching_configuration(device, "1280x1024", nil).should == { :mode => "1280x1024",
|
116
|
+
:rate => "75.0" }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "more matching configurations due to no mode specified" do
|
121
|
+
it "should return the 'best' configuration" do
|
122
|
+
device = parse_device <<-D.strip_heredoc
|
123
|
+
HDMI1 connected 1920x1080+0+0 (normal left inverted right x axis y axis) 477mm x 268mm
|
124
|
+
1920x1080 60.0*+
|
125
|
+
1280x1024 75.0 60.0
|
126
|
+
1152x864 75.0
|
127
|
+
1024x768 75.1 60.0
|
128
|
+
800x600 75.0 60.3
|
129
|
+
640x480 75.0 60.0
|
130
|
+
720x400 70.1
|
131
|
+
D
|
132
|
+
|
133
|
+
best_matching_configuration(device, nil, "60.0").should == { :mode => "1920x1080",
|
134
|
+
:rate => "60.0" }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|