sapristi 0.1.31 → 0.1.32

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fbe69ef3d4dc1b95146ae12ed0806a35e5248d213c2e9c25421cf70e702642bd
4
- data.tar.gz: 61ba8b136d1dd8b7f34b55a945bb4297d046b7277bbcdcc3d6da5b72b73af415
3
+ metadata.gz: 841eaad20944eda7c31a7e9557a2edcb1c894184b89226715d4d07a0959cc4ab
4
+ data.tar.gz: 9ca9977a9fe31b3c8bef49af27a939c0b069843dae18a6316e5eba8e35beffca
5
5
  SHA512:
6
- metadata.gz: a9b2f36d57d26ec4489a88c810c6e46f465188c8b545ed22b5a034a1152fe25a703fae8cd255741584189a01b4ae7126c1eb50be63a5f0c28b8a889ec494aeca
7
- data.tar.gz: d0e84f6ebf91c6a726da427d63e664e9a0b133650ebe9d860ec7518c5a0888a91c546ccd4ef962c6d68970e1f776ea12e8fdccbd9aab01e79851e7088118fc0f
6
+ metadata.gz: 33fd1ecdde12c32c02dc8aacf1ad6dd8d0cc7af0e96d46b6e86c04551969965b72f6de0602c195f2549fa4145bf70131448f0216f34a062ae6a5c732d45800b0
7
+ data.tar.gz: d354af47aea67215e560744c36220b02af47728e06cdb1178a750c96e755578f1943c7845a556e51bae2788c15132bd7fb4628e9d715a207b928a5b2f8be77f9
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Sapristi
2
2
 
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/e168b7940a847148f617/maintainability)](https://codeclimate.com/github/sapristi-tool/sapristi/maintainability)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/e168b7940a847148f617/maintainability)](https://codeclimate.com/github/sapristi-tool/sapristi/maintainability) ![Gem](https://img.shields.io/gem/v/sapristi?style=plastic)
4
4
 
5
5
  ![Sapristi image](/assets/images/sapristi.jpg)
6
6
 
@@ -22,27 +22,33 @@ Install gem
22
22
 
23
23
  > `-f FILE` load your definitions from another file, ie: sapristi -f ~/machine_learning_definitions.csv
24
24
 
25
- > `-v | --verbose` verbose mode.
25
+ > `-v | --verbose` verbose mode
26
26
 
27
- > `--dry-run` dry mode, show your definitions but it doesn't execute them.
27
+ > `--dry-run` dry mode, show your definitions but it doesn't execute them
28
28
 
29
29
  > `-g|--group name` load definitions tagged with group, ie: sapristi -g social
30
+
31
+ > `-h|--help` show help
32
+
33
+ > `-m|--monitors` show available monitors info (including work area size) and exits
34
+
35
+ > `-w|--wait-time wait-time-in-seconds` set wait time for detecting a window (default: 30)
30
36
 
31
37
 
32
38
  ### Configuration example: ~/.sapristi.csv
33
39
 
34
40
  | __Title__ | __Command__ | __Monitor__ | __X__ | __Y__ | __Width__ | __Height__ | __Workspace__ | __Group__ |
35
41
  |-------|---------------------------------------------------------------------------------|---------|------------|------------|--------|--------|-----------|----------|
36
- | | subl ~/projects/ruby/sapristi | | 0 | 0 | 60% | 100% | 0 | sapristi |
37
- | | terminator --working-directory=~/projects/ruby/sapristi | | 60% | 0 | 40% | 50% | 0 | sapristi |
42
+ | | subl ~/projects/ruby/sapristi | | 0% | 0% | 60% | 100% | 0 | sapristi |
43
+ | | terminator --working-directory=~/projects/ruby/sapristi | | 60% | 0% | 40% | 50% | 0 | sapristi |
38
44
  | | zeal | | 60% | 50% | 40% | 50% | 0 | sapristi |
39
- | | subl ~/projects/python/stuff | | 0 | 0 | 60% | 100% | 1 | python |
40
- | | terminator --working-directory=~/projects/python/stuff | | 60% | 0 | 40% | 50% | 1 | python |
45
+ | | subl ~/projects/python/stuff | | 0% | 0% | 60% | 100% | 1 | python |
46
+ | | terminator --working-directory=~/projects/python/stuff | | 60% | 0% | 40% | 50% | 1 | python |
41
47
  | | firefox --new-window https://docs.python.org/3/index.html | | 60% | 50% | 40% | 50% | 1 | python |
42
- | | firefox --new-window https://www.gmail.com | | 0 | 0 | 50% | 100% | 2 | social |
43
- | | firefox --new-window https://www.slack.com | | 50% | 0 | 50% | 50% | 2 | social |
44
- | | firefox --new-window https://www.twitter.com | | 50% | | 50% | 50% | 2 | social |
45
- | | sol | DP-2 | 0 | 0 | 100% | 100% | 3 | games |
48
+ | | firefox --new-window https://www.gmail.com | | 0% | 0% | 50% | 100% | 2 | social |
49
+ | | firefox --new-window https://www.slack.com | | 50% | 0% | 50% | 50% | 2 | social |
50
+ | | firefox --new-window https://www.twitter.com | | 50% | 0% | 50% | 50% | 2 | social |
51
+ | | sol | DP-2 | 0% | 0% | 100% | 100% | 3 | games |
46
52
 
47
53
  The configuration file is a CSV file (comma delimited, no separator). First line is the header line, next lines are definitions. Each definition prescribes:
48
54
  - How choose a window from the current ones, or how to launch a program to produce the window: __Title__, __Command__
@@ -52,17 +58,17 @@ The configuration file is a CSV file (comma delimited, no separator). First line
52
58
 
53
59
  The table above represents a CSV file like the one below:
54
60
  ```
55
- Title,Command,Monitor,X,Y,Width,Height,Workspace
56
- ,subl ~/projects/ruby/sapristi,0,0,60%,100%,0,sapristi
57
- ,terminator --working-directory=~/projects/ruby/sapristi,60%,0,40%,50%,0,sapristi
58
- ,zeal,60%,50%,40%,50%,0,sapristi
59
- ,subl ~/projects/python/stuff,0,0,60%,100%,1,python
60
- ,terminator --working-directory=~/projects/python/stuff,60%,0,40%,50%,1,python
61
- ,firefox --new-window https://docs.python.org/3/index.html,60%,50%,40%,50%,1,python
62
- ,firefox --new-window https://www.gmail.com,0,0,50%,100%,2,social
63
- ,firefox --new-window https://www.slack.com,50%,0,50%,50%,2,social
64
- ,firefox --new-window https://www.twitter.com,50%,50%,50%,2,social
65
- ,sol,DP-2,0,0,100%,100%,3,games
61
+ Title,Command,Monitor,X,Y,Width,Height,Workspace,Group
62
+ ,subl ~/projects/ruby/sapristi,,0%,0%,60%,100%,0,sapristi
63
+ ,terminator --working-directory=~/projects/ruby/sapristi,,60%,0%,40%,50%,0,sapristi
64
+ ,zeal,,60%,50%,40%,50%,0,sapristi
65
+ ,subl ~/projects/python/stuff,,0%,0%,60%,100%,1,python
66
+ ,terminator --working-directory=~/projects/python/stuff,,60%,0%,40%,50%,1,python
67
+ ,firefox --new-window https://docs.python.org/3/index.html,,60%,50%,40%,50%,1,python
68
+ ,firefox --new-window https://www.gmail.com,,0%,0%,50%,100%,2,social
69
+ ,firefox --new-window https://www.slack.com,,50%,0%,50%,50%,2,social
70
+ ,firefox --new-window https://www.twitter.com,,50%,50%,50%,50%,2,social
71
+ ,sol,DP-2,0%,0%,100%,100%,3,games
66
72
  ```
67
73
 
68
74
 
@@ -73,24 +79,27 @@ Title,Command,Monitor,X,Y,Width,Height,Workspace
73
79
  - Twitter.+Firefox
74
80
  - System Monitor
75
81
 
76
- - __Command__(optional): A command. If __Title__ is not provided or there isn't a window that matches it, sapristi will execute __Command__.
77
- Every line has to define a __Title__, a __Command__ or both. Examples:
82
+ - __Command__(optional): A command. If __Title__ is not provided or there isn't a window that matches it, sapristi will execute __Command__. Examples:
78
83
  - firefox --new-window https://www.twitter.com
79
84
  - terminator --working-directory=~/projects/python/stuff
80
85
 
81
-
86
+ (Every line has to define a __Title__, **OR** a __Command__ or both)
87
+
82
88
  - __Monitor__(optional): Monitor name (check your monitor names with xrandr) If a definition specifies a monitor not present or if is empty, window will be placed in the main monitor of the actual environment.
83
89
  - Use monitor when specified.
84
90
  - Use main monitor if monitor name is not found.
85
91
  - Use main monitor if __Monitor__ is not provided.
86
92
 
87
93
  - __X__(mandatory): Absolute or relative. Horizontal top left coordinate to place the window:
88
- - Absolute (pixels): ie 100, 200, 250.
94
+ - Absolute (monitor width): ie 100, 200, 250.
89
95
  - Relative (monitor workarea): 10%, 20%, 50%. Percentage has to be an integer between 0 and 100.
90
96
 
91
97
  - __Y__(mandatory): Absolute or relative. Vertical top left coordinate to place the window:
92
- - Absolute (pixels): ie 100, 200, 250.
98
+ - Absolute (monitor monitor height): ie 100, 200, 250.
93
99
  - Relative (monitor workarea): 10%, 20%, 50%. Percentage has to be an integer between 0 and 100.
100
+
101
+ The work area should be considered when positioning menus and similar popups, to avoid placing them below panels, docks or other desktop components.
102
+ ![workarea image](/assets/images/workarea.jpg)
94
103
 
95
104
  - __Width__(mandatory): Absolute (pixels) or relative (workarea) Window width. Examples: 100, 50%.
96
105
 
@@ -11,12 +11,13 @@ module Sapristi
11
11
  end
12
12
 
13
13
  def run(args)
14
- options = ArgumentsParser.new.parse args
15
- build(options)
16
-
17
- options.file ? @sapristi.run(options.file) : @sapristi.run
14
+ @options = ArgumentsParser.new.parse args
15
+ setup
16
+ process
18
17
  rescue Error => e
19
18
  exit_error 1, e.message
19
+ rescue OptionParser::InvalidOption => e
20
+ exit_error 1, "Error: #{e}, check sapristi -h"
20
21
  rescue StandardError => e
21
22
  error_file = save_stacktrace e
22
23
 
@@ -25,10 +26,17 @@ module Sapristi
25
26
 
26
27
  private
27
28
 
28
- def build(options)
29
- @sapristi.verbose! if options.verbose
30
- @sapristi.dry! if options.dry
31
- @sapristi.filter!(options.group) if options.group
29
+ def process
30
+ return MonitorManager.new.show_monitors if @options.show_monitors
31
+
32
+ @options.file ? @sapristi.run(@options.file) : @sapristi.run
33
+ end
34
+
35
+ def setup
36
+ @sapristi.verbose! if @options.verbose
37
+ @sapristi.dry! if @options.dry
38
+ @sapristi.filter!(@options.group) if @options.group
39
+ @sapristi.wait_time!(@options.wait_time) if @options.wait_time
32
40
  end
33
41
 
34
42
  def save_stacktrace(error)
@@ -21,16 +21,36 @@ module Sapristi
21
21
  end
22
22
 
23
23
  # This method smells of :reek:TooManyStatements
24
+ # rubocop:disable Metrics/AbcSize
25
+ # rubocop:disable Metrics/MethodLength
24
26
  def self.populate_options(opts, args)
25
27
  opts.banner = 'Usage: sapristi [options]'
26
28
  opts.on('-v', '--verbose', 'Verbose mode') { |value| args.verbose = value }
27
29
  opts.on('-g', '--group GROUP', 'Use named group definitions') { |value| args.group = value }
30
+ opts.on('-w', '--wait-time NUMBER_OF_SECONDS (1-120), default=30', 'Wait time for detecting a window') do |value|
31
+ args.wait_time = parse_integer(value, 1, 120)
32
+ end
28
33
  opts.on('--dry-run', 'Dry run') { |value| args.dry = value }
29
34
  opts.on('-f', '--file FILE', 'Read configuration from FILE') { |file| args.file = file }
30
35
  opts.on('-h', '--help', 'Prints this help') do
31
36
  puts opts
32
37
  exit
33
38
  end
39
+ opts.on('-m', '--monitors', 'Show monitor\'s info') { args.show_monitors = true }
40
+ end
41
+ # rubocop:enable Metrics/AbcSize
42
+ # rubocop:enable Metrics/MethodLength
43
+
44
+ def self.parse_integer(value, min = nil, max = nil)
45
+ raise OptionParser::InvalidOption, "'#{value}' is not an integer" unless value.match(/^[0-9]+$/)
46
+
47
+ integer = value.to_i
48
+ raise OptionParser::InvalidOption, "requires a wait time > 0, provided=#{value}" unless min.nil? || integer >= min
49
+ unless max.nil? || integer <= max
50
+ raise OptionParser::InvalidOption, "requires a wait time <= 120 seconds, provided=#{value}"
51
+ end
52
+
53
+ integer
34
54
  end
35
55
  end
36
56
  end
@@ -5,8 +5,11 @@ module Sapristi
5
5
  def initialize(window_manager = WindowManager.new, process_manager = NewProcessWindowDetector.new)
6
6
  @window_manager = window_manager
7
7
  @process_manager = process_manager
8
+ @wait_time = 30
8
9
  end
9
10
 
11
+ attr_accessor :wait_time
12
+
10
13
  def process_definition(definition)
11
14
  window = get_window definition.title, definition.command
12
15
 
@@ -21,7 +24,7 @@ module Sapristi
21
24
 
22
25
  def get_window(title, command)
23
26
  (title && find_one_by_title(title)) ||
24
- (command && @process_manager.detect_window_for_process(command, title)) ||
27
+ (command && @process_manager.detect_window_for_process(command, title, @wait_time)) ||
25
28
  raise(Error, "Couldn't produce a window for this definition")
26
29
  end
27
30
 
@@ -12,6 +12,12 @@ module Sapristi
12
12
 
13
13
  ATTRIBUTES = %i[id main name x y offset_x offset_y work_area work_area_width work_area_height].freeze
14
14
 
15
+ def to_s
16
+ # rubocop:disable Layout/LineLength
17
+ "#{id} #{main ? 'main' : ' '} #{name} #{x}x#{y} workarea[x=#{work_area[0]}, y=#{work_area[1]}, width=#{work_area_width}, height=#{work_area_height}]"
18
+ # rubocop:enable Layout/LineLength
19
+ end
20
+
15
21
  attr_reader(*ATTRIBUTES)
16
22
 
17
23
  def hash
@@ -21,6 +21,13 @@ module Sapristi
21
21
  @os_manager.monitors
22
22
  end
23
23
 
24
+ def show_monitors
25
+ the_monitors = monitors
26
+
27
+ puts "Monitors: #{the_monitors.size}"
28
+ the_monitors.each_value { |monitor| puts monitor }
29
+ end
30
+
24
31
  private
25
32
 
26
33
  def monitor_present?(name)
@@ -47,9 +47,9 @@ module Sapristi
47
47
  start_time = Time.now
48
48
 
49
49
  while Time.now - start_time < timeout_in_seconds # && waiter.alive?
50
- process_window = detect_new_windows.find { |window| window_for?(waiter, program, title, window) }
50
+ new_window = detect_new_windows.find { |window| window_for?(waiter, program, title, window) }
51
51
 
52
- return process_window if process_window
52
+ return new_window if new_window && !splash?(new_window)
53
53
 
54
54
  sleep 0.2
55
55
  end
@@ -105,5 +105,17 @@ module Sapristi
105
105
  def new_window?(window)
106
106
  !previous_windows_ids.include?(window.id)
107
107
  end
108
+
109
+ def splash?(window)
110
+ skip_taskbar?(window) || skip_pager?(window)
111
+ end
112
+
113
+ def skip_taskbar?(window)
114
+ window.state.include? '_NET_WM_STATE_SKIP_TASKBAR'
115
+ end
116
+
117
+ def skip_pager?(window)
118
+ window.state.include? '_NET_WM_STATE_SKIP_PAGER'
119
+ end
108
120
  end
109
121
  end
@@ -7,6 +7,7 @@ module Sapristi
7
7
  @definition_processor = definition_processor
8
8
  @dry = false
9
9
  @verbose = false
10
+ @group = nil
10
11
  end
11
12
 
12
13
  def run(conf_file = Sapristi.user_default_configuration_file)
@@ -32,6 +33,10 @@ module Sapristi
32
33
  logger.level = :info if logger.level > Logger::INFO
33
34
  end
34
35
 
36
+ def wait_time!(wait_time)
37
+ @definition_processor.wait_time = wait_time
38
+ end
39
+
35
40
  def filter!(group)
36
41
  @group = group
37
42
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sapristi
4
- VERSION = '0.1.31'
4
+ VERSION = '0.1.32'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sapristi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.31
4
+ version: 0.1.32
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sapristi dev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-25 00:00:00.000000000 Z
11
+ date: 2020-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -284,6 +284,7 @@ files:
284
284
  - README.md
285
285
  - Rakefile
286
286
  - assets/images/sapristi.jpg
287
+ - assets/images/workarea.jpg
287
288
  - bin/console
288
289
  - bin/sapristi
289
290
  - bin/setup