Introduction
Introduction Statistics Contact Development Disclaimer Help
st-autocomplete-20240703-6508693.diff - sites - public wiki contents of suckles…
git clone git://git.suckless.org/sites
Log
Files
Refs
---
st-autocomplete-20240703-6508693.diff (21473B)
---
1 From 650869359d3568dd2a000d474054e835a9c7ac74 Mon Sep 17 00:00:00 2001
2 From: elbachir-one <[email protected]>
3 Date: Wed, 3 Jul 2024 22:44:40 +0100
4 Subject: [PATCH] The use of mkstemp'
5
6 ---
7 Makefile | 3 +
8 autocomplete.h | 16 +++
9 config.def.h | 12 ++
10 st-autocomplete | 310 ++++++++++++++++++++++++++++++++++++++++++++++++
11 st.c | 227 +++++++++++++++++++++++++++++++++++
12 st.h | 2 +
13 x.c | 9 ++
14 7 files changed, 579 insertions(+)
15 create mode 100644 autocomplete.h
16 create mode 100644 st-autocomplete
17
18 diff --git a/Makefile b/Makefile
19 index 93fed02..9aff9e0 100644
20 --- a/Makefile
21 +++ b/Makefile
22 @@ -38,6 +38,8 @@ install: st
23 mkdir -p $(DESTDIR)$(PREFIX)/bin
24 cp -f st $(DESTDIR)$(PREFIX)/bin
25 chmod 755 $(DESTDIR)$(PREFIX)/bin/st
26 + cp -f st-autocomplete $(DESTDIR)$(PREFIX)/bin
27 + chmod 755 $(DESTDIR)$(PREFIX)/bin/st-autocomplete
28 mkdir -p $(DESTDIR)$(MANPREFIX)/man1
29 sed "s/VERSION/$(VERSION)/g" < st.1 > $(DESTDIR)$(MANPREFIX)/ma…
30 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/st.1
31 @@ -46,6 +48,7 @@ install: st
32
33 uninstall:
34 rm -f $(DESTDIR)$(PREFIX)/bin/st
35 + rm -f $(DESTDIR)$(PREFIX)/bin/st-autocomplete
36 rm -f $(DESTDIR)$(MANPREFIX)/man1/st.1
37
38 .PHONY: all clean dist install uninstall
39 diff --git a/autocomplete.h b/autocomplete.h
40 new file mode 100644
41 index 0000000..fc88447
42 --- /dev/null
43 +++ b/autocomplete.h
44 @@ -0,0 +1,16 @@
45 +# ifndef __ST_AUTOCOMPLETE_H
46 +# define __ST_AUTOCOMPLETE_H
47 +
48 +enum {
49 + ACMPL_DEACTIVATE,
50 + ACMPL_WORD,
51 + ACMPL_WWORD,
52 + ACMPL_FUZZY_WORD,
53 + ACMPL_FUZZY_WWORD,
54 + ACMPL_FUZZY,
55 + ACMPL_SUFFIX,
56 + ACMPL_SURROUND,
57 + ACMPL_UNDO,
58 +};
59 +
60 +# endif // __ST_AUTOCOMPLETE_H
61 diff --git a/config.def.h b/config.def.h
62 index 2cd740a..b74e03e 100644
63 --- a/config.def.h
64 +++ b/config.def.h
65 @@ -170,6 +170,8 @@ static unsigned int defaultattr = 11;
66 */
67 static uint forcemousemod = ShiftMask;
68
69 +#include "autocomplete.h"
70 +
71 /*
72 * Internal mouse shortcuts.
73 * Beware that overloading Button1 will disable the selection.
74 @@ -187,6 +189,8 @@ static MouseShortcut mshortcuts[] = {
75 #define MODKEY Mod1Mask
76 #define TERMMOD (ControlMask|ShiftMask)
77
78 +#define ACMPL_MOD ControlMask|Mod1Mask
79 +
80 static Shortcut shortcuts[] = {
81 /* mask keysym function argumen…
82 { XK_ANY_MOD, XK_Break, sendbreak, {.i = …
83 @@ -201,6 +205,14 @@ static Shortcut shortcuts[] = {
84 { TERMMOD, XK_Y, selpaste, {.i = …
85 { ShiftMask, XK_Insert, selpaste, {.i = …
86 { TERMMOD, XK_Num_Lock, numlock, {.i = …
87 + { ACMPL_MOD, XK_slash, autocomplete, { .i = …
88 + { ACMPL_MOD, XK_period, autocomplete, { .i = …
89 + { ACMPL_MOD, XK_comma, autocomplete, { .i = …
90 + { ACMPL_MOD, XK_apostrophe, autocomplete, { .i = …
91 + { ACMPL_MOD, XK_semicolon, autocomplete, { .i = …
92 + { ACMPL_MOD, XK_bracketright,autocomplete, { .i = …
93 + { ACMPL_MOD, XK_bracketleft, autocomplete, { .i = …
94 + { ACMPL_MOD, XK_equal, autocomplete, { .i = …
95 };
96
97 /*
98 diff --git a/st-autocomplete b/st-autocomplete
99 new file mode 100644
100 index 0000000..0fad536
101 --- /dev/null
102 +++ b/st-autocomplete
103 @@ -0,0 +1,310 @@
104 +#!/usr/bin/perl
105 +#######################################################################…
106 +# Copyright (C) 2012-2017 Wojciech Siewierski …
107 +# …
108 +# This program is free software: you can redistribute it and/or modify …
109 +# it under the terms of the GNU General Public License as published by …
110 +# the Free Software Foundation, either version 3 of the License, or …
111 +# (at your option) any later version. …
112 +# …
113 +# This program is distributed in the hope that it will be useful, …
114 +# but WITHOUT ANY WARRANTY; without even the implied warranty of …
115 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the …
116 +# GNU General Public License for more details. …
117 +# …
118 +# You should have received a copy of the GNU General Public License …
119 +# along with this program. If not, see <http://www.gnu.org/licenses/>.…
120 +#######################################################################…
121 +
122 +my ($cmd, $cursor_row, $cursor_column) = @ARGV;
123 +
124 +my $lines = [];
125 +my $lines1 = [];
126 +
127 +my $last_line = -1;
128 +my $lines_before_cursor = 0;
129 +
130 +while (<stdin>)
131 +{
132 + $last_line++;
133 +
134 + s/[^[:print:]]/?/g;
135 +
136 + if ($last_line < $cursor_row)
137 + {
138 + unshift @{$lines1}, $_;
139 + $lines_before_cursor++;
140 + }
141 + else
142 + {
143 + unshift @{$lines}, $_;
144 + }
145 +}
146 +
147 +foreach (@{$lines1})
148 +{
149 + unshift @{$lines}, $_;
150 +}
151 +
152 +my $cursor_row_in = $cursor_row;
153 +
154 +$cursor_row = $last_line;
155 +
156 +
157 +$self = {};
158 +
159 +# A reference to a function that transforms the completed word
160 +# into a regex matching the completions. Usually generated by
161 +# generate_matcher().
162 +#
163 +# For example
164 +# $fun = generate_matcher(".*");
165 +# $fun->("foo");
166 +# would return "f.*o.*o"
167 +#
168 +# In other words, indirectly decides which characters can
169 +# appear in the completion.
170 +my $matcher;
171 +
172 +# A regular expression matching a character before each match.
173 +# For example, it you want to match the text after a
174 +# whitespace, set it to "\s".
175 +my $char_class_before;
176 +
177 +# A regular expression matching every character in the entered
178 +# text that will be used to find matching completions. Usually
179 +# "\w" or similar.
180 +my $char_class_to_complete;
181 +
182 +# A regular expression matching every allowed last character
183 +# of the completion (uses greedy matching).
184 +my $char_class_at_end;
185 +
186 +if ($cmd eq 'word-complete') {
187 + # Basic word completion. Completes the current word
188 + # without any special matching.
189 + $char_class_before = '[^-\w]';
190 + $matcher = sub { quotemeta shift }; # identity
191 + $char_class_at_end = '[-\w]';
192 + $char_class_to_complete = '[-\w]';
193 +} elsif ($cmd eq 'WORD-complete') {
194 + # The same as above but in the Vim meaning of a "WORD" --
195 + # whitespace delimited.
196 + $char_class_before = '\s';
197 + $matcher = sub { quotemeta shift };
198 + $char_class_at_end = '\S';
199 + $char_class_to_complete = '\S';
200 +} elsif ($cmd eq 'fuzzy-word-complete' ||
201 + $cmd eq 'skeleton-word-complete') {
202 + # Fuzzy completion of the current word.
203 + $char_class_before = '[^-\w]';
204 + $matcher = generate_matcher('[-\w]*');
205 + $char_class_at_end = '[-\w]';
206 + $char_class_to_complete = '[-\w]';
207 +} elsif ($cmd eq 'fuzzy-WORD-complete') {
208 + # Fuzzy completion of the current WORD.
209 + $char_class_before = '\s';
210 + $matcher = generate_matcher('\S*');
211 + $char_class_at_end = '\S';
212 + $char_class_to_complete = '\S';
213 +} elsif ($cmd eq 'fuzzy-complete' ||
214 + $cmd eq 'skeleton-complete') {
215 + # Fuzzy completion of an arbitrary text.
216 + $char_class_before = '\W';
217 + $matcher = generate_matcher('.*?');
218 + $char_class_at_end = '\w';
219 + $char_class_to_complete = '\S';
220 +} elsif ($cmd eq 'suffix-complete') {
221 + # Fuzzy completion of an completing suffixes, like
222 + # completing test=hello from /blah/hello.
223 + $char_class_before = '\S';
224 + $matcher = generate_matcher('\S*');
225 + $char_class_at_end = '\S';
226 + $char_class_to_complete = '\S';
227 +} elsif ($cmd eq 'surround-complete') {
228 + # Completing contents of quotes and braces.
229 +
230 + # Here we are using three named groups: s, b, p for quotes, bra…
231 + # and parenthesis.
232 + $char_class_before = '((?<q>["\'`])|(?<b>\[)|(?<p>\())';
233 +
234 + $matcher = generate_matcher('.*?');
235 +
236 + # Here we match text till enclosing pair, using perl conditiona…
237 + # regexps (?(condition)yes-expression|no-expression).
238 + # \0 is used to hack concatenation with '*' later in the code.
239 + $char_class_at_end = '.*?(.(?=(?(<b>)\]|((?(<p>)\)|\g{q}))…
240 + $char_class_to_complete = '\S';
241 +}
242 +
243 +
244 +# use the last used word or read the word behind the cursor
245 +my $word_to_complete = read_word_at_coord($self, $cursor_row, $cursor_c…
246 + …
247 +
248 +print stdout "$word_to_complete\n";
249 +
250 +if ($word_to_complete) {
251 + while (1) {
252 + # ignore the completed word itself
253 + $self->{already_completed}{$word_to_complete} = 1;
254 +
255 + # continue the last search or start from the current row
256 + my $completion = find_match($self,
257 + …
258 + …
259 + …
260 + …
261 + …
262 + if ($completion) {
263 + print stdout $completion."\n".join ("\n", @{$se…
264 + }
265 + else {
266 + last;
267 + }
268 + }
269 +}
270 +
271 +######################################################################
272 +
273 +sub highlight_match {
274 + my ($self, $linenum, $completion) = @_;
275 +
276 + # clear_highlight($self);
277 +
278 + my $line = @{$lines}[$linenum];
279 + my $re = quotemeta $completion;
280 +
281 + $line =~ /$re/;
282 +
283 + my $beg = $-[0];
284 + my $end = $+[0];
285 +
286 + if ($linenum >= $lines_before_cursor)
287 + {
288 + $lline = $last_line - $lines_before_cursor;
289 + $linenum -= $lines_before_cursor;
290 + $linenum = $lline - $linenum;
291 + $linenum += $lines_before_cursor;
292 + }
293 +
294 +
295 + $self->{highlight} = [$linenum, $beg, $end];
296 +}
297 +
298 +######################################################################
299 +
300 +sub read_word_at_coord {
301 + my ($self, $row, $col, $char_class) = @_;
302 +
303 + $_ = substr(@{$lines} [$row], 0, $col); # get the current line up t…
304 + s/.*?($char_class*)$/$1/; # ...and read the last word…
305 + return $_;
306 +}
307 +
308 +######################################################################
309 +
310 +# Returns a function that takes a string and returns that string with
311 +# this function's argument inserted between its every two characters.
312 +# The resulting string is used as a regular expression matching the
313 +# completion candidates.
314 +sub generate_matcher {
315 + my $regex_between = shift;
316 +
317 + sub {
318 + $_ = shift;
319 +
320 + # sorry for this lispy code, I couldn't resist ;)
321 + (join "$regex_between",
322 + (map quotemeta,
323 + (split //)))
324 + }
325 +}
326 +
327 +######################################################################
328 +
329 +# Checks whether the completion found by find_match() was already
330 +# found and if it was, calls find_match() again to find the next
331 +# completion.
332 +#
333 +# Takes all the arguments that find_match() would take, to make a
334 +# mutually recursive call.
335 +sub skip_duplicates {
336 + my ($self, $word_to_match, $current_row, $regexp, $char_class_befor…
337 + my $completion;
338 +
339 + if ($current_row <= $lines_before_cursor)
340 + {
341 + $completion = shift @{$self->{matches_in_row}}; # get t…
342 + }
343 + else
344 + {
345 + $completion = pop @{$self->{matches_in_row}}; # get the…
346 + }
347 +
348 + # check for duplicates
349 + if (exists $self->{already_completed}{$completion}) {
350 + # skip this completion
351 + return find_match(@_);
352 + } else {
353 + $self->{already_completed}{$completion} = 1;
354 +
355 + highlight_match($self,
356 + $self->{next_row}+1,
357 + $completion);
358 +
359 + return $completion;
360 + }
361 +}
362 +
363 +######################################################################
364 +
365 +# Finds the next matching completion in the row current row or above
366 +# while skipping duplicates using skip_duplicates().
367 +sub find_match {
368 + my ($self, $word_to_match, $current_row, $regexp, $char_class_befor…
369 + $self->{matches_in_row} //= [];
370 +
371 + # cycle through all the matches in the current row if not starting …
372 + if (@{$self->{matches_in_row}}) {
373 + return skip_duplicates($self, $word_to_match, $current_row, $re…
374 + }
375 +
376 +
377 + my $i;
378 + # search through all the rows starting with current one or one abov…
379 + for ($i = $current_row; $i >= 0; --$i) {
380 + my $line = @{$lines}[$i]; # get the line of text from the row
381 +
382 + # if ($i == $cursor_row) {
383 + # $line = substr $line, 0, $cursor_column;
384 + # }
385 +
386 + $_ = $line;
387 +
388 + # find all the matches in the current line
389 + my $match;
390 + push @{$self->{matches_in_row}}, $+{match} while ($_, $match) =…
391 + …
392 + …
393 + …
394 + …
395 + …
396 + /i…
397 + # corner case: match at the very beginning of line
398 + push @{$self->{matches_in_row}}, $+{match} if $line =~ /^(${cha…
399 +
400 + if (@{$self->{matches_in_row}}) {
401 + # remember which row should be searched next
402 + $self->{next_row} = --$i;
403 +
404 + # arguments needed for find_match() mutual recursion
405 + return skip_duplicates($self, $word_to_match, $i, $regexp, …
406 + }
407 + }
408 +
409 + # # no more possible completions, revert to the original word
410 + # undo_completion($self) if $i < 0;
411 +
412 + return undef;
413 +}
414 diff --git a/st.c b/st.c
415 index 57c6e96..9ff8d00 100644
416 --- a/st.c
417 +++ b/st.c
418 @@ -17,6 +17,7 @@
419 #include <unistd.h>
420 #include <wchar.h>
421
422 +#include "autocomplete.h"
423 #include "st.h"
424 #include "win.h"
425
426 @@ -2557,6 +2558,8 @@ tresize(int col, int row)
427 return;
428 }
429
430 + autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
431 +
432 /*
433 * slide screen to keep cursor where we expect it -
434 * tscrollup would work here, but we can optimize to
435 @@ -2676,3 +2679,227 @@ redraw(void)
436 tfulldirt();
437 draw();
438 }
439 +
440 +void autocomplete (const Arg *arg) {
441 + static _Bool active = 0;
442 + int acmpl_cmdindex = arg->i;
443 + static int acmpl_cmdindex_prev;
444 +
445 + if (active == 0)
446 + acmpl_cmdindex_prev = acmpl_cmdindex;
447 +
448 + static const char * const acmpl_cmd[] = {
449 + [ACMPL_DEACTIVATE] = "__DEACTIVATE__",
450 + [ACMPL_WORD] = "word-complete",
451 + [ACMPL_WWORD] = "WORD-complete",
452 + [ACMPL_FUZZY_WORD] = "fuzzy-word-complete",
453 + [ACMPL_FUZZY_WWORD] = "fuzzy-WORD-complete",
454 + [ACMPL_FUZZY] = "fuzzy-complete",
455 + [ACMPL_SUFFIX] = "suffix-complete",
456 + [ACMPL_SURROUND] = "surround-complete",
457 + [ACMPL_UNDO] = "__UNDO__",
458 + };
459 +
460 + static FILE *acmpl_exec = NULL;
461 + static int acmpl_status;
462 + static char *stbuffile;
463 + static char *target = NULL;
464 + static size_t targetlen;
465 + static char *completion = NULL;
466 + static size_t complen_prev = 0;
467 + static int cx, cy;
468 +
469 + if (acmpl_cmdindex == ACMPL_DEACTIVATE) {
470 + if (active) {
471 + active = 0;
472 + pclose(acmpl_exec);
473 + unlink(stbuffile);
474 + free(stbuffile);
475 + stbuffile = NULL;
476 +
477 + if (complen_prev) {
478 + selclear();
479 + complen_prev = 0;
480 + }
481 + }
482 + return;
483 + }
484 +
485 + if (acmpl_cmdindex == ACMPL_UNDO) {
486 + if (active) {
487 + active = 0;
488 + pclose(acmpl_exec);
489 + unlink(stbuffile);
490 + free(stbuffile);
491 + stbuffile = NULL;
492 +
493 + if (complen_prev) {
494 + selclear();
495 + for (size_t i = 0; i < complen_prev; i++)
496 + ttywrite((char[]) {'\b'}, 1, 1);
497 + complen_prev = 0;
498 + ttywrite(target, targetlen, 0);
499 + }
500 + }
501 + return;
502 + }
503 +
504 + if (acmpl_cmdindex != acmpl_cmdindex_prev) {
505 + if (active) {
506 + acmpl_cmdindex_prev = acmpl_cmdindex;
507 + goto acmpl_begin;
508 + }
509 + }
510 +
511 + if (active == 0) {
512 + acmpl_cmdindex_prev = acmpl_cmdindex;
513 + cx = term.c.x;
514 + cy = term.c.y;
515 +
516 + char filename[] = "/tmp/st-autocomplete-XXXXXX";
517 + int fd = mkstemp(filename);
518 +
519 + if (fd == -1) {
520 + perror("mkstemp");
521 + return;
522 + }
523 +
524 + stbuffile = strdup(filename);
525 +
526 + FILE *stbuf = fdopen(fd, "w");
527 + if (!stbuf) {
528 + perror("fdopen");
529 + close(fd);
530 + unlink(stbuffile);
531 + free(stbuffile);
532 + stbuffile = NULL;
533 + return;
534 + }
535 +
536 + char *stbufline = malloc(term.col + 2);
537 + if (!stbufline) {
538 + perror("malloc");
539 + fclose(stbuf);
540 + unlink(stbuffile);
541 + free(stbuffile);
542 + stbuffile = NULL;
543 + return;
544 + }
545 +
546 + int cxp = 0;
547 + for (size_t y = 0; y < term.row; y++) {
548 + if (y == term.c.y) cx += cxp * term.col;
549 +
550 + size_t x = 0;
551 + for (; x < term.col; x++)
552 + utf8encode(term.line[y][x].u, stbufline + x);
553 + if (term.line[y][x - 1].mode & ATTR_WRAP) {
554 + x--;
555 + if (y <= term.c.y) cy--;
556 + cxp++;
557 + } else {
558 + stbufline[x] = '\n';
559 + cxp = 0;
560 + }
561 + stbufline[x + 1] = 0;
562 + fputs(stbufline, stbuf);
563 + }
564 +
565 + free(stbufline);
566 + fclose(stbuf);
567 +
568 +acmpl_begin:
569 + target = malloc(term.col + 1);
570 + completion = malloc(term.col + 1);
571 + if (!target || !completion) {
572 + perror("malloc");
573 + free(target);
574 + free(completion);
575 + unlink(stbuffile);
576 + free(stbuffile);
577 + stbuffile = NULL;
578 + return;
579 + }
580 +
581 + char acmpl[1500];
582 + snprintf(acmpl, sizeof(acmpl),
583 + "cat %s | st-autocomplete %s %d %d",
584 + stbuffile, acmpl_cmd[acmpl_cmdindex], cy, cx);
585 +
586 + acmpl_exec = popen(acmpl, "r");
587 + if (!acmpl_exec) {
588 + perror("popen");
589 + free(target);
590 + free(completion);
591 + unlink(stbuffile);
592 + free(stbuffile);
593 + stbuffile = NULL;
594 + return;
595 + }
596 +
597 + if (fscanf(acmpl_exec, "%s\n", target) != 1) {
598 + perror("fscanf");
599 + pclose(acmpl_exec);
600 + free(target);
601 + free(completion);
602 + unlink(stbuffile);
603 + free(stbuffile);
604 + stbuffile = NULL;
605 + return;
606 + }
607 + targetlen = strlen(target);
608 + }
609 +
610 + unsigned line, beg, end;
611 +
612 + acmpl_status = fscanf(acmpl_exec, "%[^\n]\n%u\n%u\n%u\n", completio…
613 + if (acmpl_status == EOF) {
614 + if (active == 0) {
615 + pclose(acmpl_exec);
616 + free(target);
617 + free(completion);
618 + unlink(stbuffile);
619 + free(stbuffile);
620 + stbuffile = NULL;
621 + return;
622 + }
623 + active = 0;
624 + pclose(acmpl_exec);
625 + ttywrite(target, targetlen, 0);
626 + goto acmpl_begin;
627 + }
628 +
629 + active = 1;
630 +
631 + if (complen_prev == 0) {
632 + for (size_t i = 0; i < targetlen; i++)
633 + ttywrite((char[]) {'\b'}, 1, 1);
634 + } else {
635 + selclear();
636 + for (size_t i = 0; i < complen_prev; i++)
637 + ttywrite((char[]) {'\b'}, 1, 1);
638 + complen_prev = 0;
639 + }
640 +
641 + complen_prev = strlen(completion);
642 + ttywrite(completion, complen_prev, 0);
643 +
644 + if (line == cy && beg > cx) {
645 + beg += complen_prev - targetlen;
646 + end += complen_prev - targetlen;
647 + }
648 +
649 + end--;
650 +
651 + int wl = 0;
652 + int tl = line;
653 + for (int l = 0; l < tl; l++)
654 + if (term.line[l][term.col - 1].mode & ATTR_WRAP) {
655 + wl++;
656 + tl++;
657 + }
658 +
659 + selstart(beg % term.col, line + wl + beg / term.col, 0);
660 + selextend(end % term.col, line + wl + end / term.col, 1, 0);
661 + xsetsel(getsel());
662 +}
663 diff --git a/st.h b/st.h
664 index fd3b0d8..113ebad 100644
665 --- a/st.h
666 +++ b/st.h
667 @@ -77,6 +77,8 @@ typedef union {
668 const char *s;
669 } Arg;
670
671 +void autocomplete (const Arg *);
672 +
673 void die(const char *, ...);
674 void redraw(void);
675 void draw(void);
676 diff --git a/x.c b/x.c
677 index bd23686..c647721 100644
678 --- a/x.c
679 +++ b/x.c
680 @@ -1859,11 +1859,20 @@ kpress(XEvent *ev)
681 /* 1. shortcuts */
682 for (bp = shortcuts; bp < shortcuts + LEN(shortcuts); bp++) {
683 if (ksym == bp->keysym && match(bp->mod, e->state)) {
684 + if (bp -> func != autocomplete)
685 + autocomplete ((const Arg []) { ACMPL_DE…
686 bp->func(&(bp->arg));
687 return;
688 }
689 }
690
691 + if (!(
692 + len == 0 &&
693 + e -> state & ~ignoremod // ACMPL_ISSUE: …
694 + | ACMPL_MOD == ACMPL_MOD
695 + ))
696 + autocomplete ((const Arg []) { ACMPL_DEACTIVATE });
697 +
698 /* 2. custom keys from config.h */
699 if ((customkey = kmap(ksym, e->state))) {
700 ttywrite(customkey, strlen(customkey), 1);
701 --
702 2.45.2
703
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.