fvwm-window-search 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (10) hide show
  1. checksums.yaml +4 -4
  2. data/Makefile +9 -1
  3. data/README.md +5 -5
  4. data/focus.c +80 -0
  5. data/focus.sh +5 -15
  6. data/fvwm-window-search +5 -8
  7. data/lib.c +24 -0
  8. data/lib.rb +7 -75
  9. data/winlist.c +124 -0
  10. metadata +9 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd55a9dff4bdaee0fcb2e3aab600d85f6c0d40b8a706ea1dd0d7d17e2c0a83c8
4
- data.tar.gz: b47ae4bac4a1a0e68f629306de58e76f6d0111f48e1a9bb686fb63362179161b
3
+ metadata.gz: 2819e711c1b51e6a619fbbf5839481b9a8019fc2c6db40265bda3756c65cad51
4
+ data.tar.gz: 8a58cab8dfc2e504e83d9b71a430a52bb95a63144fc5c499db02172078c57cb6
5
5
  SHA512:
6
- metadata.gz: 13ce630d60e59c33f1bd26c6b29f4dcca5134dabc8d4f0a6a54f812e9d5d50ebb4ca644031e126785cefdaad6cd95ce008a3ca7167deb04b103a0e9dfc43ed53
7
- data.tar.gz: 00311f2288294e4556f3e38b297c33581a7f0ab0b8ced615daf07d31c88682d0555b164a8eb5be6d68729495a8460469ed244511d93d8f2cab0793abac7a8fbc
6
+ metadata.gz: 9d0ab3a08c7bf6993e74c6c8aa07cfbbc1d34dfbc0a31bc6e61f190d6db4fbe0201305967522bf7507470df21e8183e49032b78faa9ea8e77b5f71fe0603d0d5
7
+ data.tar.gz: 58b49f87d0b92f176b6cf4c80ae0d11fc81f471ed096ac64e50bb74c64c5e52cc929ee437c240c298212ee58cf4af4633d965ebe6ebfc15e27f7f2943bf4c48d
data/Makefile CHANGED
@@ -1,6 +1,8 @@
1
1
  out := _out
2
2
  dmenu := $(out)/dmenu
3
- dmenu.commit := 9b38fda6feda68f95754d5b8932b1a69471df960
3
+ dmenu.commit := 1a13d0465d1a6f4f74bc5b07b04c9bd542f20ba6
4
+
5
+ all: $(out)/.dmenu.build $(out)/focus $(out)/winlist
4
6
 
5
7
  $(out)/.dmenu.build: $(out)/.dmenu.$(dmenu.commit) dmenu.patch
6
8
  patch -d $(dmenu) -p1 < dmenu.patch
@@ -12,5 +14,11 @@ $(out)/.dmenu.$(dmenu.commit):
12
14
  git -C $(dmenu) checkout $(dmenu.commit) -q
13
15
  touch $@
14
16
 
17
+ libs := x11 jansson
18
+ LDFLAGS := $(shell pkg-config --libs $(libs))
19
+ CFLAGS := -g -Wall $(shell pkg-config --cflags $(libs))
20
+ $(out)/%: %.c lib.c
21
+ $(LINK.c) $< $(LOADLIBES) $(LDLIBS) -o $@
22
+
15
23
  # an empty target to satisfy rubygems
16
24
  install:
