aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/syntax
diff options
context:
space:
mode:
authorSimon Rozman <simon@rozman.si>2020-10-28 17:30:58 +0100
committerJason A. Donenfeld <Jason@zx2c4.com>2020-11-13 14:42:54 +0100
commitea200f82c3e855fcb2b5fd56fc5800374b80514f (patch)
tree3b9e7b8ff1792c4bb4f9b67d6e7edebba6201cea /ui/syntax
parentbuild: remove duplicated ld flags (diff)
downloadwireguard-windows-ea200f82c3e855fcb2b5fd56fc5800374b80514f.tar.xz
wireguard-windows-ea200f82c3e855fcb2b5fd56fc5800374b80514f.zip
syntax: port to go
Arm has no CGo support, so port the syntax editor C code to Go and hope that it's fast enough. This is a pretty literal/unsafe translation from the C. Signed-off-by: Simon Rozman <simon@rozman.si> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'ui/syntax')
-rw-r--r--ui/syntax/highlighter.c641
-rw-r--r--ui/syntax/highlighter.go623
-rw-r--r--ui/syntax/highlighter.h39
-rw-r--r--ui/syntax/syntaxedit.c415
-rw-r--r--ui/syntax/syntaxedit.go422
-rw-r--r--ui/syntax/syntaxedit.h31
6 files changed, 1013 insertions, 1158 deletions
diff --git a/ui/syntax/highlighter.c b/ui/syntax/highlighter.c
deleted file mode 100644
index d89feda1..00000000
--- a/ui/syntax/highlighter.c
+++ /dev/null
@@ -1,641 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include "highlighter.h"
-
-typedef struct {
- const char *s;
- size_t len;
-} string_span_t;
-
-static bool is_decimal(char c)
-{
- return c >= '0' && c <= '9';
-}
-
-static bool is_hexadecimal(char c)
-{
- return is_decimal(c) || ((c | 32) >= 'a' && (c | 32) <= 'f');
-}
-
-static bool is_alphabet(char c)
-{
- return (c | 32) >= 'a' && (c | 32) <= 'z';
-}
-
-static bool is_same(string_span_t s, const char *c)
-{
- size_t len = strlen(c);
-
- if (len != s.len)
- return false;
- return !memcmp(s.s, c, len);
-}
-
-static bool is_caseless_same(string_span_t s, const char *c)
-{
- size_t len = strlen(c);
-
- if (len != s.len)
- return false;
- for (size_t i = 0; i < len; ++i) {
- char a = c[i], b = s.s[i];
- if ((unsigned)a - 'a' < 26)
- a &= 95;
- if ((unsigned)b - 'a' < 26)
- b &= 95;
- if (a != b)
- return false;
- }
- return true;
-}
-
-static bool is_valid_key(string_span_t s)
-{
- if (s.len != 44 || s.s[43] != '=')
- return false;
-
- for (size_t i = 0; i < 42; ++i) {
- if (!is_decimal(s.s[i]) && !is_alphabet(s.s[i]) &&
- s.s[i] != '/' && s.s[i] != '+')
- return false;
- }
- switch (s.s[42]) {
- case 'A':
- case 'E':
- case 'I':
- case 'M':
- case 'Q':
- case 'U':
- case 'Y':
- case 'c':
- case 'g':
- case 'k':
- case 'o':
- case 's':
- case 'w':
- case '4':
- case '8':
- case '0':
- break;
- default:
- return false;
- }
- return true;
-}
-
-static bool is_valid_hostname(string_span_t s)
-{
- size_t num_digit = 0, num_entity = s.len;
-
- if (s.len > 63 || !s.len)
- return false;
- if (s.s[0] == '-' || s.s[s.len - 1] == '-')
- return false;
- if (s.s[0] == '.' || s.s[s.len - 1] == '.')
- return false;
-
- for (size_t i = 0; i < s.len; ++i) {
- if (is_decimal(s.s[i])) {
- ++num_digit;
- continue;
- }
- if (s.s[i] == '.') {
- --num_entity;
- continue;
- }
-
- if (!is_alphabet(s.s[i]) && s.s[i] != '-')
- return false;
-
- if (i && s.s[i] == '.' && s.s[i - 1] == '.')
- return false;
- }
- return num_digit != num_entity;
-}
-
-static bool is_valid_ipv4(string_span_t s)
-{
- for (size_t j, i = 0, pos = 0; i < 4 && pos < s.len; ++i) {
- uint32_t val = 0;
-
- for (j = 0; j < 3 && pos + j < s.len && is_decimal(s.s[pos + j]); ++j)
- val = 10 * val + s.s[pos + j] - '0';
- if (j == 0 || (j > 1 && s.s[pos] == '0') || val > 255)
- return false;
- if (pos + j == s.len && i == 3)
- return true;
- if (s.s[pos + j] != '.')
- return false;
- pos += j + 1;
- }
- return false;
-}
-
-static bool is_valid_ipv6(string_span_t s)
-{
- size_t pos = 0;
- bool seen_colon = false;
-
- if (s.len < 2)
- return false;
- if (s.s[pos] == ':' && s.s[++pos] != ':')
- return false;
- if (s.s[s.len - 1] == ':' && s.s[s.len - 2] != ':')
- return false;
-
- for (size_t j, i = 0; pos < s.len; ++i) {
- if (s.s[pos] == ':' && !seen_colon) {
- seen_colon = true;
- if (++pos == s.len)
- break;
- if (i == 7)
- return false;
- continue;
- }
- for (j = 0; j < 4 && pos + j < s.len && is_hexadecimal(s.s[pos + j]); ++j);
- if (j == 0)
- return false;
- if (pos + j == s.len && (seen_colon || i == 7))
- break;
- if (i == 7)
- return false;
- if (s.s[pos + j] != ':') {
- if (s.s[pos + j] != '.' || (i < 6 && !seen_colon))
- return false;
- return is_valid_ipv4((string_span_t){ s.s + pos, s.len - pos });
- }
- pos += j + 1;
- }
- return true;
-}
-
-static bool is_valid_uint(string_span_t s, bool support_hex, uint64_t min, uint64_t max)
-{
- uint64_t val = 0;
-
- /* Bound this around 32 bits, so that we don't have to write overflow logic. */
- if (s.len > 10 || !s.len)
- return false;
-
- if (support_hex && s.len > 2 && s.s[0] == '0' && s.s[1] == 'x') {
- for (size_t i = 2; i < s.len; ++i) {
- if ((unsigned)s.s[i] - '0' < 10)
- val = 16 * val + (s.s[i] - '0');
- else if (((unsigned)s.s[i] | 32) - 'a' < 6)
- val = 16 * val + (s.s[i] | 32) - 'a' + 10;
- else
- return false;
- }
- } else {
- for (size_t i = 0; i < s.len; ++i) {
- if (!is_decimal(s.s[i]))
- return false;
- val = 10 * val + s.s[i] - '0';
- }
- }
- return val <= max && val >= min;
-}
-
-static bool is_valid_port(string_span_t s)
-{
- return is_valid_uint(s, false, 0, 65535);
-}
-
-static bool is_valid_mtu(string_span_t s)
-{
- return is_valid_uint(s, false, 576, 65535);
-}
-
-static bool is_valid_persistentkeepalive(string_span_t s)
-{
- if (is_same(s, "off"))
- return true;
- return is_valid_uint(s, false, 0, 65535);
-}
-
-#ifndef MOBILE_WGQUICK_SUBSET
-
-static bool is_valid_fwmark(string_span_t s)
-{
- if (is_same(s, "off"))
- return true;
- return is_valid_uint(s, true, 0, 4294967295);
-}
-
-static bool is_valid_table(string_span_t s)
-{
- if (is_same(s, "auto"))
- return true;
- if (is_same(s, "off"))
- return true;
- /* This pretty much invalidates the other checks, but rt_names.c's
- * fread_id_name does no validation aside from this. */
- if (s.len < 512)
- return true;
- return is_valid_uint(s, false, 0, 4294967295);
-}
-
-static bool is_valid_saveconfig(string_span_t s)
-{
- return is_same(s, "true") || is_same(s, "false");
-}
-
-static bool is_valid_prepostupdown(string_span_t s)
-{
- /* It's probably not worthwhile to try to validate a bash expression.
- * So instead we just demand non-zero length. */
- return s.len;
-}
-#endif
-
-static bool is_valid_scope(string_span_t s)
-{
- if (s.len > 64 || !s.len)
- return false;
- for (size_t i = 0; i < s.len; ++i) {
- if (!is_alphabet(s.s[i]) && !is_decimal(s.s[i]) &&
- s.s[i] != '_' && s.s[i] != '=' && s.s[i] != '+' &&
- s.s[i] != '.' && s.s[i] != '-')
- return false;
- }
- return true;
-}
-
-static bool is_valid_endpoint(string_span_t s)
-{
-
- if (!s.len)
- return false;
-
- if (s.s[0] == '[') {
- bool seen_scope = false;
- string_span_t hostspan = { s.s + 1, 0 };
-
- for (size_t i = 1; i < s.len; ++i) {
- if (s.s[i] == '%') {
- if (seen_scope)
- return false;
- seen_scope = true;
- if (!is_valid_ipv6(hostspan))
- return false;
- hostspan = (string_span_t){ s.s + i + 1, 0 };
- } else if (s.s[i] == ']') {
- if (seen_scope) {
- if (!is_valid_scope(hostspan))
- return false;
- } else if (!is_valid_ipv6(hostspan)) {
- return false;
- }
- if (i == s.len - 1 || s.s[i + 1] != ':')
- return false;
- return is_valid_port((string_span_t){ s.s + i + 2, s.len - i - 2 });
- } else {
- ++hostspan.len;
- }
- }
- return false;
- }
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == ':') {
- string_span_t host = { s.s, i }, port = { s.s + i + 1, s.len - i - 1};
- return is_valid_port(port) && (is_valid_ipv4(host) || is_valid_hostname(host));
- }
- }
- return false;
-}
-
-static bool is_valid_network(string_span_t s)
-{
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == '/') {
- string_span_t ip = { s.s, i }, cidr = { s.s + i + 1, s.len - i - 1};
- uint16_t cidrval = 0;
-
- if (cidr.len > 3 || !cidr.len)
- return false;
-
- for (size_t j = 0; j < cidr.len; ++j) {
- if (!is_decimal(cidr.s[j]))
- return false;
- cidrval = 10 * cidrval + cidr.s[j] - '0';
- }
- if (is_valid_ipv4(ip))
- return cidrval <= 32;
- else if (is_valid_ipv6(ip))
- return cidrval <= 128;
- return false;
- }
- }
- return is_valid_ipv4(s) || is_valid_ipv6(s);
-}
-
-enum field {
- InterfaceSection,
- PrivateKey,
- ListenPort,
- Address,
- DNS,
- MTU,
-#ifndef MOBILE_WGQUICK_SUBSET
- FwMark,
- Table,
- PreUp, PostUp, PreDown, PostDown,
- SaveConfig,
-#endif
-
- PeerSection,
- PublicKey,
- PresharedKey,
- AllowedIPs,
- Endpoint,
- PersistentKeepalive,
-
- Invalid
-};
-
-static enum field section_for_field(enum field t)
-{
- if (t > InterfaceSection && t < PeerSection)
- return InterfaceSection;
- if (t > PeerSection && t < Invalid)
- return PeerSection;
- return Invalid;
-}
-
-static enum field get_field(string_span_t s)
-{
-#define check_enum(t) do { if (is_caseless_same(s, #t)) return t; } while (0)
- check_enum(PrivateKey);
- check_enum(ListenPort);
- check_enum(Address);
- check_enum(DNS);
- check_enum(MTU);
- check_enum(PublicKey);
- check_enum(PresharedKey);
- check_enum(AllowedIPs);
- check_enum(Endpoint);
- check_enum(PersistentKeepalive);
-#ifndef MOBILE_WGQUICK_SUBSET
- check_enum(FwMark);
- check_enum(Table);
- check_enum(PreUp);
- check_enum(PostUp);
- check_enum(PreDown);
- check_enum(PostDown);
- check_enum(SaveConfig);
-#endif
- return Invalid;
-#undef check_enum
-}
-
-static enum field get_sectiontype(string_span_t s)
-{
- if (is_caseless_same(s, "[Peer]"))
- return PeerSection;
- if (is_caseless_same(s, "[Interface]"))
- return InterfaceSection;
- return Invalid;
-}
-
-struct highlight_span_array {
- size_t len, capacity;
- struct highlight_span *spans;
-};
-
-/* A useful OpenBSD-ism. */
-static void *realloc_array(void *optr, size_t nmemb, size_t size)
-{
- if ((nmemb >= (size_t)1 << (sizeof(size_t) * 4) ||
- size >= (size_t)1 << (sizeof(size_t) * 4)) &&
- nmemb > 0 && SIZE_MAX / nmemb < size) {
- errno = ENOMEM;
- return NULL;
- }
- return realloc(optr, size * nmemb);
-}
-
-static bool append_highlight_span(struct highlight_span_array *a, const char *o, string_span_t s, enum highlight_type t)
-{
- if (!s.len)
- return true;
- if (a->len >= a->capacity) {
- struct highlight_span *resized;
-
- a->capacity = a->capacity ? a->capacity * 2 : 64;
- resized = realloc_array(a->spans, a->capacity, sizeof(*resized));
- if (!resized) {
- free(a->spans);
- memset(a, 0, sizeof(*a));
- return false;
- }
- a->spans = resized;
- }
- a->spans[a->len++] = (struct highlight_span){ t, s.s - o, s.len };
- return true;
-}
-
-static void highlight_multivalue_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- switch (section) {
- case DNS:
- if (is_valid_ipv4(s) || is_valid_ipv6(s))
- append_highlight_span(ret, parent.s, s, HighlightIP);
- else if (is_valid_hostname(s))
- append_highlight_span(ret, parent.s, s, HighlightHost);
- else
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- case Address:
- case AllowedIPs: {
- size_t slash;
-
- if (!is_valid_network(s)) {
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- }
- for (slash = 0; slash < s.len; ++slash) {
- if (s.s[slash] == '/')
- break;
- }
- if (slash == s.len) {
- append_highlight_span(ret, parent.s, s, HighlightIP);
- } else {
- append_highlight_span(ret, parent.s, (string_span_t){ s.s, slash }, HighlightIP);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash, 1 }, HighlightDelimiter);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + slash + 1, s.len - slash - 1 }, HighlightCidr);
- }
- break;
- }
- default:
- append_highlight_span(ret, parent.s, s, HighlightError);
- }
-}
-
-static void highlight_multivalue(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- string_span_t current_span = { s.s, 0 };
- size_t len_at_last_space = 0;
-
- for (size_t i = 0; i < s.len; ++i) {
- if (s.s[i] == ',') {
- current_span.len = len_at_last_space;
- highlight_multivalue_value(ret, parent, current_span, section);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + i, 1 }, HighlightDelimiter);
- len_at_last_space = 0;
- current_span = (string_span_t){ s.s + i + 1, 0 };
- } else if (s.s[i] == ' ' || s.s[i] == '\t') {
- if (&s.s[i] == current_span.s && !current_span.len)
- ++current_span.s;
- else
- ++current_span.len;
- } else {
- len_at_last_space = ++current_span.len;
- }
- }
- current_span.len = len_at_last_space;
- if (current_span.len)
- highlight_multivalue_value(ret, parent, current_span, section);
- else if (ret->spans[ret->len - 1].type == HighlightDelimiter)
- ret->spans[ret->len - 1].type = HighlightError;
-}
-
-static void highlight_value(struct highlight_span_array *ret, const string_span_t parent, const string_span_t s, enum field section)
-{
- switch (section) {
- case PrivateKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPrivateKey : HighlightError);
- break;
- case PublicKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPublicKey : HighlightError);
- break;
- case PresharedKey:
- append_highlight_span(ret, parent.s, s, is_valid_key(s) ? HighlightPresharedKey : HighlightError);
- break;
- case MTU:
- append_highlight_span(ret, parent.s, s, is_valid_mtu(s) ? HighlightMTU : HighlightError);
- break;
-#ifndef MOBILE_WGQUICK_SUBSET
- case SaveConfig:
- append_highlight_span(ret, parent.s, s, is_valid_saveconfig(s) ? HighlightSaveConfig : HighlightError);
- break;
- case FwMark:
- append_highlight_span(ret, parent.s, s, is_valid_fwmark(s) ? HighlightFwMark : HighlightError);
- break;
- case Table:
- append_highlight_span(ret, parent.s, s, is_valid_table(s) ? HighlightTable : HighlightError);
- break;
- case PreUp:
- case PostUp:
- case PreDown:
- case PostDown:
- append_highlight_span(ret, parent.s, s, is_valid_prepostupdown(s) ? HighlightCmd : HighlightError);
- break;
-#endif
- case ListenPort:
- append_highlight_span(ret, parent.s, s, is_valid_port(s) ? HighlightPort : HighlightError);
- break;
- case PersistentKeepalive:
- append_highlight_span(ret, parent.s, s, is_valid_persistentkeepalive(s) ? HighlightKeepalive : HighlightError);
- break;
- case Endpoint: {
- size_t colon;
-
- if (!is_valid_endpoint(s)) {
- append_highlight_span(ret, parent.s, s, HighlightError);
- break;
- }
- for (colon = s.len; colon --> 0;) {
- if (s.s[colon] == ':')
- break;
- }
- append_highlight_span(ret, parent.s, (string_span_t){ s.s, colon }, HighlightHost);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon, 1 }, HighlightDelimiter);
- append_highlight_span(ret, parent.s, (string_span_t){ s.s + colon + 1, s.len - colon - 1 }, HighlightPort);
- break;
- }
- case Address:
- case DNS:
- case AllowedIPs:
- highlight_multivalue(ret, parent, s, section);
- break;
- default:
- append_highlight_span(ret, parent.s, s, HighlightError);
- }
-}
-
-struct highlight_span *highlight_config(const char *config)
-{
- struct highlight_span_array ret = { 0 };
- const string_span_t s = { config, strlen(config) };
- string_span_t current_span = { s.s, 0 };
- enum field current_section = Invalid, current_field = Invalid;
- enum { OnNone, OnKey, OnValue, OnComment, OnSection } state = OnNone;
- size_t len_at_last_space = 0, equals_location = 0;
-
- for (size_t i = 0; i <= s.len; ++i) {
- if (i == s.len || s.s[i] == '\n' || (state != OnComment && s.s[i] == '#')) {
- if (state == OnKey) {
- current_span.len = len_at_last_space;
- append_highlight_span(&ret, s.s, current_span, HighlightError);
- } else if (state == OnValue) {
- if (current_span.len) {
- append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightDelimiter);
- current_span.len = len_at_last_space;
- highlight_value(&ret, s, current_span, current_field);
- } else {
- append_highlight_span(&ret, s.s, (string_span_t){ s.s + equals_location, 1 }, HighlightError);
- }
- } else if (state == OnSection) {
- current_span.len = len_at_last_space;
- current_section = get_sectiontype(current_span);
- append_highlight_span(&ret, s.s, current_span, current_section == Invalid ? HighlightError : HighlightSection);
- } else if (state == OnComment) {
- append_highlight_span(&ret, s.s, current_span, HighlightComment);
- }
- if (i == s.len)
- break;
- len_at_last_space = 0;
- current_field = Invalid;
- if (s.s[i] == '#') {
- current_span = (string_span_t){ s.s + i, 1 };
- state = OnComment;
- } else {
- current_span = (string_span_t){ s.s + i + 1, 0 };
- state = OnNone;
- }
- } else if (state == OnComment) {
- ++current_span.len;
- } else if (s.s[i] == ' ' || s.s[i] == '\t') {
- if (&s.s[i] == current_span.s && !current_span.len)
- ++current_span.s;
- else
- ++current_span.len;
- } else if (s.s[i] == '=' && state == OnKey) {
- current_span.len = len_at_last_space;
- current_field = get_field(current_span);
- enum field section = section_for_field(current_field);
- if (section == Invalid || current_field == Invalid || section != current_section)
- append_highlight_span(&ret, s.s, current_span, HighlightError);
- else
- append_highlight_span(&ret, s.s, current_span, HighlightField);
- equals_location = i;
- current_span = (string_span_t){ s.s + i + 1, 0 };
- state = OnValue;
- } else {
- if (state == OnNone)
- state = s.s[i] == '[' ? OnSection : OnKey;
- len_at_last_space = ++current_span.len;
- }
- }
-
- append_highlight_span(&ret, s.s, (string_span_t){ s.s, -1 }, HighlightEnd);
- return ret.spans;
-}
diff --git a/ui/syntax/highlighter.go b/ui/syntax/highlighter.go
new file mode 100644
index 00000000..fa6a6530
--- /dev/null
+++ b/ui/syntax/highlighter.go
@@ -0,0 +1,623 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2020 WireGuard LLC. All Rights Reserved.
+ *
+ * This is a direct translation of the original C, and for that reason, it's pretty unusual Go code:
+ * https://git.zx2c4.com/wireguard-tools/tree/contrib/highlighter/highlighter.c
+ */
+
+package syntax
+
+import "unsafe"
+
+type highlight int
+
+const (
+ highlightSection highlight = iota
+ highlightField
+ highlightPrivateKey
+ highlightPublicKey
+ highlightPresharedKey
+ highlightIP
+ highlightCidr
+ highlightHost
+ highlightPort
+ highlightMTU
+ highlightKeepalive
+ highlightComment
+ highlightDelimiter
+ highlightCmd
+ highlightError
+)
+
+func validateHighlight(isValid bool, t highlight) highlight {
+ if isValid {
+ return t
+ }
+ return highlightError
+}
+
+type highlightSpan struct {
+ t highlight
+ s int
+ len int
+}
+
+func isDecimal(c byte) bool {
+ return c >= '0' && c <= '9'
+}
+
+func isHexadecimal(c byte) bool {
+ return isDecimal(c) || (c|32) >= 'a' && (c|32) <= 'f'
+}
+
+func isAlphabet(c byte) bool {
+ return (c|32) >= 'a' && (c|32) <= 'z'
+}
+
+type stringSpan struct {
+ s *byte
+ len int
+}
+
+func (s stringSpan) at(i int) *byte {
+ return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(s.s)) + uintptr(i)))
+}
+
+func (s stringSpan) isSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) != cb[i] {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isCaselessSame(c string) bool {
+ if s.len != len(c) {
+ return false
+ }
+ cb := ([]byte)(c)
+ for i := 0; i < s.len; i++ {
+ a := *s.at(i)
+ b := cb[i]
+ if a-'a' < 26 {
+ a &= 95
+ }
+ if b-'a' < 26 {
+ b &= 95
+ }
+ if a != b {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidKey() bool {
+ if s.len != 44 || *s.at(43) != '=' {
+ return false
+ }
+ for i := 0; i < 42; i++ {
+ if !isDecimal(*s.at(i)) && !isAlphabet(*s.at(i)) && *s.at(i) != '/' && *s.at(i) != '+' {
+ return false
+ }
+ }
+ switch *s.at(42) {
+ case 'A', 'E', 'I', 'M', 'Q', 'U', 'Y', 'c', 'g', 'k', 'o', 's', 'w', '4', '8', '0':
+ return true
+ }
+ return false
+}
+
+func (s stringSpan) isValidHostname() bool {
+ numDigit := 0
+ numEntity := s.len
+ if s.len > 63 || s.len == 0 {
+ return false
+ }
+ if *s.s == '-' || *s.at(s.len - 1) == '-' {
+ return false
+ }
+ if *s.s == '.' || *s.at(s.len - 1) == '.' {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isDecimal(*s.at(i)) {
+ numDigit++
+ continue
+ }
+ if *s.at(i) == '.' {
+ numEntity--
+ continue
+ }
+ if !isAlphabet(*s.at(i)) && *s.at(i) != '-' {
+ return false
+ }
+ if i != 0 && *s.at(i) == '.' && *s.at(i - 1) == '.' {
+ return false
+ }
+ }
+ return numDigit != numEntity
+}
+
+func (s stringSpan) isValidIPv4() bool {
+ pos := 0
+ for i := 0; i < 4 && pos < s.len; i++ {
+ val := 0
+ j := 0
+ for ; j < 3 && pos+j < s.len && isDecimal(*s.at(pos + j)); j++ {
+ val = 10*val + int(*s.at(pos + j)-'0')
+ }
+ if j == 0 || j > 1 && *s.at(pos) == '0' || val > 255 {
+ return false
+ }
+ if pos+j == s.len && i == 3 {
+ return true
+ }
+ if *s.at(pos + j) != '.' {
+ return false
+ }
+ pos += j + 1
+ }
+ return false
+}
+
+func (s stringSpan) isValidIPv6() bool {
+ if s.len < 2 {
+ return false
+ }
+ pos := 0
+ if *s.at(0) == ':' {
+ if *s.at(1) != ':' {
+ return false
+ }
+ pos = 1
+ }
+ if *s.at(s.len - 1) == ':' && *s.at(s.len - 2) != ':' {
+ return false
+ }
+ seenColon := false
+ for i := 0; pos < s.len; i++ {
+ if *s.at(pos) == ':' && !seenColon {
+ seenColon = true
+ pos++
+ if pos == s.len {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ continue
+ }
+ j := 0
+ for ; ; j++ {
+ if j < 4 && pos+j < s.len && isHexadecimal(*s.at(pos + j)) {
+ continue
+ }
+ break
+ }
+ if j == 0 {
+ return false
+ }
+ if pos+j == s.len && (seenColon || i == 7) {
+ break
+ }
+ if i == 7 {
+ return false
+ }
+ if *s.at(pos + j) != ':' {
+ if *s.at(pos + j) != '.' || i < 6 && !seenColon {
+ return false
+ }
+ return stringSpan{s.at(pos), s.len - pos}.isValidIPv4()
+ }
+ pos += j + 1
+ }
+ return true
+}
+
+func (s stringSpan) isValidUint(supportHex bool, min uint64, max uint64) bool {
+ // Bound this around 32 bits, so that we don't have to write overflow logic.
+ if s.len > 10 || s.len == 0 {
+ return false
+ }
+ val := uint64(0)
+ if supportHex && s.len > 2 && *s.s == '0' && *s.at(1) == 'x' {
+ for i := 2; i < s.len; i++ {
+ if *s.at(i)-'0' < 10 {
+ val = 16*val + uint64(*s.at(i)-'0')
+ } else if (*s.at(i))|32-'a' < 6 {
+ val = 16*val + uint64((*s.at(i)|32)-'a'+10)
+ } else {
+ return false
+ }
+ }
+ } else {
+ for i := 0; i < s.len; i++ {
+ if !isDecimal(*s.at(i)) {
+ return false
+ }
+ val = 10*val + uint64(*s.at(i)-'0')
+ }
+ }
+ return val <= max && val >= min
+}
+
+func (s stringSpan) isValidPort() bool {
+ return s.isValidUint(false, 0, 65535)
+}
+
+func (s stringSpan) isValidMTU() bool {
+ return s.isValidUint(false, 576, 65535)
+}
+
+func (s stringSpan) isValidPersistentKeepAlive() bool {
+ if s.isSame("off") {
+ return true
+ }
+ return s.isValidUint(false, 0, 65535)
+}
+
+// It's probably not worthwhile to try to validate a bash expression. So instead we just demand non-zero length.
+func (s stringSpan) isValidPrePostUpDown() bool {
+ return s.len != 0
+}
+
+func (s stringSpan) isValidScope() bool {
+ if s.len > 64 || s.len == 0 {
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if isAlphabet(*s.at(i)) && !isDecimal(*s.at(i)) && *s.at(i) != '_' && *s.at(i) != '=' && *s.at(i) != '+' && *s.at(i) != '.' && *s.at(i) != '-' {
+ return false
+ }
+ }
+ return true
+}
+
+func (s stringSpan) isValidEndpoint() bool {
+ if s.len == 0 {
+ return false
+ }
+ if *s.s == '[' {
+ seenScope := false
+ hostspan := stringSpan{s.at(1), 0}
+ for i := 1; i < s.len; i++ {
+ if *s.at(i) == '%' {
+ if seenScope {
+ return false
+ }
+ seenScope = true
+ if !hostspan.isValidIPv6() {
+ return false
+ }
+ hostspan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ']' {
+ if seenScope {
+ if !hostspan.isValidScope() {
+ return false
+ }
+ } else if !hostspan.isValidIPv6() {
+ return false
+ }
+ if i == s.len-1 || *s.at((i + 1)) != ':' {
+ return false
+ }
+ return stringSpan{s.at(i + 2), s.len - i - 2}.isValidPort()
+ } else {
+ hostspan.len++
+ }
+ }
+ return false
+ }
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ':' {
+ host := stringSpan{s.s, i}
+ port := stringSpan{s.at(i + 1), s.len - i - 1}
+ return port.isValidPort() && (host.isValidIPv4() || host.isValidHostname())
+ }
+ }
+ return false
+}
+
+func (s stringSpan) isValidNetwork() bool {
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == '/' {
+ ip := stringSpan{s.s, i}
+ cidr := stringSpan{s.at(i + 1), s.len - i - 1}
+ cidrval := uint16(0)
+ if cidr.len > 3 || cidr.len == 0 {
+ return false
+ }
+ for j := 0; j < cidr.len; j++ {
+ if !isDecimal(*cidr.at(j)) {
+ return false
+ }
+ cidrval = 10*cidrval + uint16(*cidr.at(j)-'0')
+ }
+ if ip.isValidIPv4() {
+ return cidrval <= 32
+ } else if ip.isValidIPv6() {
+ return cidrval <= 128
+ }
+ return false
+ }
+ }
+ return s.isValidIPv4() || s.isValidIPv6()
+}
+
+type field int32
+
+const (
+ fieldInterfaceSection field = iota
+ fieldPrivateKey
+ fieldListenPort
+ fieldAddress
+ fieldDNS
+ fieldMTU
+ fieldPreUp
+ fieldPostUp
+ fieldPreDown
+ fieldPostDown
+ fieldPeerSection
+ fieldPublicKey
+ fieldPresharedKey
+ fieldAllowedIPs
+ fieldEndpoint
+ fieldPersistentKeepalive
+ fieldInvalid
+)
+
+func sectionForField(t field) field {
+ if t > fieldInterfaceSection && t < fieldPeerSection {
+ return fieldInterfaceSection
+ }
+ if t > fieldPeerSection && t < fieldInvalid {
+ return fieldPeerSection
+ }
+ return fieldInvalid
+}
+
+func (s stringSpan) field() field {
+ switch {
+ case s.isCaselessSame("PrivateKey"):
+ return fieldPrivateKey
+ case s.isCaselessSame("ListenPort"):
+ return fieldListenPort
+ case s.isCaselessSame("Address"):
+ return fieldAddress
+ case s.isCaselessSame("DNS"):
+ return fieldDNS
+ case s.isCaselessSame("MTU"):
+ return fieldMTU
+ case s.isCaselessSame("PublicKey"):
+ return fieldPublicKey
+ case s.isCaselessSame("PresharedKey"):
+ return fieldPresharedKey
+ case s.isCaselessSame("AllowedIPs"):
+ return fieldAllowedIPs
+ case s.isCaselessSame("Endpoint"):
+ return fieldEndpoint
+ case s.isCaselessSame("PersistentKeepalive"):
+ return fieldPersistentKeepalive
+ }
+ /* TODO: uncomment this once we support these in the client
+ case s.isCaselessSame("PreUp"):
+ return fieldPreUp
+ case s.isCaselessSame("PostUp"):
+ return fieldPostUp
+ case s.isCaselessSame("PreDown"):
+ return fieldPreDown
+ case s.isCaselessSame("PostDown"):
+ return fieldPostDown
+ */
+ return fieldInvalid
+}
+
+func (s stringSpan) sectionType() field {
+ switch {
+ case s.isCaselessSame("[Peer]"):
+ return fieldPeerSection
+ case s.isCaselessSame("[Interface]"):
+ return fieldInterfaceSection
+ }
+ return fieldInvalid
+}
+
+type highlightSpanArray []highlightSpan
+
+func (hsa *highlightSpanArray) append(o *byte, s stringSpan, t highlight) {
+ if s.len == 0 {
+ return
+ }
+ *hsa = append(*hsa, highlightSpan{t, int((uintptr(unsafe.Pointer(s.s))) - (uintptr(unsafe.Pointer(o)))), s.len})
+}
+
+func (hsa *highlightSpanArray) highlightMultivalueValue(parent stringSpan, s stringSpan, section field) {
+ switch section {
+ case fieldDNS:
+ if s.isValidIPv4() || s.isValidIPv6() {
+ hsa.append(parent.s, s, highlightIP)
+ } else if s.isValidHostname() {
+ hsa.append(parent.s, s, highlightHost)
+ } else {
+ hsa.append(parent.s, s, highlightError)
+ }
+ case fieldAddress, fieldAllowedIPs:
+ if !s.isValidNetwork() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ slash := 0
+ for ; slash < s.len; slash++ {
+ if *s.at(slash) == '/' {
+ break
+ }
+ }
+ if slash == s.len {
+ hsa.append(parent.s, s, highlightIP)
+ } else {
+ hsa.append(parent.s, stringSpan{s.s, slash}, highlightIP)
+ hsa.append(parent.s, stringSpan{s.at(slash), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(slash + 1), s.len - slash - 1}, highlightCidr)
+ }
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func (hsa *highlightSpanArray) highlightMultivalue(parent stringSpan, s stringSpan, section field) {
+ currentSpan := stringSpan{s.s, 0}
+ lenAtLastSpace := 0
+ for i := 0; i < s.len; i++ {
+ if *s.at(i) == ',' {
+ currentSpan.len = lenAtLastSpace
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ hsa.append(parent.s, stringSpan{s.at(i), 1}, highlightDelimiter)
+ lenAtLastSpace = 0
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else {
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ currentSpan.len = lenAtLastSpace
+ if currentSpan.len != 0 {
+ hsa.highlightMultivalueValue(parent, currentSpan, section)
+ } else if (*hsa)[len(*hsa)-1].t == highlightDelimiter {
+ (*hsa)[len(*hsa)-1].t = highlightError
+ }
+}
+
+func (hsa *highlightSpanArray) highlightValue(parent stringSpan, s stringSpan, section field) {
+ switch section {
+ case fieldPrivateKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPrivateKey))
+ case fieldPublicKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPublicKey))
+ case fieldPresharedKey:
+ hsa.append(parent.s, s, validateHighlight(s.isValidKey(), highlightPresharedKey))
+ case fieldMTU:
+ hsa.append(parent.s, s, validateHighlight(s.isValidMTU(), highlightMTU))
+ case fieldPreUp, fieldPostUp, fieldPreDown, fieldPostDown:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPrePostUpDown(), highlightCmd))
+ case fieldListenPort:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPort(), highlightPort))
+ case fieldPersistentKeepalive:
+ hsa.append(parent.s, s, validateHighlight(s.isValidPersistentKeepAlive(), highlightKeepalive))
+ case fieldEndpoint:
+ if !s.isValidEndpoint() {
+ hsa.append(parent.s, s, highlightError)
+ break
+ }
+ colon := s.len
+ for colon > 0 {
+ colon--
+ if *s.at(colon) == ':' {
+ break
+ }
+ }
+ hsa.append(parent.s, stringSpan{s.s, colon}, highlightHost)
+ hsa.append(parent.s, stringSpan{s.at(colon), 1}, highlightDelimiter)
+ hsa.append(parent.s, stringSpan{s.at(colon + 1), s.len - colon - 1}, highlightPort)
+ case fieldAddress, fieldDNS, fieldAllowedIPs:
+ hsa.highlightMultivalue(parent, s, section)
+ default:
+ hsa.append(parent.s, s, highlightError)
+ }
+}
+
+func highlightConfig(config string) []highlightSpan {
+ var ret highlightSpanArray
+ b := append([]byte(config), 0)
+ s := stringSpan{&b[0], len(b) - 1}
+ currentSpan := stringSpan{s.s, 0}
+ currentSection := fieldInvalid
+ currentField := fieldInvalid
+ const (
+ onNone = iota
+ onKey
+ onValue
+ onComment
+ onSection
+ )
+ state := onNone
+ lenAtLastSpace := 0
+ equalsLocation := 0
+ for i := 0; i <= s.len; i++ {
+ if i == s.len || *s.at(i) == '\n' || state != onComment && *s.at(i) == '#' {
+ if state == onKey {
+ currentSpan.len = lenAtLastSpace
+ ret.append(s.s, currentSpan, highlightError)
+ } else if state == onValue {
+ if currentSpan.len != 0 {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightDelimiter)
+ currentSpan.len = lenAtLastSpace
+ ret.highlightValue(s, currentSpan, currentField)
+ } else {
+ ret.append(s.s, stringSpan{s.at(equalsLocation), 1}, highlightError)
+ }
+ } else if state == onSection {
+ currentSpan.len = lenAtLastSpace
+ currentSection = currentSpan.sectionType()
+ ret.append(s.s, currentSpan, validateHighlight(currentSection != fieldInvalid, highlightSection))
+ } else if state == onComment {
+ ret.append(s.s, currentSpan, highlightComment)
+ }
+ if i == s.len {
+ break
+ }
+ lenAtLastSpace = 0
+ currentField = fieldInvalid
+ if *s.at(i) == '#' {
+ currentSpan = stringSpan{s.at(i), 1}
+ state = onComment
+ } else {
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onNone
+ }
+ } else if state == onComment {
+ currentSpan.len++
+ } else if *s.at(i) == ' ' || *s.at(i) == '\t' {
+ if s.at(i) == currentSpan.s && currentSpan.len == 0 {
+ currentSpan.s = currentSpan.at(1)
+ } else {
+ currentSpan.len++
+ }
+ } else if *s.at(i) == '=' && state == onKey {
+ currentSpan.len = lenAtLastSpace
+ currentField = currentSpan.field()
+ section := sectionForField(currentField)
+ if section == fieldInvalid || currentField == fieldInvalid || section != currentSection {
+ ret.append(s.s, currentSpan, highlightError)
+ } else {
+ ret.append(s.s, currentSpan, highlightField)
+ }
+ equalsLocation = i
+ currentSpan = stringSpan{s.at(i + 1), 0}
+ state = onValue
+ } else {
+ if state == onNone {
+ if *s.at(i) == '[' {
+ state = onSection
+ } else {
+ state = onKey
+ }
+ }
+ currentSpan.len++
+ lenAtLastSpace = currentSpan.len
+ }
+ }
+ return ([]highlightSpan)(ret)
+}
diff --git a/ui/syntax/highlighter.h b/ui/syntax/highlighter.h
deleted file mode 100644
index 0a86de78..00000000
--- a/ui/syntax/highlighter.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
- */
-
-#include <sys/types.h>
-
-#define MOBILE_WGQUICK_SUBSET
-
-enum highlight_type {
- HighlightSection,
- HighlightField,
- HighlightPrivateKey,
- HighlightPublicKey,
- HighlightPresharedKey,
- HighlightIP,
- HighlightCidr,
- HighlightHost,
- HighlightPort,
- HighlightMTU,
- HighlightKeepalive,
- HighlightComment,
- HighlightDelimiter,
-#ifndef MOBILE_WGQUICK_SUBSET
- HighlightTable,
- HighlightFwMark,
- HighlightSaveConfig,
- HighlightCmd,
-#endif
- HighlightError,
- HighlightEnd
-};
-
-struct highlight_span {
- enum highlight_type type;
- size_t start, len;
-};
-
-struct highlight_span *highlight_config(const char *config);
diff --git a/ui/syntax/syntaxedit.c b/ui/syntax/syntaxedit.c
deleted file mode 100644
index c18d496b..00000000
--- a/ui/syntax/syntaxedit.c
+++ /dev/null
@@ -1,415 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <string.h>
-#include <assert.h>
-#include <windows.h>
-#include <windowsx.h>
-#include <richedit.h>
-#include <richole.h>
-#include <tom.h>
-
-#include "syntaxedit.h"
-#include "highlighter.h"
-
-const GUID CDECL IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80, 0x98, 0x00, 0xAA, 0x00, 0x47, 0xBE, 0x5D } };
-
-struct syntaxedit_data {
- IRichEditOle *irich;
- ITextDocument *idoc;
- enum block_state last_block_state;
- LONG yheight;
- bool highlight_guard;
-};
-
-static WNDPROC parent_proc;
-
-struct span_style {
- COLORREF color;
- DWORD effects;
-};
-
-static const struct span_style stylemap[] = {
- [HighlightSection] = { .color = RGB(0x32, 0x6D, 0x74), .effects = CFE_BOLD },
- [HighlightField] = { .color = RGB(0x9B, 0x23, 0x93), .effects = CFE_BOLD },
- [HighlightPrivateKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightPublicKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightPresharedKey] = { .color = RGB(0x64, 0x38, 0x20) },
- [HighlightIP] = { .color = RGB(0x0E, 0x0E, 0xFF) },
- [HighlightCidr] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightHost] = { .color = RGB(0x0E, 0x0E, 0xFF) },
- [HighlightPort] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightMTU] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightKeepalive] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightComment] = { .color = RGB(0x53, 0x65, 0x79), .effects = CFE_ITALIC },
- [HighlightDelimiter] = { .color = RGB(0x00, 0x00, 0x00) },
-#ifndef MOBILE_WGQUICK_SUBSET
- [HighlightTable] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightFwMark] = { .color = RGB(0x1C, 0x00, 0xCF) },
- [HighlightSaveConfig] = { .color = RGB(0x81, 0x5F, 0x03) },
- [HighlightCmd] = { .color = RGB(0x63, 0x75, 0x89) },
-#endif
- [HighlightError] = { .color = RGB(0xC4, 0x1A, 0x16), .effects = CFE_UNDERLINE }
-};
-
-static void evaluate_untunneled_blocking(struct syntaxedit_data *this, HWND hWnd, const char *msg, struct highlight_span *spans)
-{
- enum block_state state = InevaluableBlockingUntunneledTraffic;
- bool on_allowedips = false;
- bool seen_peer = false;
- bool seen_v6_00 = false, seen_v4_00 = false;
- bool seen_v6_01 = false, seen_v6_80001 = false, seen_v4_01 = false, seen_v4_1281 = false;
-
- for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) {
- switch (span->type) {
- case HighlightError:
- goto done;
- case HighlightSection:
- if (span->len != 6 || strncasecmp(&msg[span->start], "[peer]", 6))
- break;
- if (!seen_peer)
- seen_peer = true;
- else
- goto done;
- break;
- case HighlightField:
- on_allowedips = span->len == 10 && !strncasecmp(&msg[span->start], "allowedips", 10);
- break;
- case HighlightIP:
- if (!on_allowedips || !seen_peer)
- break;
- if ((span + 1)->type != HighlightDelimiter || (span + 2)->type != HighlightCidr)
- break;
- if ((span + 2)->len != 1)
- break;
- if (msg[(span + 2)->start] == '0') {
- if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7))
- seen_v4_00 = true;
- else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2))
- seen_v6_00 = true;
- } else if (msg[(span + 2)->start] == '1') {
- if (span->len == 7 && !strncmp(&msg[span->start], "0.0.0.0", 7))
- seen_v4_01 = true;
- else if (span->len == 9 && !strncmp(&msg[span->start], "128.0.0.0", 9))
- seen_v4_1281 = true;
- else if (span->len == 2 && !strncmp(&msg[span->start], "::", 2))
- seen_v6_01 = true;
- else if (span->len == 6 && !strncmp(&msg[span->start], "8000::", 6))
- seen_v6_80001 = true;
- }
- break;
- }
- }
-
- if (seen_v4_00 || seen_v6_00)
- state = BlockingUntunneledTraffic;
- else if ((seen_v4_01 && seen_v4_1281) || (seen_v6_01 && seen_v6_80001))
- state = NotBlockingUntunneledTraffic;
-
-done:
- if (state != this->last_block_state) {
- SendMessage(hWnd, SE_TRAFFIC_BLOCK, 0, state);
- this->last_block_state = state;
- }
-}
-
-static void highlight_text(HWND hWnd)
-{
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- GETTEXTLENGTHEX gettextlengthex = {
- .flags = GTL_NUMBYTES,
- .codepage = CP_ACP /* Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes. */
- };
- GETTEXTEX gettextex = {
- .flags = GT_NOHIDDENTEXT,
- .codepage = gettextlengthex.codepage
- };
- CHARFORMAT2 format = {
- .cbSize = sizeof(CHARFORMAT2),
- .dwMask = CFM_COLOR | CFM_CHARSET | CFM_SIZE | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE,
- .dwEffects = CFE_AUTOCOLOR,
- .yHeight = this->yheight ?: 20 * 10,
- .bCharSet = ANSI_CHARSET
- };
- LRESULT msg_size;
- char *msg = NULL;
- struct highlight_span *spans = NULL;
- CHARRANGE orig_selection;
- POINT original_scroll;
- bool found_private_key = false;
- COLORREF bg_color, bg_inversion;
- size_t num_spans;
-
- if (this->highlight_guard)
- return;
- this->highlight_guard = true;
-
- msg_size = SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0);
- if (msg_size == E_INVALIDARG)
- return;
- gettextex.cb = msg_size + 1;
-
- msg = malloc(msg_size + 1);
- if (!msg)
- goto out;
- if (SendMessage(hWnd, EM_GETTEXTEX, (WPARAM)&gettextex, (LPARAM)msg) <= 0)
- goto out;
-
- /* By default we get CR not CRLF, so just convert to LF. */
- for (size_t i = 0; i < msg_size; ++i) {
- if (msg[i] == '\r')
- msg[i] = '\n';
- }
-
- spans = highlight_config(msg);
- if (!spans)
- goto out;
-
- evaluate_untunneled_blocking(this, hWnd, msg, spans);
-
- this->idoc->lpVtbl->Undo(this->idoc, tomSuspend, NULL);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, 0);
- SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
- SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&orig_selection);
- SendMessage(hWnd, EM_GETSCROLLPOS, 0, (LPARAM)&original_scroll);
- SendMessage(hWnd, EM_HIDESELECTION, TRUE, 0);
- SendMessage(hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&format);
- bg_color = GetSysColor(COLOR_WINDOW);
- bg_inversion = (bg_color & RGB(0xFF, 0xFF, 0xFF)) ^ RGB(0xFF, 0xFF, 0xFF);
- SendMessage(hWnd, EM_SETBKGNDCOLOR, 0, bg_color);
- num_spans = _msize(spans) / sizeof(spans[0]);
- for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) {
- if (num_spans <= 2048) {
- CHARRANGE selection = { span->start, span->len + span->start };
- SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&selection);
- format.crTextColor = stylemap[span->type].color ^ bg_inversion;
- format.dwEffects = stylemap[span->type].effects;
- SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
- }
- if (span->type == HighlightPrivateKey && !found_private_key) {
- /* Rather than allocating a new string, we mangle this one, since (for now) we don't use msg again. */
- msg[span->start + span->len] = '\0';
- SendMessage(hWnd, SE_PRIVATE_KEY, 0, (LPARAM)&msg[span->start]);
- found_private_key = true;
- }
- }
- SendMessage(hWnd, EM_SETSCROLLPOS, 0, (LPARAM)&original_scroll);
- SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&orig_selection);
- SendMessage(hWnd, EM_HIDESELECTION, FALSE, 0);
- SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
- RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
- this->idoc->lpVtbl->Undo(this->idoc, tomResume, NULL);
- if (!found_private_key)
- SendMessage(hWnd, SE_PRIVATE_KEY, 0, 0);
-
-out:
- free(spans);
- free(msg);
- this->highlight_guard = false;
-}
-
-static void context_menu(HWND hWnd, INT x, INT y)
-{
- GETTEXTLENGTHEX gettextlengthex = {
- .flags = GTL_DEFAULT,
- .codepage = CP_ACP
- };
- /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
- HMENU popup, menu = LoadMenuW(GetModuleHandleW(L"comctl32.dll"), MAKEINTRESOURCEW(1));
- CHARRANGE selection = { 0 };
- bool has_selection, can_selectall, can_undo, can_paste;
- UINT cmd;
-
- if (!menu)
- return;
-
- SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&selection);
- has_selection = selection.cpMax - selection.cpMin;
- can_selectall = selection.cpMin || (selection.cpMax < SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0));
- can_undo = SendMessage(hWnd, EM_CANUNDO, 0, 0);
- can_paste = SendMessage(hWnd, EM_CANPASTE, CF_TEXT, 0);
-
- popup = GetSubMenu(menu, 0);
- EnableMenuItem(popup, WM_UNDO, MF_BYCOMMAND | (can_undo ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_CUT, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_COPY, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_PASTE, MF_BYCOMMAND | (can_paste ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, WM_CLEAR, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
- EnableMenuItem(popup, EM_SETSEL, MF_BYCOMMAND | (can_selectall ? MF_ENABLED : MF_GRAYED));
-
- /* Delete items that we don't handle. */
- for (int ctl = GetMenuItemCount(popup) - 1; ctl >= 0; --ctl) {
- MENUITEMINFOW menu_item = {
- .cbSize = sizeof(MENUITEMINFOW),
- .fMask = MIIM_FTYPE | MIIM_ID
- };
- if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
- continue;
- if (menu_item.fType & MFT_SEPARATOR)
- continue;
- switch (menu_item.wID) {
- case WM_UNDO:
- case WM_CUT:
- case WM_COPY:
- case WM_PASTE:
- case WM_CLEAR:
- case EM_SETSEL:
- continue;
- }
- DeleteMenu(popup, ctl, MF_BYPOSITION);
- }
- /* Delete trailing and adjacent separators. */
- for (int ctl = GetMenuItemCount(popup) - 1, end = true; ctl >= 0; --ctl) {
- MENUITEMINFOW menu_item = {
- .cbSize = sizeof(MENUITEMINFOW),
- .fMask = MIIM_FTYPE
- };
- if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
- continue;
- if (!(menu_item.fType & MFT_SEPARATOR)) {
- end = false;
- continue;
- }
- if (!end && ctl) {
- if (!GetMenuItemInfoW(popup, ctl - 1, MF_BYPOSITION, &menu_item))
- continue;
- if (!(menu_item.fType & MFT_SEPARATOR))
- continue;
- }
- DeleteMenu(popup, ctl, MF_BYPOSITION);
- }
-
- if (x == -1 && y == -1) {
- RECT rect;
- GetWindowRect(hWnd, &rect);
- x = (rect.left + rect.right) / 2;
- y = (rect.top + rect.bottom) / 2;
- }
-
- if (GetFocus() != hWnd)
- SetFocus(hWnd);
-
- cmd = TrackPopupMenu(popup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, x, y, 0, hWnd, NULL);
- if (cmd)
- SendMessage(hWnd, cmd, 0, cmd == EM_SETSEL ? -1 : 0);
-
- DestroyMenu(menu);
-}
-
-static LRESULT CALLBACK child_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
-{
- switch (Msg) {
- case WM_CREATE: {
- struct syntaxedit_data *this = calloc(1, sizeof(*this));
- SetWindowLong(hWnd, GWL_EXSTYLE, GetWindowLong(hWnd, GWL_EXSTYLE) & ~WS_EX_CLIENTEDGE);
- assert(this);
- SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
- SendMessage(hWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&this->irich);
- assert(this->irich);
- this->irich->lpVtbl->QueryInterface(this->irich, &IID_ITextDocument, (void **)&this->idoc);
- assert(this->idoc);
- SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
- SendMessage(hWnd, EM_SETTEXTMODE, TM_SINGLECODEPAGE, 0);
- break;
- }
- case WM_DESTROY: {
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- this->idoc->lpVtbl->Release(this->idoc);
- this->irich->lpVtbl->Release(this->irich);
- free(this);
- }
- case WM_SETTEXT: {
- LRESULT ret = parent_proc(hWnd, Msg, wParam, lParam);
- highlight_text(hWnd);
- SendMessage(hWnd, EM_EMPTYUNDOBUFFER, 0, 0);
- return ret;
- }
- case SE_SET_PARENT_DPI: {
- struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
- HDC hdc = GetDC(hWnd);
- if (this->yheight)
- SendMessage(hWnd, EM_SETZOOM, GetDeviceCaps(hdc, LOGPIXELSY), wParam);
- this->yheight = MulDiv(20 * 10, wParam, GetDeviceCaps(hdc, LOGPIXELSY));
- ReleaseDC(hWnd, hdc);
- highlight_text(hWnd);
- return 0;
- }
- case WM_REFLECT + WM_COMMAND:
- case WM_COMMAND:
- case WM_REFLECT + WM_NOTIFY:
- case WM_NOTIFY:
- switch (HIWORD(wParam)) {
- case EN_CHANGE:
- highlight_text(hWnd);
- break;
- }
- break;
- case WM_PASTE:
- SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
- return 0;
- case WM_KEYDOWN: {
- WORD key = LOWORD(wParam);
- if ((key == 'V' && GetKeyState(VK_CONTROL) < 0) ||
- (key == VK_INSERT && GetKeyState(VK_SHIFT) < 0)) {
- SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
- return 0;
- }
- break;
- }
- case WM_CONTEXTMENU:
- context_menu(hWnd, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
- return 0;
- case WM_THEMECHANGED:
- highlight_text(hWnd);
- break;
- case WM_GETDLGCODE: {
- MSG *m = (MSG *)lParam;
- LRESULT lres = parent_proc(hWnd, Msg, wParam, lParam);
- lres &= ~DLGC_WANTTAB;
- if (m && m->message == WM_KEYDOWN && m->wParam == VK_TAB && GetKeyState(VK_CONTROL) >= 0)
- lres &= ~DLGC_WANTMESSAGE;
- return lres;
- }
- }
- return parent_proc(hWnd, Msg, wParam, lParam);
-}
-
-static long has_loaded = 0;
-
-bool register_syntax_edit(void)
-{
- WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW) };
- WNDPROC pp;
- HANDLE lib;
-
- if (InterlockedCompareExchange(&has_loaded, 1, 0) != 0)
- return !!parent_proc;
-
- lib = LoadLibraryExW(L"msftedit.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
- if (!lib)
- return false;
-
- if (!GetClassInfoExW(NULL, L"RICHEDIT50W", &class))
- goto err;
- pp = class.lpfnWndProc;
- if (!pp)
- goto err;
- class.cbSize = sizeof(WNDCLASSEXW);
- class.hInstance = GetModuleHandleW(NULL);
- class.lpszClassName = L"WgQuickSyntaxEdit";
- class.lpfnWndProc = child_proc;
- if (!RegisterClassExW(&class))
- goto err;
- parent_proc = pp;
- return true;
-
-err:
- FreeLibrary(lib);
- return false;
-}
diff --git a/ui/syntax/syntaxedit.go b/ui/syntax/syntaxedit.go
index 257bdbd2..3bbcfd9c 100644
--- a/ui/syntax/syntaxedit.go
+++ b/ui/syntax/syntaxedit.go
@@ -1,35 +1,41 @@
/* SPDX-License-Identifier: MIT
*
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ * Copyright (C) 2019-2020 WireGuard LLC. All Rights Reserved.
*/
package syntax
import (
"errors"
+ "fmt"
"strings"
+ "sync/atomic"
"syscall"
"unsafe"
"github.com/lxn/walk"
"github.com/lxn/win"
+ "golang.org/x/sys/windows"
)
-// #cgo LDFLAGS: -lgdi32
-// #include "syntaxedit.h"
-import "C"
-
type SyntaxEdit struct {
walk.WidgetBase
+ irich *win.IRichEditOle
+ idoc *win.ITextDocument
+ lastBlockState BlockState
+ yheight int
+ highlightGuard uint32
textChangedPublisher walk.EventPublisher
privateKeyPublisher walk.StringEventPublisher
blockUntunneledTrafficPublisher walk.IntEventPublisher
}
+type BlockState int
+
const (
- InevaluableBlockingUntunneledTraffic = C.InevaluableBlockingUntunneledTraffic
- BlockingUntunneledTraffic = C.BlockingUntunneledTraffic
- NotBlockingUntunneledTraffic = C.NotBlockingUntunneledTraffic
+ InevaluableBlockingUntunneledTraffic BlockState = iota
+ BlockingUntunneledTraffic
+ NotBlockingUntunneledTraffic
)
func (se *SyntaxEdit) LayoutFlags() walk.LayoutFlags {
@@ -63,7 +69,6 @@ func (se *SyntaxEdit) SetText(text string) (err error) {
if win.TRUE != se.SendMessage(win.WM_SETTEXT, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(text)))) {
err = errors.New("WM_SETTEXT failed")
}
- se.textChangedPublisher.Publish()
return
}
@@ -79,42 +84,388 @@ func (se *SyntaxEdit) BlockUntunneledTrafficStateChanged() *walk.IntEvent {
return se.blockUntunneledTrafficPublisher.Event()
}
-func (se *SyntaxEdit) WndProc(hwnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
+type spanStyle struct {
+ color win.COLORREF
+ effects uint32
+}
+
+var stylemap = map[highlight]spanStyle{
+ highlightSection: spanStyle{color: win.RGB(0x32, 0x6D, 0x74), effects: win.CFE_BOLD},
+ highlightField: spanStyle{color: win.RGB(0x9B, 0x23, 0x93), effects: win.CFE_BOLD},
+ highlightPrivateKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPublicKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)},
+ highlightPresharedKey: spanStyle{color: win.RGB(0x64, 0x38, 0x20)},
+ highlightIP: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightCidr: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightHost: spanStyle{color: win.RGB(0x0E, 0x0E, 0xFF)},
+ highlightPort: spanStyle{color: win.RGB(0x81, 0x5F, 0x03)},
+ highlightMTU: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightKeepalive: spanStyle{color: win.RGB(0x1C, 0x00, 0xCF)},
+ highlightComment: spanStyle{color: win.RGB(0x53, 0x65, 0x79), effects: win.CFE_ITALIC},
+ highlightDelimiter: spanStyle{color: win.RGB(0x00, 0x00, 0x00)},
+ highlightCmd: spanStyle{color: win.RGB(0x63, 0x75, 0x89)},
+ highlightError: spanStyle{color: win.RGB(0xC4, 0x1A, 0x16), effects: win.CFE_UNDERLINE},
+}
+
+func (se *SyntaxEdit) evaluateUntunneledBlocking(cfg string, spans []highlightSpan) {
+ state := InevaluableBlockingUntunneledTraffic
+ var onAllowedIPs,
+ seenPeer,
+ seen00v6,
+ seen00v4,
+ seen01v6,
+ seen80001v6,
+ seen01v4,
+ seen1281v4 bool
+
+ for i := range spans {
+ span := &spans[i]
+ switch span.t {
+ case highlightError:
+ goto done
+ case highlightSection:
+ if !strings.EqualFold(cfg[span.s:span.s+span.len], "[Peer]") {
+ break
+ }
+ if !seenPeer {
+ seenPeer = true
+ } else {
+ goto done
+ }
+ break
+ case highlightField:
+ onAllowedIPs = strings.EqualFold(cfg[span.s:span.s+span.len], "AllowedIPs")
+ break
+ case highlightIP:
+ if !onAllowedIPs || !seenPeer {
+ break
+ }
+ if i+2 >= len(spans) || spans[i+1].t != highlightDelimiter || spans[i+2].t != highlightCidr {
+ break
+ }
+ if spans[i+2].len != 1 {
+ break
+ }
+ switch cfg[spans[i+2].s] {
+ case '0':
+ switch cfg[span.s : span.s+span.len] {
+ case "0.0.0.0":
+ seen00v4 = true
+ case "::":
+ seen00v6 = true
+ }
+ case '1':
+ switch cfg[span.s : span.s+span.len] {
+ case "0.0.0.0":
+ seen01v4 = true
+ case "128.0.0.0":
+ seen1281v4 = true
+ case "::":
+ seen01v6 = true
+ case "8000::":
+ seen80001v6 = true
+ }
+ }
+ break
+ }
+ }
+
+ if seen00v4 || seen00v6 {
+ state = BlockingUntunneledTraffic
+ } else if (seen01v4 && seen1281v4) || (seen01v6 && seen80001v6) {
+ state = NotBlockingUntunneledTraffic
+ }
+
+done:
+ if state != se.lastBlockState {
+ se.blockUntunneledTrafficPublisher.Publish(int(state))
+ se.lastBlockState = state
+ }
+}
+
+func (se *SyntaxEdit) highlightText() error {
+ if !atomic.CompareAndSwapUint32(&se.highlightGuard, 0, 1) {
+ return nil
+ }
+ defer atomic.StoreUint32(&se.highlightGuard, 0)
+
+ hWnd := se.Handle()
+ gettextlengthex := win.GETTEXTLENGTHEX{
+ Flags: win.GTL_NUMBYTES,
+ Codepage: win.CP_ACP, // Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes.
+ }
+ msgSize := uint32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))
+ if msgSize == win.E_INVALIDARG {
+ return errors.New("Failed to get text length")
+ }
+
+ gettextex := win.GETTEXTEX{
+ Flags: win.GT_NOHIDDENTEXT,
+ Codepage: gettextlengthex.Codepage,
+ Cb: msgSize + 1,
+ }
+ msg := make([]byte, msgSize+1)
+ msgCount := win.SendMessage(hWnd, win.EM_GETTEXTEX, uintptr(unsafe.Pointer(&gettextex)), uintptr(unsafe.Pointer(&msg[0])))
+ if msgCount < 0 {
+ return errors.New("Failed to get text")
+ }
+ cfg := strings.Replace(string(msg[:msgCount]), "\r", "\n", -1)
+
+ spans := highlightConfig(cfg)
+ se.evaluateUntunneledBlocking(cfg, spans)
+
+ se.idoc.Undo(win.TomSuspend, nil)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, 0)
+ win.SendMessage(hWnd, win.WM_SETREDRAW, win.FALSE, 0)
+ var origSelection win.CHARRANGE
+ win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
+ var origScroll win.POINT
+ win.SendMessage(hWnd, win.EM_GETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
+ win.SendMessage(hWnd, win.EM_HIDESELECTION, win.TRUE, 0)
+ format := win.CHARFORMAT2{
+ CHARFORMAT: win.CHARFORMAT{
+ CbSize: uint32(unsafe.Sizeof(win.CHARFORMAT2{})),
+ DwMask: win.CFM_COLOR | win.CFM_CHARSET | win.CFM_SIZE | win.CFM_BOLD | win.CFM_ITALIC | win.CFM_UNDERLINE,
+ DwEffects: win.CFE_AUTOCOLOR,
+ BCharSet: win.ANSI_CHARSET,
+ },
+ }
+ if se.yheight != 0 {
+ format.YHeight = 20 * 10
+ }
+ win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_ALL, uintptr(unsafe.Pointer(&format)))
+ bgColor := win.COLORREF(win.GetSysColor(win.COLOR_WINDOW))
+ bgInversion := (bgColor & win.RGB(0xFF, 0xFF, 0xFF)) ^ win.RGB(0xFF, 0xFF, 0xFF)
+ win.SendMessage(hWnd, win.EM_SETBKGNDCOLOR, 0, uintptr(bgColor))
+ numSpans := len(spans)
+ foundPrivateKey := false
+ for i := range spans {
+ span := &spans[i]
+ if numSpans <= 2048 {
+ selection := win.CHARRANGE{int32(span.s), int32(span.s + span.len)}
+ win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&selection)))
+ format.CrTextColor = stylemap[span.t].color ^ bgInversion
+ format.DwEffects = stylemap[span.t].effects
+ win.SendMessage(hWnd, win.EM_SETCHARFORMAT, win.SCF_SELECTION, uintptr(unsafe.Pointer(&format)))
+ }
+ if span.t == highlightPrivateKey && !foundPrivateKey {
+ privateKey := cfg[span.s : span.s+span.len]
+ se.privateKeyPublisher.Publish(privateKey)
+ foundPrivateKey = true
+ }
+ }
+ win.SendMessage(hWnd, win.EM_SETSCROLLPOS, 0, uintptr(unsafe.Pointer(&origScroll)))
+ win.SendMessage(hWnd, win.EM_EXSETSEL, 0, uintptr(unsafe.Pointer(&origSelection)))
+ win.SendMessage(hWnd, win.EM_HIDESELECTION, win.FALSE, 0)
+ win.SendMessage(hWnd, win.WM_SETREDRAW, win.TRUE, 0)
+ win.RedrawWindow(hWnd, nil, 0, win.RDW_ERASE|win.RDW_FRAME|win.RDW_INVALIDATE|win.RDW_ALLCHILDREN)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
+ se.idoc.Undo(win.TomResume, nil)
+ if !foundPrivateKey {
+ se.privateKeyPublisher.Publish("")
+ }
+ return nil
+}
+
+func (se *SyntaxEdit) contextMenu(x, y int32) error {
+ /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
+ comctl32UTF16, err := windows.UTF16PtrFromString("comctl32.dll")
+ if err != nil {
+ return err
+ }
+ comctl32Handle := win.GetModuleHandle(comctl32UTF16)
+ if comctl32Handle == 0 {
+ return errors.New("Failed to get comctl32.dll handle")
+ }
+ menu := win.LoadMenu(comctl32Handle, win.MAKEINTRESOURCE(1))
+ if menu == 0 {
+ return errors.New("Failed to load menu")
+ }
+ defer win.DestroyMenu(menu)
+
+ hWnd := se.Handle()
+ enableWhenSelected := uint32(win.MF_GRAYED)
+ var selection win.CHARRANGE
+ win.SendMessage(hWnd, win.EM_EXGETSEL, 0, uintptr(unsafe.Pointer(&selection)))
+ if selection.CpMin < selection.CpMax {
+ enableWhenSelected = win.MF_ENABLED
+ }
+ enableSelectAll := uint32(win.MF_GRAYED)
+ gettextlengthex := win.GETTEXTLENGTHEX{
+ Flags: win.GTL_DEFAULT,
+ Codepage: win.CP_ACP,
+ }
+ if selection.CpMin != 0 || (selection.CpMax < int32(win.SendMessage(hWnd, win.EM_GETTEXTLENGTHEX, uintptr(unsafe.Pointer(&gettextlengthex)), 0))) {
+ enableSelectAll = win.MF_ENABLED
+ }
+ enableUndo := uint32(win.MF_GRAYED)
+ if win.SendMessage(hWnd, win.EM_CANUNDO, 0, 0) != 0 {
+ enableUndo = win.MF_ENABLED
+ }
+ enablePaste := uint32(win.MF_GRAYED)
+ if win.SendMessage(hWnd, win.EM_CANPASTE, win.CF_TEXT, 0) != 0 {
+ enablePaste = win.MF_ENABLED
+ }
+
+ popup := win.GetSubMenu(menu, 0)
+ win.EnableMenuItem(popup, win.WM_UNDO, win.MF_BYCOMMAND|enableUndo)
+ win.EnableMenuItem(popup, win.WM_CUT, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.WM_COPY, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.WM_PASTE, win.MF_BYCOMMAND|enablePaste)
+ win.EnableMenuItem(popup, win.WM_CLEAR, win.MF_BYCOMMAND|enableWhenSelected)
+ win.EnableMenuItem(popup, win.EM_SETSEL, win.MF_BYCOMMAND|enableSelectAll)
+
+ // Delete items that we don't handle.
+ for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
+ menuItem := win.MENUITEMINFO{
+ CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
+ FMask: win.MIIM_FTYPE | win.MIIM_ID,
+ }
+ if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) != 0 {
+ continue
+ }
+ switch menuItem.WID {
+ case win.WM_UNDO, win.WM_CUT, win.WM_COPY, win.WM_PASTE, win.WM_CLEAR, win.EM_SETSEL:
+ continue
+ }
+ win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
+ }
+ // Delete trailing and adjacent separators.
+ end := true
+ for ctl := win.GetMenuItemCount(popup) - 1; ctl >= 0; ctl-- {
+ menuItem := win.MENUITEMINFO{
+ CbSize: uint32(unsafe.Sizeof(win.MENUITEMINFO{})),
+ FMask: win.MIIM_FTYPE,
+ }
+ if !win.GetMenuItemInfo(popup, uint32(ctl), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
+ end = false
+ continue
+ }
+ if !end && ctl > 0 {
+ if !win.GetMenuItemInfo(popup, uint32(ctl-1), win.MF_BYPOSITION, &menuItem) {
+ continue
+ }
+ if (menuItem.FType & win.MFT_SEPARATOR) == 0 {
+ continue
+ }
+ }
+ win.DeleteMenu(popup, uint32(ctl), win.MF_BYPOSITION)
+ }
+
+ if x == -1 && y == -1 {
+ var rect win.RECT
+ win.GetWindowRect(hWnd, &rect)
+ x = (rect.Left + rect.Right) / 2
+ y = (rect.Top + rect.Bottom) / 2
+ }
+
+ if win.GetFocus() != hWnd {
+ win.SetFocus(hWnd)
+ }
+
+ cmd := win.TrackPopupMenu(popup, win.TPM_LEFTALIGN|win.TPM_RIGHTBUTTON|win.TPM_RETURNCMD|win.TPM_NONOTIFY, x, y, 0, hWnd, nil)
+ if cmd != 0 {
+ lParam := uintptr(0)
+ if cmd == win.EM_SETSEL {
+ lParam = ^uintptr(0)
+ }
+ win.SendMessage(hWnd, cmd, 0, lParam)
+ }
+
+ return nil
+}
+
+func (*SyntaxEdit) NeedsWmSize() bool {
+ return true
+}
+
+func (se *SyntaxEdit) WndProc(hWnd win.HWND, msg uint32, wParam, lParam uintptr) uintptr {
switch msg {
- case win.WM_NOTIFY, win.WM_COMMAND:
+ case win.WM_DESTROY:
+ if se.idoc != nil {
+ se.idoc.Release()
+ }
+ if se.irich != nil {
+ se.irich.Release()
+ }
+
+ case win.WM_SETTEXT:
+ ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
+ se.highlightText()
+ win.SendMessage(hWnd, win.EM_EMPTYUNDOBUFFER, 0, 0)
+ se.textChangedPublisher.Publish()
+ return ret
+
+ case win.WM_COMMAND, win.WM_NOTIFY:
switch win.HIWORD(uint32(wParam)) {
case win.EN_CHANGE:
+ se.highlightText()
se.textChangedPublisher.Publish()
}
- // This is a horrible trick from MFC where we reflect the event back to the child.
- se.SendMessage(msg+C.WM_REFLECT, wParam, lParam)
- case C.SE_PRIVATE_KEY:
- if lParam == 0 {
- se.privateKeyPublisher.Publish("")
- } else {
- se.privateKeyPublisher.Publish(C.GoString((*C.char)(unsafe.Pointer(lParam))))
+
+ case win.WM_PASTE:
+ win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
+ return 0
+
+ case win.WM_KEYDOWN:
+ key := win.LOWORD(uint32(wParam))
+ if key == 'V' && win.GetKeyState(win.VK_CONTROL) < 0 ||
+ key == win.VK_INSERT && win.GetKeyState(win.VK_SHIFT) < 0 {
+ win.SendMessage(hWnd, win.EM_PASTESPECIAL, win.CF_TEXT, 0)
+ return 0
}
- case C.SE_TRAFFIC_BLOCK:
- se.blockUntunneledTrafficPublisher.Publish(int(lParam))
+
+ case win.WM_CONTEXTMENU:
+ se.contextMenu(win.GET_X_LPARAM(lParam), win.GET_Y_LPARAM(lParam))
+ return 0
+
+ case win.WM_THEMECHANGED:
+ se.highlightText()
+
+ case win.WM_GETDLGCODE:
+ m := (*win.MSG)(unsafe.Pointer(lParam))
+ ret := se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
+ ret &^= win.DLGC_WANTTAB
+ if m != nil && m.Message == win.WM_KEYDOWN && m.WParam == win.VK_TAB && win.GetKeyState(win.VK_CONTROL) >= 0 {
+ ret &^= win.DLGC_WANTMESSAGE
+ }
+ return ret
}
- return se.WidgetBase.WndProc(hwnd, msg, wParam, lParam)
+
+ return se.WidgetBase.WndProc(hWnd, msg, wParam, lParam)
}
func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) {
- C.register_syntax_edit()
+ const LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800
+ _, err := windows.LoadLibraryEx("msftedit.dll", 0, LOAD_LIBRARY_SEARCH_SYSTEM32)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to load msftedit.dll: %v", err)
+ }
+
se := &SyntaxEdit{}
- err := walk.InitWidget(
+ if err := walk.InitWidget(
se,
parent,
- "WgQuickSyntaxEdit",
- C.SYNTAXEDIT_STYLE,
- C.SYNTAXEDIT_EXTSTYLE,
- )
- if err != nil {
+ win.MSFTEDIT_CLASS,
+ win.WS_CHILD|win.ES_MULTILINE|win.WS_VISIBLE|win.WS_VSCROLL|win.WS_BORDER|win.WS_HSCROLL|win.WS_TABSTOP|win.ES_WANTRETURN|win.ES_NOOLEDRAGDROP,
+ 0); err != nil {
return nil, err
}
- se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(parent.DPI()), 0)
-
+ hWnd := se.Handle()
+ win.SetWindowLong(hWnd, win.GWL_EXSTYLE, win.GetWindowLong(hWnd, win.GWL_EXSTYLE)&^win.WS_EX_CLIENTEDGE)
+ win.SendMessage(hWnd, win.EM_GETOLEINTERFACE, 0, uintptr(unsafe.Pointer(&se.irich)))
+ var idoc unsafe.Pointer
+ se.irich.QueryInterface(&win.IID_ITextDocument, &idoc)
+ se.idoc = (*win.ITextDocument)(idoc)
+ win.SendMessage(hWnd, win.EM_SETEVENTMASK, 0, win.ENM_CHANGE)
+ win.SendMessage(hWnd, win.EM_SETTEXTMODE, win.TM_SINGLECODEPAGE, 0)
+ se.ApplyDPI(parent.DPI())
se.GraphicsEffects().Add(walk.InteractionEffect)
se.GraphicsEffects().Add(walk.FocusEffect)
se.MustRegisterProperty("Text", walk.NewProperty(
@@ -128,10 +479,17 @@ func NewSyntaxEdit(parent walk.Container) (*SyntaxEdit, error) {
return se.SetText("")
},
se.textChangedPublisher.Event()))
-
return se, nil
}
func (se *SyntaxEdit) ApplyDPI(dpi int) {
- se.SendMessage(C.SE_SET_PARENT_DPI, uintptr(dpi), 0)
+ hWnd := se.Handle()
+ hdc := win.GetDC(hWnd)
+ logPixels := win.GetDeviceCaps(hdc, win.LOGPIXELSY)
+ if se.yheight != 0 {
+ win.SendMessage(hWnd, win.EM_SETZOOM, uintptr(logPixels), uintptr(dpi))
+ }
+ se.yheight = 20 * 10 * dpi / int(logPixels)
+ win.ReleaseDC(hWnd, hdc)
+ se.highlightText()
}
diff --git a/ui/syntax/syntaxedit.h b/ui/syntax/syntaxedit.h
deleted file mode 100644
index 048e7bc4..00000000
--- a/ui/syntax/syntaxedit.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: MIT
- *
- * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
- */
-
-#ifndef SYNTAXEDIT_H
-#define SYNTAXEDIT_H
-
-#include <stdbool.h>
-#include <windows.h>
-#include <richedit.h>
-
-#define SYNTAXEDIT_STYLE (WS_CHILD | ES_MULTILINE | WS_VISIBLE | WS_VSCROLL | WS_BORDER | WS_HSCROLL | WS_TABSTOP | ES_WANTRETURN | ES_NOOLEDRAGDROP)
-#define SYNTAXEDIT_EXTSTYLE (0)
-
-/* The old MFC reflection trick. */
-#define WM_REFLECT (WM_USER + 0x1C00)
-
-#define SE_PRIVATE_KEY (WM_USER + 0x3100)
-#define SE_TRAFFIC_BLOCK (WM_USER + 0x3101)
-#define SE_SET_PARENT_DPI (WM_USER + 0x3102)
-
-enum block_state {
- InevaluableBlockingUntunneledTraffic,
- BlockingUntunneledTraffic,
- NotBlockingUntunneledTraffic
-};
-
-extern bool register_syntax_edit(void);
-
-#endif