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
checksums.yaml
ADDED
@@ -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
data/Gemfile.lock
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
## RMonitor
|
2
|
+
|
3
|
+
[](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.
|
data/bin/rmonitor
ADDED
@@ -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
|
data/lib/rmonitor.rb
ADDED
@@ -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
|