fvwm-window-search 2.0.0 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +17 -7
- data/fvwm-window-search +58 -29
- data/lib.c +1 -1
- data/winlist.c +14 -9
- metadata +5 -6
- data/lib.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6334e45909512868782e41da16d60c26114f43230f0579b1e2e60918fadca628
|
4
|
+
data.tar.gz: 16e792a204f7ed859752781dc537a9e106200ac2c722dd8a581510d90d51d164
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca486c7cac4a994953c0855877b687a44283c106b6e21cc2cba56ea4a95248ddec2b4a9fa3b0b4df1d82f1d8743a33d949566371c68e54c5c96d4646db4af531
|
7
|
+
data.tar.gz: 3b6f993cdc08f38fb695fb99ca9f2e50c09150bd3cb65d7423b9d99aa97917e71115863b5b46cf1372d34cf8f5dd40b623fd98df9456f8c8b5cb17006e1167c1
|
data/README.md
CHANGED
@@ -12,21 +12,21 @@ Incremental window search & immediate switch to the selected window
|
|
12
12
|
|
13
13
|
## Reqs
|
14
14
|
|
15
|
-
* Ruby 2.
|
15
|
+
* Ruby 2.1+
|
16
16
|
* `dnf install jansson-devel`
|
17
17
|
|
18
18
|
## Compilation
|
19
19
|
|
20
|
-
Type `make`. This clones the dmenu repo, patches & builds it. It
|
21
|
-
|
20
|
+
Type `make`. This clones the dmenu repo, patches & builds it. It
|
21
|
+
doesn't interfere w/ a system-installed dmenu.
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
25
|
~~~
|
26
26
|
$ ./fvwm-window-search -h
|
27
27
|
Usage: fvwm-window-search [options]
|
28
|
-
-c path
|
29
|
-
-r
|
28
|
+
-c path an alternative path to conf.yaml
|
29
|
+
-r switch to a window only when <Return> is pressed
|
30
30
|
~~~
|
31
31
|
|
32
32
|
To customise dmenu or filtering, create a yaml file
|
@@ -50,10 +50,20 @@ CLO.
|
|
50
50
|
|
51
51
|
[dmenu(1)]: https://manpages.debian.org/unstable/suckless-tools/dmenu.1.en.html
|
52
52
|
|
53
|
-
`filter-out` key tells what windows should be
|
54
|
-
|
53
|
+
`filter-out` key tells what windows should be ignored. Each value in a
|
54
|
+
subkey is an array of regexes. See the defaults at the top of
|
55
55
|
`fvwm-window-search` file.
|
56
56
|
|
57
|
+
## Start-up time
|
58
|
+
|
59
|
+
As a task switcher, the program must not only run fast, but also
|
60
|
+
*start* fast. I managed to get it under 70ms on my laptop, when you
|
61
|
+
run `./fvwm-window-search` directly from the repo.
|
62
|
+
|
63
|
+
This is not the case with rubygems! The latter generates a stub script
|
64
|
+
that invokes `./fvwm-window-search` file. This indirection may add
|
65
|
+
~140ms of additional delay.
|
66
|
+
|
57
67
|
## Bugs
|
58
68
|
|
59
69
|
* Tested only w/ Fvwm3.
|
data/fvwm-window-search
CHANGED
@@ -1,19 +1,18 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
1
|
+
#!/usr/bin/env -S ruby --disable-gems
|
2
2
|
|
3
|
-
require_relative './lib'
|
4
|
-
include FvwmWindowSearch
|
5
3
|
require 'yaml'
|
4
|
+
require 'json'
|
6
5
|
require 'optparse'
|
7
6
|
|
8
7
|
def options
|
9
8
|
default = {
|
10
|
-
"dmenu" => {
|
11
|
-
"selhook" => File.join(__dir__, "focus.sh %s"),
|
12
|
-
"selhook-return-key-focus-only" => false,
|
9
|
+
"dmenu" => { # each key corresponds to a dmenu CL option, except for selhook*
|
13
10
|
"fn" => "Monospace-10",
|
14
11
|
"l" => 8,
|
15
12
|
"b" => true,
|
16
13
|
"i" => true,
|
14
|
+
"selhook" => File.join(__dir__, "focus.sh %s"),
|
15
|
+
"selhook-return-key-focus-only" => false, # FIXME: rename
|
17
16
|
},
|
18
17
|
"filter-out" => {
|
19
18
|
"name" => [],
|
@@ -22,34 +21,38 @@ def options
|
|
22
21
|
}
|
23
22
|
}
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
deep_merge
|
24
|
+
args = options_command_line
|
25
|
+
file = options_config_file(args) || {}
|
26
|
+
deep_merge default, deep_merge(file, args)
|
28
27
|
end
|
29
28
|
|
30
|
-
def
|
29
|
+
def options_command_line
|
31
30
|
opt = { "dmenu" => {} }
|
32
31
|
OptionParser.new do |o|
|
33
32
|
o.on("-c path", "an alternative path to conf.yaml") { |v| opt["conf"] = v }
|
34
|
-
o.on("-r", "
|
33
|
+
o.on("-r", "switch to a window only when <Return> is pressed") do
|
35
34
|
opt["dmenu"]["selhook-return-key-focus-only"] = true
|
36
35
|
end
|
37
36
|
end.parse!
|
38
37
|
opt
|
39
38
|
end
|
40
39
|
|
41
|
-
def
|
42
|
-
|
40
|
+
def options_config_file opt
|
41
|
+
file = opt["conf"] || -> do
|
43
42
|
xdg_config_home = ENV['XDG_CONFIG_HOME'] || File.expand_path('~/.config')
|
44
43
|
File.join xdg_config_home, 'fvwm-window-search', 'conf.yaml'
|
45
44
|
end.call
|
46
|
-
r = File.read
|
47
|
-
YAML.
|
45
|
+
r = File.read file rescue nil
|
46
|
+
YAML.safe_load(r, filename: file) rescue abort "invalid config: #{$!}" if r
|
48
47
|
end
|
49
48
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
49
|
+
def deep_merge first, second
|
50
|
+
merger = proc {|_,v1,v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
51
|
+
first.merge(second, &merger)
|
52
|
+
end
|
53
|
+
|
54
|
+
def dmenu_cmd params
|
55
|
+
[File.join(__dir__, "_out/dmenu/dmenu")] + params.map do |k,v|
|
53
56
|
k = "-"+k
|
54
57
|
if !!v == v
|
55
58
|
v ? k : nil
|
@@ -57,22 +60,48 @@ def menu params, text
|
|
57
60
|
[k,v]
|
58
61
|
end
|
59
62
|
end.reject(&:nil?).flatten.map(&:to_s)
|
60
|
-
|
63
|
+
end
|
64
|
+
|
65
|
+
def desired patterns, window
|
66
|
+
match = -> (type, value) {
|
67
|
+
include = patterns[type].select {|v| v[0] != '!'}
|
68
|
+
exclude = patterns[type].select {|v| v[0] == '!'}.map {|v| v[1..-1]}
|
69
|
+
|
70
|
+
exclude.each do |pattern|
|
71
|
+
return true if value.match pattern
|
72
|
+
end
|
73
|
+
include.each do |pattern|
|
74
|
+
return false if value.match pattern
|
75
|
+
end
|
76
|
+
true
|
77
|
+
}
|
78
|
+
|
79
|
+
match.call("class", window['class']) &&
|
80
|
+
match.call("resource", window['resource']) &&
|
81
|
+
match.call("name", window['name'])
|
61
82
|
end
|
62
83
|
|
63
84
|
def main
|
64
85
|
opt = options
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
86
|
+
pp opt if $DEBUG
|
87
|
+
dmenu = IO.popen(dmenu_cmd(opt['dmenu']), 'r+')
|
88
|
+
|
89
|
+
IO.popen(File.join(__dir__, '_out/winlist')).each_line do |line|
|
90
|
+
begin
|
91
|
+
w = JSON.parse line
|
92
|
+
rescue
|
93
|
+
dmenu.puts $!.to_s.gsub(/\n+/m, ' ') # let a user see an error
|
94
|
+
next
|
95
|
+
end
|
96
|
+
|
97
|
+
next unless desired opt['filter-out'], w
|
98
|
+
|
71
99
|
desk = w['desk'] == -1 ? '*' : w['desk']
|
72
|
-
[desk, w['class'], w['name'], w['host'], '0x'+w['id'].to_s(16)].join ' | '
|
73
|
-
end
|
100
|
+
dmenu.puts [desk, w['class'], w['name'], w['host'], '0x'+w['id'].to_s(16)].join ' | '
|
101
|
+
end
|
74
102
|
|
75
|
-
|
103
|
+
dmenu.close
|
76
104
|
end
|
77
105
|
|
78
|
-
|
106
|
+
# not __FILE__ == $0, for $0 points to a generated stub after `gem install ...`
|
107
|
+
main unless defined? Minitest
|
data/lib.c
CHANGED
@@ -17,7 +17,7 @@ long desktop(Display *dpy, Window wid) {
|
|
17
17
|
if (!prop(dpy, wid, XA_CARDINAL, "_NET_WM_DESKTOP", &prop_val, &prop_size))
|
18
18
|
return -2;
|
19
19
|
|
20
|
-
long r = -1;
|
20
|
+
long r = -1; // means a window is in a 'sticky' mode
|
21
21
|
if (prop_val) r = ((long*)prop_val)[0];
|
22
22
|
free(prop_val);
|
23
23
|
return r;
|
data/winlist.c
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
/*
|
2
|
+
produces line-delimited JSON of currently managed windows by an X
|
3
|
+
window manager:
|
4
|
+
|
5
|
+
{"desk":0,"host":"hm76","name":"xterm","resource":"xterm","class":"XTerm","id":67108878}
|
6
|
+
*/
|
7
|
+
|
1
8
|
#include <stdlib.h>
|
2
9
|
#include <err.h>
|
3
10
|
#include <stdio.h>
|
@@ -16,7 +23,7 @@ typedef struct {
|
|
16
23
|
ulong size;
|
17
24
|
} WinList;
|
18
25
|
|
19
|
-
// result should be freed
|
26
|
+
// result (WinList.ids) should be freed
|
20
27
|
WinList winlist(Display *dpy) {
|
21
28
|
WinList list;
|
22
29
|
u_char *result;
|
@@ -50,7 +57,7 @@ typedef struct {
|
|
50
57
|
char *class_name;
|
51
58
|
} ResClass;
|
52
59
|
|
53
|
-
// result should be freed
|
60
|
+
// result (ResClass.*) should be freed
|
54
61
|
ResClass wm_class(Display *dpy, Window wid) {
|
55
62
|
ResClass r = {.resource = NULL};
|
56
63
|
|
@@ -92,7 +99,6 @@ int main() {
|
|
92
99
|
Display *dpy = XOpenDisplay(getenv("DISPLAY"));
|
93
100
|
if (!dpy) errx(1, "failed to open display %s", getenv("DISPLAY"));
|
94
101
|
|
95
|
-
json_t *r = json_array();
|
96
102
|
WinList list = winlist(dpy);
|
97
103
|
for (ulong idx = 0; idx < list.size; idx++) {
|
98
104
|
ulong wid = list.ids[idx];
|
@@ -108,7 +114,11 @@ int main() {
|
|
108
114
|
json_object_set_new(line, "resource", json_string(rc.resource));
|
109
115
|
json_object_set_new(line, "class", json_string(rc.class_name));
|
110
116
|
json_object_set_new(line, "id", json_integer(wid));
|
111
|
-
|
117
|
+
|
118
|
+
char *dump = json_dumps(line, JSON_COMPACT);
|
119
|
+
printf("%s\n", dump);
|
120
|
+
free(dump);
|
121
|
+
json_decref(line);
|
112
122
|
|
113
123
|
free(host);
|
114
124
|
free(name);
|
@@ -116,9 +126,4 @@ int main() {
|
|
116
126
|
free(rc.class_name);
|
117
127
|
}
|
118
128
|
XFree(list.ids);
|
119
|
-
|
120
|
-
char *dump = json_dumps(r, JSON_COMPACT);
|
121
|
-
printf("%s\n", dump);
|
122
|
-
free(dump);
|
123
|
-
json_decref(r);
|
124
129
|
}
|
metadata
CHANGED
@@ -1,20 +1,20 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fvwm-window-search
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Gromnitsky
|
8
8
|
autorequire:
|
9
9
|
bindir: "."
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-04-
|
11
|
+
date: 2021-04-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
A window switcher: search for windows interactively using a patched
|
15
15
|
dmenu utility (comes with the gem). This was originally made for Fvwm,
|
16
16
|
but it's been fully rewritten to work out-of-the-box with any stacking
|
17
|
-
window manager. Requires jansson-devel C library
|
17
|
+
window manager. Requires a preinstalled jansson-devel C library.
|
18
18
|
|
19
19
|
It differs from rofi & co in that it activates (brings up) windows
|
20
20
|
_during_ the search.
|
@@ -33,7 +33,6 @@ files:
|
|
33
33
|
- focus.c
|
34
34
|
- focus.sh
|
35
35
|
- lib.c
|
36
|
-
- lib.rb
|
37
36
|
- winlist.c
|
38
37
|
homepage: https://github.com/gromnitsky/fvwm-window-search
|
39
38
|
licenses:
|
@@ -47,14 +46,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
47
46
|
requirements:
|
48
47
|
- - ">="
|
49
48
|
- !ruby/object:Gem::Version
|
50
|
-
version: 2.
|
49
|
+
version: 2.1.0
|
51
50
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
51
|
requirements:
|
53
52
|
- - ">="
|
54
53
|
- !ruby/object:Gem::Version
|
55
54
|
version: '0'
|
56
55
|
requirements: []
|
57
|
-
rubygems_version: 3.
|
56
|
+
rubygems_version: 3.2.3
|
58
57
|
signing_key:
|
59
58
|
specification_version: 4
|
60
59
|
summary: 'A window switcher: an interactive incremental windows search & selection
|
data/lib.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
require "json"
|
2
|
-
|
3
|
-
module FvwmWindowSearch
|
4
|
-
def windows
|
5
|
-
cmd = File.join(__dir__, '_out/winlist')
|
6
|
-
JSON.parse `#{cmd}`
|
7
|
-
end
|
8
|
-
|
9
|
-
def windows_filter_out patterns, winlist
|
10
|
-
desired = -> (type, value) {
|
11
|
-
include = patterns[type].select {|v| v[0] != '!'}
|
12
|
-
exclude = patterns[type].select {|v| v[0] == '!'}.map {|v| v[1..-1]}
|
13
|
-
|
14
|
-
exclude.each do |pattern|
|
15
|
-
return true if value.match pattern
|
16
|
-
end
|
17
|
-
include.each do |pattern|
|
18
|
-
return false if value.match pattern
|
19
|
-
end
|
20
|
-
true
|
21
|
-
}
|
22
|
-
|
23
|
-
winlist.select { |w| desired.call "class", w['class'] }
|
24
|
-
.select { |w| desired.call "resource", w['resource'] }
|
25
|
-
.select { |w| desired.call "name", w['name'] }
|
26
|
-
end
|
27
|
-
|
28
|
-
def deep_merge first, second
|
29
|
-
merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
30
|
-
first.merge(second, &merger)
|
31
|
-
end
|
32
|
-
|
33
|
-
def errx exit_code, msg
|
34
|
-
$stderr.puts "#{File.basename $0} error: #{msg}"
|
35
|
-
exit exit_code
|
36
|
-
end
|
37
|
-
end
|