fvwm-window-search 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Makefile +16 -0
- data/README.md +51 -0
- data/dmenu.patch +128 -0
- data/extconf.rb +1 -0
- data/focus.sh +7 -0
- data/fvwm-window-search +64 -0
- data/lib.rb +105 -0
- metadata +54 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1365fa4326358bdf06efd23fa5e8a42348dfad1a4acae24f169fa603c0c6f8c2
|
4
|
+
data.tar.gz: da1fc15018240a4d37bad24726e81f60cfe44232e7e23b172d30c7d5d384d91c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2bd53629fa900f2c1b30e6cd02a4de7db3bde4676ec5b0c5dcef9c5ef9a26c3c174cce33a58470060643da89532fccb478609110d31fdc7faeb1aab765a8706a
|
7
|
+
data.tar.gz: 0ecc94c5b3161697d3f71cf02e20ef06d70b4d878c5b224b6f3737335aa494bd9b6bb5637c97e953b36169f6b67a13aeaff916b5885806649554b3f18590286c
|
data/Makefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
out := _out
|
2
|
+
dmenu := $(out)/dmenu
|
3
|
+
dmenu.commit := 9b38fda6feda68f95754d5b8932b1a69471df960
|
4
|
+
|
5
|
+
$(out)/.dmenu.build: $(out)/.dmenu.$(dmenu.commit) dmenu.patch
|
6
|
+
patch -d $(dmenu) -p1 < dmenu.patch
|
7
|
+
make -C $(dmenu)
|
8
|
+
touch $@
|
9
|
+
|
10
|
+
$(out)/.dmenu.$(dmenu.commit):
|
11
|
+
git clone https://git.suckless.org/dmenu $(dmenu)
|
12
|
+
git -C $(dmenu) checkout $(dmenu.commit) -q
|
13
|
+
touch $@
|
14
|
+
|
15
|
+
# an empty target to satisfy rubygems
|
16
|
+
install:
|
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# fvwm-window-search
|
2
|
+
|
3
|
+
Incremental window search & immediate switch to the selected window
|
4
|
+
*during the search*. Uses a patched version of dmenu as a GUI.
|
5
|
+
|
6
|
+
$ gem install fvwm-window-search
|
7
|
+
|
8
|
+
![A screenshot of running fvwm-window-search](https://raw.github.com/gromnitsky/fvwm-window-search/master/screnshot1.png)
|
9
|
+
|
10
|
+
* Should work w/ any X11 window manager.
|
11
|
+
* Filtering by windows names/resources/classes.
|
12
|
+
|
13
|
+
## Reqs
|
14
|
+
|
15
|
+
* Ruby (tested w/ 2.7.0)
|
16
|
+
* `xwininfo` & `xdotool` (`xorg-x11-utils` & `xdotool` Fedora pkgs)
|
17
|
+
|
18
|
+
## Compilation
|
19
|
+
|
20
|
+
Type `make`. This clones the dmenu repo, patches & builds it. It does
|
21
|
+
not contravene w/ a system-installed dmenu.
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Run `fvwm-window-search`.
|
26
|
+
|
27
|
+
To customise dmenu or filtering, create a yaml file
|
28
|
+
`$XDG_CONFIG_HOME/fvwm-window-search/conf.yaml`, e.g.:
|
29
|
+
|
30
|
+
~~~
|
31
|
+
---
|
32
|
+
dmenu:
|
33
|
+
fn: Monospace-12
|
34
|
+
b: false
|
35
|
+
filter:
|
36
|
+
name: ['System Monitor']
|
37
|
+
~~~
|
38
|
+
|
39
|
+
This passes to dmenu `-fn` & `-b` CLOs & instructs to filter out any
|
40
|
+
window that matches `System Monitor` regexp in its title.
|
41
|
+
|
42
|
+
See the defaults in `fvwm-window-search` file.
|
43
|
+
|
44
|
+
## Bugs
|
45
|
+
|
46
|
+
* Tested only w/ Fvwm3.
|
47
|
+
* No distinction between normal & iconified windows.
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
MIT.
|
data/dmenu.patch
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
diff --git a/Makefile b/Makefile
|
2
|
+
index a03a95c..ee5cffb 100644
|
3
|
+
--- a/Makefile
|
4
|
+
+++ b/Makefile
|
5
|
+
@@ -17,8 +17,8 @@ options:
|
6
|
+
.c.o:
|
7
|
+
$(CC) -c $(CFLAGS) $<
|
8
|
+
|
9
|
+
-config.h:
|
10
|
+
- cp config.def.h $@
|
11
|
+
+config.h: config.def.h
|
12
|
+
+ cp $< $@
|
13
|
+
|
14
|
+
$(OBJ): arg.h config.h config.mk drw.h
|
15
|
+
|
16
|
+
diff --git a/config.def.h b/config.def.h
|
17
|
+
index 1edb647..c6d081f 100644
|
18
|
+
--- a/config.def.h
|
19
|
+
+++ b/config.def.h
|
20
|
+
@@ -21,3 +21,6 @@ static unsigned int lines = 0;
|
21
|
+
* for example: " /?\"&[]"
|
22
|
+
*/
|
23
|
+
static const char worddelimiters[] = " ";
|
24
|
+
+
|
25
|
+
+/* -selhook option; run a command on every selection */
|
26
|
+
+static const char *selection_hook = NULL;
|
27
|
+
diff --git a/dmenu.c b/dmenu.c
|
28
|
+
index 65f25ce..69254ee 100644
|
29
|
+
--- a/dmenu.c
|
30
|
+
+++ b/dmenu.c
|
31
|
+
@@ -304,6 +304,62 @@ movewordedge(int dir)
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
+/*
|
36
|
+
+ * Replacing all instances of ' with '\'' then enclosing the whole
|
37
|
+
+ * string in single quotes (') is the safe way.
|
38
|
+
+ * */
|
39
|
+
+static char *
|
40
|
+
+shellquote(const char *src)
|
41
|
+
+{
|
42
|
+
+ char *quoted, *q;
|
43
|
+
+ const char *p;
|
44
|
+
+
|
45
|
+
+ if (!src) return NULL;
|
46
|
+
+ p = src;
|
47
|
+
+
|
48
|
+
+ if (!(quoted = malloc(strlen(src)*4+3))) die("malloc failed");
|
49
|
+
+ q = quoted;
|
50
|
+
+
|
51
|
+
+ *q++ = '\'';
|
52
|
+
+ while (*p) {
|
53
|
+
+ if (*p == '\'') {
|
54
|
+
+ *q++ = '\'';
|
55
|
+
+ *q++ = '\\';
|
56
|
+
+ *q++ = '\'';
|
57
|
+
+ *q++ = '\'';
|
58
|
+
+ } else {
|
59
|
+
+ *q++ = *p;
|
60
|
+
+ }
|
61
|
+
+
|
62
|
+
+ p++;
|
63
|
+
+ }
|
64
|
+
+ *q++ = '\'';
|
65
|
+
+ *q = '\0';
|
66
|
+
+
|
67
|
+
+ return quoted;
|
68
|
+
+}
|
69
|
+
+
|
70
|
+
+void
|
71
|
+
+selhook(const char *user_command, const struct item *item)
|
72
|
+
+{
|
73
|
+
+ char *cmd, *quoted_item_text;
|
74
|
+
+ size_t cmd_size;
|
75
|
+
+
|
76
|
+
+ if ( !(user_command && item)) return;
|
77
|
+
+ quoted_item_text = shellquote(item->text);
|
78
|
+
+ /* printf("QUOTED: >%s<\n", quoted_item_text); */
|
79
|
+
+
|
80
|
+
+ cmd_size = strlen(user_command) + strlen(quoted_item_text) + 1;
|
81
|
+
+ if ( !(cmd = malloc(cmd_size))) die("malloc failed");
|
82
|
+
+ snprintf(cmd, cmd_size, user_command, quoted_item_text);
|
83
|
+
+
|
84
|
+
+ /* printf("SELECTED HOOK: %s\n", cmd); */
|
85
|
+
+ system(cmd);
|
86
|
+
+
|
87
|
+
+ free(quoted_item_text);
|
88
|
+
+ free(cmd);
|
89
|
+
+}
|
90
|
+
+
|
91
|
+
static void
|
92
|
+
keypress(XKeyEvent *ev)
|
93
|
+
{
|
94
|
+
@@ -464,6 +520,7 @@ insert:
|
95
|
+
break;
|
96
|
+
case XK_Return:
|
97
|
+
case XK_KP_Enter:
|
98
|
+
+ selhook(selection_hook, sel);
|
99
|
+
puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
|
100
|
+
if (!(ev->state & ControlMask)) {
|
101
|
+
cleanup();
|
102
|
+
@@ -572,6 +629,7 @@ run(void)
|
103
|
+
break;
|
104
|
+
case KeyPress:
|
105
|
+
keypress(&ev.xkey);
|
106
|
+
+ selhook(selection_hook, sel);
|
107
|
+
break;
|
108
|
+
case SelectionNotify:
|
109
|
+
if (ev.xselection.property == utf8)
|
110
|
+
@@ -690,7 +748,8 @@ static void
|
111
|
+
usage(void)
|
112
|
+
{
|
113
|
+
fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
|
114
|
+
- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
|
115
|
+
+ " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n"
|
116
|
+
+ " [-selhook cmd]\n", stderr);
|
117
|
+
exit(1);
|
118
|
+
}
|
119
|
+
|
120
|
+
@@ -733,6 +792,8 @@ main(int argc, char *argv[])
|
121
|
+
colors[SchemeSel][ColFg] = argv[++i];
|
122
|
+
else if (!strcmp(argv[i], "-w")) /* embedding window id */
|
123
|
+
embed = argv[++i];
|
124
|
+
+ else if(!strcmp(argv[i], "-selhook")) /* a command to run */
|
125
|
+
+ selection_hook = argv[++i];
|
126
|
+
else
|
127
|
+
usage();
|
128
|
+
|
data/extconf.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# a dummy file that forces rubygems to run our Makefile during 'gem install'
|
data/focus.sh
ADDED
data/fvwm-window-search
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative './lib'
|
4
|
+
include FvwmWindowSearch
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
def options_load
|
8
|
+
xdg_config_home = ENV['XDG_CONFIG_HOME'] || File.expand_path('~/.config')
|
9
|
+
conf = File.join xdg_config_home, 'fvwm-window-search', 'conf.yaml'
|
10
|
+
r = File.read conf rescue nil
|
11
|
+
YAML.load r, conf rescue errx 1, "invalid config: #{$!}" if r
|
12
|
+
end
|
13
|
+
|
14
|
+
def options
|
15
|
+
default = {
|
16
|
+
"dmenu" => { # each key is a corresponding CL option
|
17
|
+
"selhook" => File.join(__dir__, "focus.sh %s"),
|
18
|
+
"fn" => "Monospace-10",
|
19
|
+
"l" => 8,
|
20
|
+
"b" => true,
|
21
|
+
"i" => true,
|
22
|
+
},
|
23
|
+
"filter" => {
|
24
|
+
"name" => [],
|
25
|
+
"resource" => [],
|
26
|
+
"class" => ['^Fvwm', '!^FvwmIdent$']
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
deep_merge(default, options_load || {})
|
31
|
+
end
|
32
|
+
|
33
|
+
def menu params, text
|
34
|
+
cmd = [File.join(__dir__, "_out/dmenu/dmenu")]
|
35
|
+
params = params.map do |k,v|
|
36
|
+
k = "-"+k
|
37
|
+
if !!v == v
|
38
|
+
v ? k : nil
|
39
|
+
else
|
40
|
+
[k,v]
|
41
|
+
end
|
42
|
+
end.reject(&:nil?).flatten.map(&:to_s)
|
43
|
+
IO.popen(cmd + params, 'w') { |ios| ios.puts text }
|
44
|
+
end
|
45
|
+
|
46
|
+
def main
|
47
|
+
['xwininfo', 'xdotool'].each do |util|
|
48
|
+
errx 1, "no #{util} in PATH" unless which util
|
49
|
+
end
|
50
|
+
|
51
|
+
opt = options
|
52
|
+
begin
|
53
|
+
winlist = windows_filter opt["filter"], windows
|
54
|
+
rescue RegexpError
|
55
|
+
errx 1, "filter: #{$!}"
|
56
|
+
end
|
57
|
+
winlist = winlist.map do |w|
|
58
|
+
"#{w.name} | #{w.class} | #{w.id}"
|
59
|
+
end.join "\n"
|
60
|
+
|
61
|
+
menu opt["dmenu"], winlist
|
62
|
+
end
|
63
|
+
|
64
|
+
main
|
data/lib.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
module FvwmWindowSearch; end
|
2
|
+
|
3
|
+
class FvwmWindowSearch::Window
|
4
|
+
def initialize xwininfo_line
|
5
|
+
@line = xwininfo_line.match(/^([x0-9a-f]+)\s+(["\(].+["\)]):\s+\((.*)\)\s+([x0-9+-]+)\s+([0-9+-]+)$/)
|
6
|
+
raise "invalid xwininfo line" unless @line
|
7
|
+
@dim = parse
|
8
|
+
end
|
9
|
+
|
10
|
+
def parse
|
11
|
+
dim = {}
|
12
|
+
if @line[4]
|
13
|
+
m4 = @line[4].match(/^([0-9]+)x([0-9]+)\+([0-9-]+)\+([0-9-]+)$/)
|
14
|
+
if m4
|
15
|
+
dim[:w] = m4[1].to_i
|
16
|
+
dim[:h] = m4[2].to_i
|
17
|
+
dim[:x_rel] = m4[3].to_i
|
18
|
+
dim[:y_rel] = m4[4].to_i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
if @line[5]
|
23
|
+
m5 = @line[5].match(/^\+([0-9-]+)\+([0-9-]+)$/)
|
24
|
+
if m5
|
25
|
+
dim[:x] = m5[1].to_i
|
26
|
+
dim[:y] = m5[2].to_i
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
dim
|
31
|
+
end
|
32
|
+
|
33
|
+
def id; @line[1]; end
|
34
|
+
|
35
|
+
def name;
|
36
|
+
return unless @line[2]
|
37
|
+
@line[2] == '(has no name)' ? nil : @line[2][1..-2]
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource; @line[3]&.split(' ')&.dig(0)&.slice(1..-2); end
|
41
|
+
def class; @line[3]&.split(' ')&.dig(1)&.slice(1..-2); end
|
42
|
+
def width; @dim[:w]; end
|
43
|
+
def height; @dim[:h]; end
|
44
|
+
def x; @dim[:x]; end # an absolute upper-left X
|
45
|
+
def y; @dim[:y]; end # an absolute upper-left Y
|
46
|
+
def x_rel; @dim[:x_rel]; end
|
47
|
+
def y_rel; @dim[:y_rel]; end
|
48
|
+
|
49
|
+
def useful?
|
50
|
+
return false unless @line
|
51
|
+
return false if width == 0 || height == 0
|
52
|
+
return false if (x == x_rel) && (y == y_rel)
|
53
|
+
return false if x_rel > 0 || y_rel > 0
|
54
|
+
return false unless self.class
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
"#<Window> id=#{id}, name=#{name}, resource=#{resource}, class=#{self.class}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module FvwmWindowSearch
|
64
|
+
def windows
|
65
|
+
`xwininfo -root -tree`.split("\n")
|
66
|
+
.select {|v| v.match(/^\s*0x.+/)}
|
67
|
+
.map(&:strip)
|
68
|
+
.map {|v| Window.new(v)}
|
69
|
+
.select(&:useful?)
|
70
|
+
end
|
71
|
+
|
72
|
+
def windows_filter patterns, winlist
|
73
|
+
desired = -> (type, value) {
|
74
|
+
include = patterns[type].filter{|v| v[0] != '!'}
|
75
|
+
exclude = patterns[type].filter{|v| v[0] == '!'}.map {|v| v[1..-1]}
|
76
|
+
|
77
|
+
exclude.each do |pattern|
|
78
|
+
return true if value.match pattern
|
79
|
+
end
|
80
|
+
include.each do |pattern|
|
81
|
+
return false if value.match pattern
|
82
|
+
end
|
83
|
+
true
|
84
|
+
}
|
85
|
+
|
86
|
+
winlist.filter { |w| desired.call "class", w.class }
|
87
|
+
.filter{ |w| desired.call "resource", w.resource }
|
88
|
+
.filter{ |w| desired.call "name", w.name }
|
89
|
+
end
|
90
|
+
|
91
|
+
def deep_merge first, second
|
92
|
+
merger = proc { |_, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
93
|
+
first.merge(second, &merger)
|
94
|
+
end
|
95
|
+
|
96
|
+
def errx exit_code, msg
|
97
|
+
$stderr.puts "#{File.basename $0} error: #{msg}"
|
98
|
+
exit exit_code
|
99
|
+
end
|
100
|
+
|
101
|
+
def which cmd
|
102
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).map {|v| File.join v, cmd }
|
103
|
+
.find {|v| File.executable?(v) && !File.directory?(v) }
|
104
|
+
end
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fvwm-window-search
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexander Gromnitsky
|
8
|
+
autorequire:
|
9
|
+
bindir: "."
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-18 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
Search for windows interactively using a patched dmenu utility.
|
15
|
+
Originally made for Fvwm, it's been fully rewritten to work out-of-the-box
|
16
|
+
with any window manager. Requires xdotool & xwininfo installed.
|
17
|
+
email: alexander.gromnitsky@gmail.com
|
18
|
+
executables:
|
19
|
+
- fvwm-window-search
|
20
|
+
extensions:
|
21
|
+
- extconf.rb
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- "./fvwm-window-search"
|
25
|
+
- Makefile
|
26
|
+
- README.md
|
27
|
+
- dmenu.patch
|
28
|
+
- extconf.rb
|
29
|
+
- focus.sh
|
30
|
+
- lib.rb
|
31
|
+
homepage: https://github.com/gromnitsky/fvwm-window-search
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
metadata: {}
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubygems_version: 3.1.2
|
51
|
+
signing_key:
|
52
|
+
specification_version: 4
|
53
|
+
summary: Interactive incremental windows search & selection for X Window
|
54
|
+
test_files: []
|