rmonitor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b64db8dea8068825d234898319c35aa04457999f
4
+ data.tar.gz: d95a141b9c7c761a6c417693eac1e3f7cd439876
5
+ SHA512:
6
+ metadata.gz: 143917bc10cbfd192d084629c75829a452299199d3c097a8b878dd3fcc97e0b08349d6226d0ebbb5f8fb77ed23a9858baf9ff3d9f17453b71c2dfeb25741bcde
7
+ data.tar.gz: 9e37a31319ecf41021da2f3c9a9c99670eaa5b2f99a63bcc9a13e67afe484ea9c510912c8903c4d0370414ef6238a4b169b89b776dd5722749424da10b39aa49
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rmonitor (1.0.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ diff-lcs (1.2.4)
10
+ rspec (2.13.0)
11
+ rspec-core (~> 2.13.0)
12
+ rspec-expectations (~> 2.13.0)
13
+ rspec-mocks (~> 2.13.0)
14
+ rspec-core (2.13.1)
15
+ rspec-expectations (2.13.0)
16
+ diff-lcs (>= 1.1.3, < 2.0)
17
+ rspec-mocks (2.13.1)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ rmonitor!
24
+ rspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Jonas Amundsen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,205 @@
1
+ ## RMonitor
2
+
3
+ [![Build Status](https://travis-ci.org/badeball/rmonitor.png)](https://travis-ci.org/badeball/rmonitor)
4
+
5
+ RMonitor is a tool for creating monitor profiles that are easily invoked. This
6
+ is useful when you often find yourself in situations with different monitor
7
+ configurations. It consists of one executable and a ruby configuration file.
8
+ The following example shows a configuration containing monitor profiles
9
+ representing three different monitor setups.
10
+
11
+ ```ruby
12
+ profile "docked" do
13
+ device "HDMI1", :mode => "1920x1080", :rate => "60.0"
14
+ device "HDMI2", :mode => "1920x1080", :rate => "60.0", :right_of => "HDMI1"
15
+ end
16
+
17
+ profile "projector" do
18
+ device "LVDS1", :mode => "1280x800", :rate => "60.0"
19
+ device "VGA1", :right_of => "LVDS1"
20
+ end
21
+
22
+ profile "default" do
23
+ device "LVDS1"
24
+ end
25
+ ```
26
+
27
+ Usage:
28
+
29
+ ```
30
+ Usage: rmonitor [option]
31
+ -c, --create [NAME] Create and output a profile with an optional name
32
+ -i, --invoke NAME Invoke a profile with a given name
33
+ -u, --update Invoke the most preferable profile
34
+ -v, --verbose Verbose output
35
+ -d, --dry-run Do everything except actually update
36
+ --config-path PATH Specify the path to the configuration file (defaults to ~/.config/rmonitor/config.rb
37
+ -h, --help Show this message
38
+ --version Print the version number of rmonitor
39
+ ```
40
+
41
+ ### --create [NAME]
42
+
43
+ Outputs a profile for your current setup.
44
+
45
+ ```
46
+ $ rmonitor --create "docked"
47
+ profile "docked" do
48
+ device "HDMI1", :mode => "1920x1080", :rate => "60.0", :pos => "0x0"
49
+ device "HDMI2", :mode => "1920x1080", :rate => "60.0", :pos => "1920x0"
50
+ end
51
+ ```
52
+
53
+ ### --invoke NAME
54
+
55
+ Invokes a monitor profile by generating an xrandr query.
56
+
57
+ ### --update
58
+
59
+ Parses the configuration file from top to bottom and invokes the first
60
+ invokable profile based on what devices that are currently connected. This is
61
+ likely the most used option and may for instance be used to automatically
62
+ configure your screens upon startup.
63
+
64
+ ## Installation
65
+
66
+ The utility can be installed using `gem`, but is also packaged for Arch Linux.
67
+
68
+ ```
69
+ $ gem install rmonitor
70
+ ```
71
+
72
+ It can be installed system-wide using the following options.
73
+
74
+ ```
75
+ $ gem install --no-user-install -i "$(ruby -e'puts Gem.default_dir')" -n /usr/bin rmonitor
76
+ ```
77
+
78
+ ### Arch Linux
79
+
80
+ ```
81
+ $ yaourt -Syua ruby-rmonitor
82
+ ```
83
+
84
+ ## Configurable options
85
+
86
+ The following examples shows how RMonitor can be configured.
87
+
88
+ ```ruby
89
+ # Specify device location with position
90
+ profile "docked" do
91
+ device "HDMI1", :pos => "0x0"
92
+ device "HDMI2", :pos => "1920x0"
93
+ end
94
+ ```
95
+
96
+ ```ruby
97
+ # Specify device location relative to another device
98
+ # Similar options include :left_of, :above, :below
99
+ profile "docked" do
100
+ device "HDMI1"
101
+ device "HDMI2", :right_of => "HDMI1"
102
+ end
103
+ ```
104
+
105
+ ```ruby
106
+ # Specify device mode (resolution), best rate is chosen automatically
107
+ profile "docked" do
108
+ device "HDMI1", :mode => "1920x1080"
109
+ device "HDMI2", :mode => "1920x1080"
110
+ end
111
+ ```
112
+
113
+ ```ruby
114
+ # Specify device rate, best mode (resolution is chosen automatically
115
+ profile "docked" do
116
+ device "HDMI1", :rate => "60.0"
117
+ device "HDMI2", :rate => "60.0"
118
+ end
119
+ ```
120
+
121
+ ```ruby
122
+ # Specify device mode (resolution) and rate
123
+ profile "docked" do
124
+ device "HDMI1", :mode => "1920x1080", :rate => "60.0"
125
+ device "HDMI2", :mode => "1920x1080", :rate => "60.0"
126
+ end
127
+ ```
128
+
129
+ ```ruby
130
+ # Specify dpi (dots per inch)
131
+ profile "docked", :dpi => 96 do
132
+ device "HDMI1"
133
+ device "HDMI2"
134
+ end
135
+ ```
136
+
137
+ ## Changelog
138
+
139
+ ### 1.0.0
140
+
141
+ * The -a option is removed.
142
+ * Fixed a bug that caused rates with of more than one decimal to not get picked up.
143
+ * Error messages are now outputted to stderr and the program exits with a non-zero status.
144
+ * The library is now turned into a gem.
145
+ * Added a --config-path option.
146
+ * Added a --version option.
147
+
148
+ ### 0.0.9
149
+
150
+ * Added one-letter short options.
151
+ * Fixed an issue where it was impossible to specify DPI as an integer.
152
+
153
+ ### 0.0.8
154
+
155
+ * Added a --dry-run option.
156
+ * Added a --verbose option.
157
+ * Added option for user defined rules to profiles.
158
+
159
+ An :only_if or :not_if option may be specified with a profile. The value
160
+ should correspond to a method defined within the configuration file and
161
+ should return whether or not the profile can be invoked.
162
+
163
+ The following code shows an example of how this is used.
164
+
165
+ ```ruby
166
+ def laptop_lid_open?
167
+ File.read('/proc/acpi/button/lid/LID/state').match(/open/)
168
+ end
169
+
170
+ profile "docked", :only_if => :laptop_lid_open? do
171
+ device "LVDS1"
172
+ device "VGA1", :right_of => "LVDS1"
173
+ end
174
+ ```
175
+
176
+ ### 0.0.7
177
+
178
+ * Added support for the --reflect directive.
179
+ * Added support for the --rotate directive.
180
+ * Added support for the --same-as directive.
181
+
182
+ ### 0.0.6
183
+
184
+ * Added support for the --dpi directive.
185
+
186
+ ### 0.0.5
187
+
188
+ * Fixing bug with finding best configuration.
189
+ * Added support for the --primary directive.
190
+
191
+ ### 0.0.4
192
+
193
+ * The generated command for changing monitor profile now contains a part that first turns of all monitors.
194
+
195
+ ### 0.0.3
196
+
197
+ * Correcting a bug in deducing invokability.
198
+
199
+ ### 0.0.2
200
+
201
+ * Fixing bug with device specified without options.
202
+
203
+ ### 0.0.1
204
+
205
+ * Initial work on the project.
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'rmonitor'
5
+ require 'rmonitor/version'
6
+
7
+ RMonitor.config_path = File.join(Dir.home, '.config', 'rmonitor', 'config.rb')
8
+
9
+ class << self
10
+ attr_accessor :options
11
+ end
12
+
13
+ self.options = { :action => :create }
14
+
15
+ OptionParser.new do |opts|
16
+ opts.banner = "Usage: rmonitor [option]"
17
+
18
+ opts.on("-c", "--create [NAME]", String, "Create and output a profile with an optional name") do |name|
19
+ options[:action] = :create
20
+ options[:name] = name
21
+ end
22
+
23
+ opts.on("-i", "--invoke NAME", String, "Invoke a profile with a given name") do |name|
24
+ options[:action] = :invoke
25
+ options[:name] = name
26
+ end
27
+
28
+ opts.on("-u", "--update", "Invoke the most preferable profile") do
29
+ options[:action] = :update
30
+ end
31
+
32
+ opts.on("-v", "--verbose", "Verbose output") do
33
+ options[:verbose] = true
34
+ end
35
+
36
+ opts.on("-d", "--dry-run", "Do everything except actually update (implies -v)") do
37
+ options[:dry_run] = true
38
+ options[:verbose] = true
39
+ end
40
+
41
+ opts.on("--config-path PATH", String, "Specify the path to the configuration file (defaults to ~/.config/rmonitor/config.rb") do |config_path|
42
+ RMonitor.config_path = config_path
43
+ end
44
+
45
+ opts.on_tail("-h", "--help", "Show this message") do
46
+ puts opts
47
+ exit
48
+ end
49
+
50
+ opts.on_tail("--version", "Print the version number of rmonitor") do
51
+ puts "rmonitor #{RMonitor::VERSION}"
52
+ exit
53
+ end
54
+ end.parse!
55
+
56
+ def v_puts(content)
57
+ if options[:verbose]
58
+ puts content
59
+ end
60
+ end
61
+
62
+ def exit_with(content)
63
+ $stderr.puts content
64
+ exit! 1
65
+ end
66
+
67
+ rm = RMonitor.load
68
+
69
+ if options[:action] == :update
70
+ # Find the first invokable profile
71
+ profile = rm.profiles.find do |profile|
72
+ RMonitor::Profiles.invokable?(rm.devices, profile)
73
+ end
74
+
75
+ if profile
76
+ v_puts "Found #{profile[:name].inspect} that is invokable."
77
+ options[:name] = profile[:name]
78
+ options[:action] = :invoke
79
+ else
80
+ exit_with 'no invokable profile exists'
81
+ end
82
+ end
83
+
84
+ if options[:action] == :invoke
85
+ profile = rm.profiles.find { |p| p[:name] == options[:name] }
86
+
87
+ if profile
88
+ if RMonitor::Profiles.invokable?(rm.devices, profile)
89
+ command = RMonitor::Profiles.to_xrandr(rm.devices, profile)
90
+ v_puts "Invoking #{profile[:name].inspect} by running #{command.inspect}."
91
+ exec(command) unless options[:dry_run]
92
+ else
93
+ exit_with 'this profile is not invokable'
94
+ end
95
+ else
96
+ exit_with 'no profile with that name exists'
97
+ end
98
+
99
+ elsif options[:action] == :create
100
+ puts "profile #{(options[:name] || 'My profile').inspect} do"
101
+ rm.devices.each do |device|
102
+ if device[:enabled]
103
+ puts ' device %s, :mode => %s, :rate => %s, :pos => %s' % [
104
+ device[:name].inspect,
105
+ device[:configuration][:mode].inspect,
106
+ device[:configuration][:rate].inspect,
107
+ device[:pos].inspect,
108
+ ]
109
+ end
110
+ end
111
+ puts 'end'
112
+ end
@@ -0,0 +1,23 @@
1
+ require 'fileutils'
2
+
3
+ require 'rmonitor/devices'
4
+ require 'rmonitor/profiles'
5
+
6
+ class RMonitor
7
+ class XRandRArgumentError < ArgumentError; end
8
+
9
+ class << self
10
+ attr_accessor :config_path
11
+ end
12
+
13
+ attr_accessor :devices, :profiles
14
+
15
+ def initialize(raw_devices_data, raw_profiles_data)
16
+ @devices = Devices.parse(raw_devices_data)
17
+ @profiles = Profiles.parse(raw_profiles_data)
18
+ end
19
+
20
+ def self.load
21
+ self.new(`xrandr -q`, File.new(config_path).read)
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ require 'rmonitor/helpers/xrandr_read_helpers'
2
+
3
+ class RMonitor
4
+ class Devices
5
+ extend XRandRReadHelpers
6
+
7
+ def self.parse(devices_data)
8
+ # Split the blocks of XRandR output
9
+ blocks = split_blocks(devices_data)
10
+
11
+ # Filter out blocks that are not devices
12
+ devices = collect_devices(blocks)
13
+
14
+ # Create a data structure for each block
15
+ devices.map! do |device|
16
+ {
17
+ :name => extract_name(device),
18
+ :pos => extract_pos(device),
19
+ :connected => extract_connected(device),
20
+ :enabled => extract_enabled(device),
21
+ :configuration => extract_configuration(device),
22
+ :configurations => extract_configurations(device),
23
+ }
24
+ end
25
+
26
+ devices
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,38 @@
1
+ class RMonitor
2
+ module DSLHelpers
3
+ class ProfileBuilder
4
+ attr_accessor :profiles
5
+
6
+ def initialize
7
+ @profiles = []
8
+ end
9
+
10
+ def profile(name, options = {}, &block)
11
+ device_builder = DeviceBuilder.new
12
+ device_builder.instance_eval(&block)
13
+
14
+ if options[:only_if]
15
+ options[:only_if] = method(options[:only_if])
16
+ elsif options[:not_if]
17
+ options[:not_if] = method(options[:not_if])
18
+ end
19
+
20
+ @profiles << { :name => name,
21
+ :options => options,
22
+ :devices => device_builder.devices }
23
+ end
24
+ end
25
+
26
+ class DeviceBuilder
27
+ attr_accessor :devices
28
+
29
+ def initialize
30
+ @devices = []
31
+ end
32
+
33
+ def device(name, options = {})
34
+ @devices << { :name => name }.merge(options)
35
+ end
36
+ end
37
+ end
38
+ end