data/README.md CHANGED
@@ -8,12 +8,12 @@ Incremental window search & immediate switch to the selected window
8
8
  ![demo](https://thumbs.gfycat.com/GenerousRingedFlicker-small.gif)
9
9
 
10
10
  * Should work w/ most stackings X11 window managers.
11
- * Filtering by window name/resource/classe.
11
+ * Filtering by window name/resource/class.
12
12
 
13
13
  ## Reqs
14
14
 
15
15
  * Ruby 2.4+
16
- * `xwininfo` & `xdotool` (`xorg-x11-utils` & `xdotool` Fedora pkgs)
16
+ * `dnf install jansson-devel`
17
17
 
18
18
  ## Compilation
19
19
 
@@ -38,7 +38,7 @@ dmenu:
38
38
  fn: Monospace-12
39
39
  b: false
40
40
  selhook-return-key-focus-only: true
41
- filter:
41
+ filter-out:
42
42
  name: ['System Monitor']
43
43
  resource: []
44
44
  class: []
@@ -50,8 +50,8 @@ CLO.
50
50
 
51
51
  [dmenu(1)]: https://manpages.debian.org/unstable/suckless-tools/dmenu.1.en.html
52
52
 
53
- `filter` key tells what windows should be filtered out. Each value in
54
- a subkey is an array of regexes. See the defaults in
53
+ `filter-out` key tells what windows should be filtered out. Each value
54
+ in a subkey is an array of regexes. See the defaults in
55
55
  `fvwm-window-search` file.
56
56
 
57
57
  ## Bugs
data/focus.c ADDED
@@ -0,0 +1,80 @@
1
+ #include <stdlib.h>
2
+ #include <err.h>
3
+ #include <stdio.h>
4
+ #include <stdbool.h>
5
+
6
+ #include <X11/Xlib.h>
7
+ #include <X11/Xatom.h>
8
+
9
+ #include "lib.c"
10
+
11
+ ulong str2id(const char *s) {
12
+ ulong id;
13
+ if (sscanf(s, "0x%lx", &id) != 1 &&
14
+ sscanf(s, "0X%lx", &id) != 1 &&
15
+ sscanf(s, "%lu", &id) != 1) return 0;
16
+ return id;
17
+ }
18
+
19
+ bool client_msg(Display *dpy, Window id, char *msg,
20
+ unsigned long data0, unsigned long data1,
21
+ unsigned long data2, unsigned long data3,
22
+ unsigned long data4) {
23
+ XEvent event;
24
+ long mask = SubstructureRedirectMask | SubstructureNotifyMask;
25
+
26
+ event.xclient.type = ClientMessage;
27
+ event.xclient.serial = 0;
28
+ event.xclient.send_event = True;
29
+ event.xclient.message_type = XInternAtom(dpy, msg, False);
30
+ event.xclient.window = id;
31
+ event.xclient.format = 32;
32
+ event.xclient.data.l[0] = data0;
33
+ event.xclient.data.l[1] = data1;
34
+ event.xclient.data.l[2] = data2;
35
+ event.xclient.data.l[3] = data3;
36
+ event.xclient.data.l[4] = data4;
37
+
38
+ if (XSendEvent(dpy, DefaultRootWindow(dpy), False, mask, &event))
39
+ return true;
40
+ warnx("cannot send %s event", msg);
41
+ return false;
42
+ }
43
+
44
+ bool window_activate(Display *dpy, Window id) {
45
+ long desk = desktop(dpy, id);
46
+ if (-1 != desk) {
47
+ client_msg(dpy, DefaultRootWindow(dpy), "_NET_CURRENT_DESKTOP",
48
+ desk, 0, 0, 0, 0);
49
+ }
50
+
51
+ bool r = client_msg(dpy, id, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0);
52
+ XMapRaised(dpy, id);
53
+ return r;
54
+ }
55
+
56
+ bool window_center_mouse(Display *dpy, ulong id) {
57
+ XWindowAttributes attrs;
58
+ if (!XGetWindowAttributes(dpy, id, &attrs)) return false;
59
+ if (!XWarpPointer(dpy, 0, id, 0, 0, 0, 0, attrs.width/2, attrs.height/2))
60
+ return false;
61
+ XFlush(dpy);
62
+ return true;
63
+ }
64
+
65
+
66
+
67
+ int main(int argc, char **argv) {
68
+ Display *dpy = XOpenDisplay(getenv("DISPLAY"));
69
+ if (!dpy) errx(1, "failed to open display %s", getenv("DISPLAY"));
70
+ if (argc != 2) errx(1, "usage: focus window-id");
71
+
72
+ ulong id = str2id(argv[1]);
73
+ if (!id) errx(1, "invalid window id: `%s`", argv[1]);
74
+
75
+ XSynchronize(dpy, True); // snake oil?
76
+ bool r = window_activate(dpy, id);
77
+ if (!r) return 1;
78
+ r = window_center_mouse(dpy, id);
79
+ return !r;
80
+ }
data/focus.sh CHANGED
@@ -1,22 +1,12 @@
1
1
  #!/bin/sh
2
2
 
3
3
  id=`echo "$1" | awk -F'|' '{print $NF} END { exit $NF == "" ? 1 : 0}'` || {
4
- echo "usage: `basename "$0"` 'name | class | id'"
4
+ echo "usage: `basename "$0"` 'foo | bar | id'"
5
5
  exit 1
6
6
  }
7
7
 
8
- # tries to put a mouse pointer as shown on the picture below (▲ is the
9
- # pointer):
10
- #
11
- # +-------------+
12
- # | Title |
13
- # +-------------+
14
- # | ▲ |
15
- # | |
16
- # | |
17
- # +-------------+
8
+ __filename=`readlink -f "$0"`
9
+ __dirname=`dirname "${__filename}"`
18
10
 
19
- eval "`xdotool getwindowgeometry --shell "$id"`"
20
- x=$((WIDTH/2))
21
-
22
- xdotool windowactivate "$id" mousemove --window "$id" "$x" 0
11
+ # shellcheck disable=2086
12
+ ${__dirname}/_out/focus $id
data/fvwm-window-search CHANGED
@@ -15,7 +15,7 @@ def options
15
15
  "b" => true,
16
16
  "i" => true,
17
17
  },
