sapristi 0.1.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/.gitignore +12 -0
- data/.reek.yml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +11 -0
- data/.travis.yml +7 -0
- data/COMM-LICENSE.txt +1 -0
- data/Gemfile +8 -0
- data/Guardfile +28 -0
- data/LICENSE.txt +2 -0
- data/README.md +39 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/sapristi +49 -0
- data/bin/setup +8 -0
- data/lib/sapristi.rb +33 -0
- data/lib/sapristi/adapters/linux/monitor_manager.rb +73 -0
- data/lib/sapristi/adapters/linux/process_manager.rb +36 -0
- data/lib/sapristi/adapters/linux/window_manager.rb +52 -0
- data/lib/sapristi/arguments_parser.rb +34 -0
- data/lib/sapristi/attribute_normalizer.rb +77 -0
- data/lib/sapristi/configuration_loader.rb +66 -0
- data/lib/sapristi/definition.rb +77 -0
- data/lib/sapristi/definition_parser.rb +66 -0
- data/lib/sapristi/definition_processor.rb +36 -0
- data/lib/sapristi/monitor.rb +33 -0
- data/lib/sapristi/monitor_manager.rb +39 -0
- data/lib/sapristi/new_process_window_detector.rb +74 -0
- data/lib/sapristi/sapristi.rb +61 -0
- data/lib/sapristi/version.rb +5 -0
- data/lib/sapristi/window_manager.rb +83 -0
- data/sapristi.gemspec +60 -0
- metadata +315 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4eb894c76aeaf49b67d080e819ff77c14023712a6194303b16a306477ef33c95
|
4
|
+
data.tar.gz: 58f367a04dabf628064f678bda0ebb0a1bf53193fac090838944e3823e7d737c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a9334da04bfff62237ead2e376d24f71795c4486fafe01a346753b6c343782b5d322e47f7ae6fb5abc69918ec6e5324dc977dd25368d63d81c1b163aabfbe1c0
|
7
|
+
data.tar.gz: c2f55b6b0b508e0543b12e45bdbdcc49d1a5759435881c3bf967b5d9964633573cd410163e9e8ff0a0dfc3d92005b9fd19884be0416139eedcef87c8f6485018
|
data/.gitignore
ADDED
data/.reek.yml
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/COMM-LICENSE.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
The commercial license is designed to for you to use Sapristi for commercial use or/and any kind of organizations.
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
group :red_green_refactor, halt_on_fail: true do
|
4
|
+
guard :rspec, cmd: 'rspec' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(?:sapristi/)?(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { 'spec' }
|
8
|
+
watch('bin/sapristi') { 'spec/sapristi_runner_spec.rb' }
|
9
|
+
end
|
10
|
+
|
11
|
+
guard :rubocop, all_on_start: false, cli: ['--format', 'html', '-o', './tmp/rubocop.html'] do
|
12
|
+
watch(%r{^spec/.+\.rb$})
|
13
|
+
watch(%r{^lib/.+\.rb$})
|
14
|
+
watch('bin/sapristi')
|
15
|
+
end
|
16
|
+
|
17
|
+
guard :rubycritic do
|
18
|
+
watch(%r{^spec/.+\.rb$})
|
19
|
+
watch(%r{^lib/.+\.rb$})
|
20
|
+
watch('bin/sapristi')
|
21
|
+
end
|
22
|
+
|
23
|
+
guard :reek, all_on_start: false, run_all: false, cli: ['--format', 'html', '>', './tmp/reek.html'] do
|
24
|
+
watch(%r{^spec/.+\.rb$})
|
25
|
+
watch(%r{^lib/.+\.rb$})
|
26
|
+
watch('bin/sapristi')
|
27
|
+
end
|
28
|
+
end
|
data/LICENSE.txt
ADDED
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Sapristi
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sapristi`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'sapristi'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install sapristi
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sapristi-tool/sapristi.
|
36
|
+
|
37
|
+
## License
|
38
|
+
|
39
|
+
Please see [LICENSE](https://github.com/sapristi-tool/sapristi/blob/master/LICENSE.txt) for personal usage and [COMM-LICENSE](https://github.com/sapristi-tool/sapristi/blob/master/COMM-LICENSE.txt) for commercial usage.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'sapristi'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/sapristi
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'sapristi'
|
6
|
+
|
7
|
+
module Sapristi
|
8
|
+
class Runner
|
9
|
+
def initialize
|
10
|
+
@sapristi = Sapristi.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(args)
|
14
|
+
options = ArgumentsParser.new.parse args
|
15
|
+
build(options)
|
16
|
+
|
17
|
+
options.file ? @sapristi.run(options.file) : @sapristi.run
|
18
|
+
rescue Error => e
|
19
|
+
exit_error 1, e.message
|
20
|
+
rescue StandardError => e
|
21
|
+
error_file = save_stacktrace e
|
22
|
+
|
23
|
+
exit_error 2, "Sapristi crashed, see #{error_file}"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def build(options)
|
29
|
+
@sapristi.verbose! if options.verbose
|
30
|
+
@sapristi.dry! if options.dry
|
31
|
+
end
|
32
|
+
|
33
|
+
def save_stacktrace(error)
|
34
|
+
file = File.join '/tmp', "sapristi.stacktrace#{Time.new.to_i}.log"
|
35
|
+
File.open(file, 'w') do |f|
|
36
|
+
f.write "#{error.class}: #{error.message}\n\n"
|
37
|
+
f.write error.backtrace.join("\n")
|
38
|
+
end
|
39
|
+
file
|
40
|
+
end
|
41
|
+
|
42
|
+
def exit_error(status, message)
|
43
|
+
$stderr.puts message
|
44
|
+
exit status
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
Sapristi::Runner.new.run(ARGV) if $PROGRAM_NAME == __FILE__
|
data/bin/setup
ADDED
data/lib/sapristi.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sapristi/version'
|
4
|
+
require 'sapristi/attribute_normalizer'
|
5
|
+
require 'sapristi/monitor_manager'
|
6
|
+
require 'sapristi/definition_parser'
|
7
|
+
require 'sapristi/configuration_loader'
|
8
|
+
require 'sapristi/definition'
|
9
|
+
require 'sapristi/window_manager'
|
10
|
+
require 'sapristi/definition_processor'
|
11
|
+
require 'sapristi/sapristi'
|
12
|
+
require 'sapristi/arguments_parser'
|
13
|
+
require 'sapristi/adapters/linux/monitor_manager'
|
14
|
+
require 'sapristi/adapters/linux/window_manager'
|
15
|
+
require 'sapristi/adapters/linux/process_manager'
|
16
|
+
require 'sapristi/new_process_window_detector'
|
17
|
+
require 'sapristi/monitor'
|
18
|
+
require 'logger'
|
19
|
+
|
20
|
+
module Sapristi
|
21
|
+
class Error < StandardError; end
|
22
|
+
|
23
|
+
def self.logger
|
24
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
25
|
+
log.progname = name
|
26
|
+
log.level = Logger::WARN
|
27
|
+
|
28
|
+
log.formatter = proc do |_severity, _datetime, _progname, msg|
|
29
|
+
"#{msg}\n"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sapristi
|
4
|
+
module Linux
|
5
|
+
class MonitorManager
|
6
|
+
RESOLUTION = '(?<x>[0-9]+)/[0-9]+x(?<y>[0-9]+)/[0-9]+'
|
7
|
+
OFFSET = '(?<offset_x>[0-9]+)\\+(?<offset_y>[0-9]+)'
|
8
|
+
MONITOR_LINE_REGEX = /^\s*+(?<id>[0-9]+):\s*\+(?<main>\*)?(?<name>[^\s]+)\s+#{RESOLUTION}\+#{OFFSET}.*$/.freeze
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
# https://ruby-gnome2.osdn.jp/hiki.cgi?Gdk%3A%3ADisplay
|
12
|
+
@screen = Gdk::Screen.default
|
13
|
+
end
|
14
|
+
|
15
|
+
def monitors
|
16
|
+
list_monitors.split("\n")[1..nil]
|
17
|
+
.map { |line| extract_monitor_info(line) }
|
18
|
+
.each_with_object({}) { |monitor, memo| memo[monitor['name']] = monitor }
|
19
|
+
rescue StandardError => e
|
20
|
+
raise Error, "Error fetching monitor information: #{e}"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def list_monitors
|
26
|
+
`xrandr --listmonitors`
|
27
|
+
end
|
28
|
+
|
29
|
+
def extract_monitor_info(line)
|
30
|
+
monitor_info = MonitorManager.parse_line line
|
31
|
+
Monitor.new monitor_info.merge work_area(monitor_info)
|
32
|
+
end
|
33
|
+
|
34
|
+
public
|
35
|
+
|
36
|
+
def self.parse_line(line)
|
37
|
+
matcher = line.match(MONITOR_LINE_REGEX)
|
38
|
+
matcher.names.each_with_object({}) do |name, memo|
|
39
|
+
value = matcher[name]
|
40
|
+
memo[name] = value&.match(/^[0-9]+$/) ? value.to_i : value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def work_area(monitor_info)
|
45
|
+
area = monitors_work_area[monitor_info['id']]
|
46
|
+
{
|
47
|
+
'work_area' => MonitorManager.dimensions(area, monitor_info),
|
48
|
+
'work_area_width' => area.width,
|
49
|
+
'work_area_height' => area.height
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.dimensions(work_area, monitor_info)
|
54
|
+
offset_x = monitor_info['offset_x']
|
55
|
+
offset_y = monitor_info['offset_y']
|
56
|
+
x_start = work_area.x
|
57
|
+
y_start = work_area.y
|
58
|
+
[
|
59
|
+
x_start - offset_x,
|
60
|
+
y_start - offset_y,
|
61
|
+
x_start + work_area.width - offset_x,
|
62
|
+
y_start + work_area.height - offset_y
|
63
|
+
]
|
64
|
+
end
|
65
|
+
|
66
|
+
def monitors_work_area
|
67
|
+
@screen.n_monitors.times.each_with_object({}) do |monitor_id, memo|
|
68
|
+
memo[monitor_id] = @screen.get_monitor_workarea(monitor_id)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sapristi
|
4
|
+
module Linux
|
5
|
+
class ProcessManager
|
6
|
+
def execute_and_detach(cmd)
|
7
|
+
process_pid = begin
|
8
|
+
Process.spawn(cmd)
|
9
|
+
rescue StandardError
|
10
|
+
raise Error, "Error executing process: #{$ERROR_INFO}"
|
11
|
+
end
|
12
|
+
::Sapristi.logger.info "Launch #{cmd.split[0]}, process=#{process_pid}"
|
13
|
+
Process.detach process_pid
|
14
|
+
end
|
15
|
+
|
16
|
+
def kill(waiter)
|
17
|
+
Process.kill 'KILL', waiter.pid
|
18
|
+
# sleep 1 # XLIB error for op code
|
19
|
+
raise Error, 'Error executing process, it didn\'t open a window'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.user_pids
|
23
|
+
user_id = `id -u`.strip
|
24
|
+
`ps -u #{user_id}`.split("\n")[1..nil].map(&:to_i)
|
25
|
+
end
|
26
|
+
|
27
|
+
def cmd_for_pid(pid)
|
28
|
+
cmd = "ps -o cmd -p #{pid}"
|
29
|
+
line = `#{cmd}`.split("\n")[1]
|
30
|
+
raise Error, "No process found pid=#{pid}" unless line
|
31
|
+
|
32
|
+
line
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wmctrl'
|
4
|
+
|
5
|
+
module Sapristi
|
6
|
+
module Linux
|
7
|
+
class WindowManager
|
8
|
+
def initialize(display = WMCtrl.display)
|
9
|
+
@display = display
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :display
|
13
|
+
|
14
|
+
def close(window)
|
15
|
+
@display.action_window(window.id, :close)
|
16
|
+
|
17
|
+
#
|
18
|
+
# sleep to allow a Graceful Dead to the window process
|
19
|
+
#
|
20
|
+
# X Error of failed request: BadWindow (invalid Window parameter)
|
21
|
+
# Major opcode of failed request: 20 (X_GetProperty)
|
22
|
+
# Resource id in failed request: 0x2200008
|
23
|
+
# Serial number of failed request: 1095
|
24
|
+
# Current serial number in output stream: 1095
|
25
|
+
sleep TIME_TO_APPLY_DIMENSIONS
|
26
|
+
end
|
27
|
+
|
28
|
+
def windows(args = {})
|
29
|
+
@display.windows args
|
30
|
+
end
|
31
|
+
|
32
|
+
def workspaces
|
33
|
+
@display.desktops
|
34
|
+
end
|
35
|
+
|
36
|
+
GRAVITY = 0
|
37
|
+
TIME_TO_APPLY_DIMENSIONS = 0.25
|
38
|
+
|
39
|
+
def move_resize(window, geometry)
|
40
|
+
remove_extended_hints(window) if window.maximized_horizontally? || window.maximized_vertically?
|
41
|
+
@display.action_window(window.id, :move_resize, GRAVITY, *geometry)
|
42
|
+
sleep TIME_TO_APPLY_DIMENSIONS
|
43
|
+
end
|
44
|
+
|
45
|
+
EXTENDED_HINTS = %w[maximized_vert maximized_horz].freeze
|
46
|
+
|
47
|
+
def remove_extended_hints(window)
|
48
|
+
display.action_window(window.id, :change_state, 'remove', *EXTENDED_HINTS)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module Sapristi
|
7
|
+
class ArgumentsParser
|
8
|
+
def initialize
|
9
|
+
@args = OpenStruct.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(options)
|
13
|
+
ArgumentsParser.build_parser(@args).parse!(options)
|
14
|
+
@args
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.build_parser(args)
|
18
|
+
OptionParser.new do |opts|
|
19
|
+
ArgumentsParser.populate_options(opts, args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.populate_options(opts, args)
|
24
|
+
opts.banner = 'Usage: sapristi [options]'
|
25
|
+
opts.on('-v', '--verbose', 'Verbose mode') { |value| args.verbose = value }
|
26
|
+
opts.on('--dry-run', 'Dry run') { |value| args.dry = value }
|
27
|
+
opts.on('-f', '--file FILE', 'Read configuration from FILE') { |file| args.file = file }
|
28
|
+
opts.on('-h', '--help', 'Prints this help') do
|
29
|
+
puts opts
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|