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