sapristi 0.1.31 → 0.1.32

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