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.
@@ -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