fvwm-window-search 1.0.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 +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
|
+

|
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: []
|