fvwm-window-search 2.0.0 → 2.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 +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
|