18
- "filter" => {
18
+ "filter-out" => {
19
19
  "name" => [],
20
20
  "resource" => [],
21
21
  "class" => ['^Fvwm', '!^FvwmIdent$']
@@ -61,18 +61,15 @@ def menu params, text
61
61
  end
62
62
 
63
63
  def main
64
- ['xwininfo', 'xdotool'].each do |util|
65
- errx 1, "no #{util} in PATH" unless which util
66
- end
67
-
68
64
  opt = options
69
65
  begin
70
- winlist = windows_filter opt["filter"], windows
66
+ winlist = windows_filter_out opt["filter-out"], windows
71
67
  rescue RegexpError
72
- errx 1, "filter: #{$!}"
68
+ errx 1, "filter-out: #{$!}"
73
69
  end
74
70
  winlist = winlist.map do |w|
75
- "#{w.name} | #{w.class} | #{w.id}"
71
+ desk = w['desk'] == -1 ? '*' : w['desk']
72
+ [desk, w['class'], w['name'], w['host'], '0x'+w['id'].to_s(16)].join ' | '
76
73
  end.join "\n"
77
74
 
78
75
  menu opt["dmenu"], winlist
data/lib.c ADDED
@@ -0,0 +1,24 @@
1
+ bool prop(Display *dpy, Window wid, Atom expected_type, const char *name,
2
+ u_char **result, ulong *size) {
3
+ Atom type;
4
+ int format;
5
+ ulong bytes_after;
6
+
7
+ Atom atom = XInternAtom(dpy, name, False);
8
+ int r = XGetWindowProperty(dpy, wid, atom, 0L, ~0L, False,
9
+ expected_type, &type, &format,
10
+ size, &bytes_after, result);
11
+ return r == Success && result;
12
+ }
13
+
14
+ long desktop(Display *dpy, Window wid) {
15
+ u_char *prop_val = NULL;
16
+ ulong prop_size;
17
+ if (!prop(dpy, wid, XA_CARDINAL, "_NET_WM_DESKTOP", &prop_val, &prop_size))
18
+ return -2;
19
+
20
+ long r = -1;
21
+ if (prop_val) r = ((long*)prop_val)[0];
22
+ free(prop_val);
23
+ return r;
24
+ }
data/lib.rb CHANGED
@@ -1,75 +1,12 @@
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
1
+ require "json"
62
2
 
63
3
  module FvwmWindowSearch
64
4
  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?)
5
+ cmd = File.join(__dir__, '_out/winlist')
6
+ JSON.parse `#{cmd}`
70
7
  end
71
8
 
72
- def windows_filter patterns, winlist
9
+ def windows_filter_out patterns, winlist
73
10
  desired = -> (type, value) {
74
11
  include = patterns[type].select {|v| v[0] != '!'}
75
12
  exclude = patterns[type].select {|v| v[0] == '!'}.map {|v| v[1..-1]}
@@ -83,9 +20,9 @@ module FvwmWindowSearch
83
20
  true
84
21
  }
85
22
 
86
- winlist.select { |w| desired.call "class", w.class }
87
- .select { |w| desired.call "resource", w.resource }
88
- .select { |w| desired.call "name", w.name }
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'] }
89
26
  end
90
27
 
91
28
  def deep_merge first, second
@@ -97,9 +34,4 @@ module FvwmWindowSearch
97
34
  $stderr.puts "#{File.basename $0} error: #{msg}"
98
35
  exit exit_code
99
36
  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
37
  end
data/winlist.c ADDED
@@ -0,0 +1,124 @@
1
+ #include <stdlib.h>
2
+ #include <err.h>
3
+ #include <stdio.h>
4
+ #include <stdbool.h>
5
+ #include <string.h>
6
+ #include <math.h>
7
+
8
+ #include <X11/Xlib.h>
9
+ #include <X11/Xatom.h>
10
+ #include <jansson.h>
11
+
12
+ #include "lib.c"
13
+
14
+ typedef struct {
15
+ Window *ids;
16
+ ulong size;
17
+ } WinList;
18
+
19
+ // result should be freed
20
+ WinList winlist(Display *dpy) {
21
+ WinList list;
22
+ u_char *result;
23
+
24
+ if (!prop(dpy, DefaultRootWindow(dpy), XA_WINDOW, "_NET_CLIENT_LIST",
25
+ &result, &list.size)) {
26
+ list.size = -1;
27
+ return list;
28
+ }
29
+
30
+ list.ids = (Window*)result;
31
+ return list;
32
+ }
33
+
34
+ // result should be freed
35
+ char* wm_client_machine(Display *dpy, Window wid) {
36
+ u_char *prop_val = NULL;
37
+ ulong prop_size;
38
+ prop(dpy, wid, XA_STRING, "WM_CLIENT_MACHINE", &prop_val, &prop_size);
39
+ return (char*)prop_val;
40
+ }
41
+
42
+ ulong str_index(const char *s, char ch) {
43
+ char *p = strchr(s, ch);
44
+ if (!p) return -1;
45
+ return (ulong)(p - s);
46
+ }
47
+
48
+ typedef struct {
49
+ char *resource;
50
+ char *class_name;
51
+ } ResClass;
52
+
53
+ // result should be freed
54
+ ResClass wm_class(Display *dpy, Window wid) {
55
+ ResClass r = {.resource = NULL};
56
+
57
+ u_char *prop_val = NULL;
58
+ ulong prop_size;
59
+ if (!prop(dpy, wid, XA_STRING, "WM_CLASS", &prop_val, &prop_size))
60
+ return r;
61
+
62
+ ulong idx = str_index((char*)prop_val, '\0');
63
+ if (idx < prop_size) {
64
+ r.resource = (char*)malloc(idx+2);
65
+ snprintf(r.resource, idx+1, "%s", prop_val);
66
+
67
+ ulong len = prop_size-idx;
68
+ r.class_name = (char*)malloc(len+1);
69
+ snprintf(r.class_name, len, "%s", prop_val+idx+1);
70
+ }
71
+
72
+ free(prop_val);
73
+ return r;
74
+ }
75
+
76
+ // result should be freed
77
+ char* wm_name(Display *dpy, Window wid) {
78
+ u_char *prop_val = NULL;
79
+ ulong prop_size;
80
+
81
+ Atom utf8_str = XInternAtom(dpy, "UTF8_STRING", False);
82
+ bool r = prop(dpy, wid, utf8_str, "_NET_WM_NAME", &prop_val, &prop_size);
83
+ if (r && prop_val) return (char*)prop_val;
84
+
85
+ prop(dpy, wid, XA_STRING, "WM_NAME", &prop_val, &prop_size);
86
+ return (char*)prop_val;
87
+ }
88
+
89
+
90
+
91
+ int main() {
92
+ Display *dpy = XOpenDisplay(getenv("DISPLAY"));
93
+ if (!dpy) errx(1, "failed to open display %s", getenv("DISPLAY"));
94
+
95
+ json_t *r = json_array();
96
+ WinList list = winlist(dpy);
97
+ for (ulong idx = 0; idx < list.size; idx++) {
98
+ ulong wid = list.ids[idx];
99
+
100
+ char *host = wm_client_machine(dpy, wid);
101
+ char *name = wm_name(dpy, wid);
102
+ ResClass rc = wm_class(dpy, wid);
103
+
104
+ json_t *line = json_object();
105
+ json_object_set_new(line, "desk", json_integer(desktop(dpy, wid)));
106
+ json_object_set_new(line, "host", json_string(host));
107
+ json_object_set_new(line, "name", json_string(name));
108
+ json_object_set_new(line, "resource", json_string(rc.resource));
109
+ json_object_set_new(line, "class", json_string(rc.class_name));
110
+ json_object_set_new(line, "id", json_integer(wid));
111
+ json_array_append_new(r, line);
112
+
113
+ free(host);
114
+ free(name);
115
+ free(rc.resource);
116
+ free(rc.class_name);
117
+ }
118
+ 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
+ }
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: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Gromnitsky
8
- autorequire:
8
+ autorequire:
9
9
  bindir: "."
10
10
  cert_chain: []
11
- date: 2020-07-21 00:00:00.000000000 Z
11
+ date: 2021-04-04 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 xdotool & xwininfo installed.
17
+ window manager. Requires jansson-devel C library installed.
18
18
 
19
19
  It differs from rofi & co in that it activates (brings up) windows
20
20
  _during_ the search.
@@ -30,13 +30,16 @@ files:
30
30
  - README.md
31
31
  - dmenu.patch
32
32
  - extconf.rb
33
+ - focus.c
33
34
  - focus.sh
35
+ - lib.c
34
36
  - lib.rb
37
+ - winlist.c
35
38
  homepage: https://github.com/gromnitsky/fvwm-window-search
36
39
  licenses:
37
40
  - MIT
38
41
  metadata: {}
39
- post_install_message:
42
+ post_install_message:
40
43
  rdoc_options: []
41
44
  require_paths:
42
45
  - lib
@@ -52,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
55
  version: '0'
53
56
  requirements: []
54
57
  rubygems_version: 3.1.2
55
- signing_key:
58
+ signing_key:
56
59
  specification_version: 4
57
60
  summary: 'A window switcher: an interactive incremental windows search & selection
58
61
  for X Window'