fvwm-window-search 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. checksums.yaml +7 -0
  2. data/Makefile +16 -0
  3. data/README.md +51 -0
  4. data/dmenu.patch +128 -0
  5. data/extconf.rb +1 -0
  6. data/focus.sh +7 -0
  7. data/fvwm-window-search +64 -0
  8. data/lib.rb +105 -0
  9. metadata +54 -0
@@ -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
@@ -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:
@@ -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.
@@ -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
+
@@ -0,0 +1 @@
1
+ # a dummy file that forces rubygems to run our Makefile during 'gem install'
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ id=`echo "$1" | awk -F'|' '{print $NF} END { exit $NF == "" ? 1 : 0}'` || {
4
+ echo "usage: `basename "$0"` 'name | class | id'"
5
+ exit 1
6
+ }
7
+ xdotool windowactivate "$id" mousemove --window "$id" 0 0
@@ -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: []