jcs
/wallops
/amendments
/131
*: Add support for Pushover notifications when screen saver is running
The API options are input in a new settings window that will
eventually add more general options.
jcs made amendment 131 about 1 year ago
--- chatter.h Fri Sep 20 12:25:26 2024
+++ chatter.h Mon Sep 23 15:42:07 2024
@@ -40,7 +40,8 @@
#define FILE_MENU_ID 129
#define FILE_MENU_CONNECT_ID 1
-#define FILE_MENU_QUIT_ID 3
+#define FILE_MENU_SETTINGS_ID 2
+#define FILE_MENU_QUIT_ID 4
#define EDIT_MENU_ID 130
#define EDIT_MENU_CUT_ID 1
@@ -106,7 +107,7 @@ struct chatter {
extern MenuHandle apple_menu, file_menu, edit_menu, view_menu, ignore_menu,
window_menu;
-void notify(void);
+void notify(struct focusable *focusable, char *msg, char *titlefmt, ...);
void cancel_notification(void);
struct chatter * chatter_init(const char *server,
--- http.c Mon Sep 23 15:45:49 2024
+++ http.c Mon Sep 23 15:45:49 2024
@@ -0,0 +1,643 @@
+/*
+ * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "dnr.h"
+#include "http.h"
+#include "util.h"
+
+char * http_user_agent(void);
+struct http_request * http_init_req(const char *verb, const char *surl,
+ const char *headers, const char *body, size_t body_len);
+ssize_t http_req_ingest(struct http_request *req);
+size_t strappend(char *str, size_t str_size, size_t str_len, char *append);
+
+struct url *
+url_parse(const char *str)
+{
+ struct url *url = NULL;
+ char *buf, *scheme, *host, *path;
+ unsigned short port;
+ short ret, pos;
+ size_t len, schemelen, hostlen, pathlen;
+
+ len = strlen(str);
+ scheme = xmalloc(len + 1);
+ if (scheme == NULL) {
+ warn("http: Failed allocating %ld", len + 1);
+ return NULL;
+ }
+ host = xmalloc(len + 1);
+ if (host == NULL) {
+ warn("http: Failed allocating %ld", len + 1);
+ xfree(&scheme);
+ return NULL;
+ }
+ path = xmalloc(len + 1);
+ if (path == NULL) {
+ warn("http: Failed allocating %ld", len + 1);
+ xfree(&host);
+ xfree(&scheme);
+ return NULL;
+ }
+
+ /* scheme://host:port/path */
+ ret = sscanf(str, "%[^:]://%[^:]:%d%s%n", scheme, host, &port, path,
+ &pos);
+ if (ret == 4) {
+ if (pos > len)
+ panic("url_parse sscanf overflow");
+ goto consolidate;
+ }
+
+ /* scheme://host/path */
+ ret = sscanf(str, "%[^:]://%[^/]%s%n", scheme, host, path, &pos);
+ if (ret == 3) {
+ if (pos > len)
+ panic("url_parse sscanf overflow");
+ if (strcmp(scheme, "http") == 0)
+ port = 80;
+ else if (strcmp(scheme, "https") == 0)
+ port = 443;
+ else
+ goto cleanup;
+ goto consolidate;
+ }
+
+ goto cleanup;
+
+consolidate:
+ schemelen = strlen(scheme);
+ hostlen = strlen(host);
+ pathlen = strlen(path);
+
+ /*
+ * Put everything in a single chunk of memory so the caller can just
+ * free(url)
+ */
+ len = sizeof(struct url) + schemelen + 1 + hostlen + 1 + pathlen + 1;
+ url = xmalloc(len);
+ if (url == NULL) {
+ warn("http: Failed allocating %ld for URL", len);
+ goto cleanup;
+ }
+
+ url->scheme = (char *)url + sizeof(struct url);
+ len = strlcpy(url->scheme, scheme, schemelen + 1);
+
+ url->host = url->scheme + len + 1;
+ len = strlcpy(url->host, host, hostlen + 1);
+
+ url->path = url->host + len + 1;
+ len = strlcpy(url->path, path, pathlen + 1);
+
+ url->port = port;
+
+cleanup:
+ xfree(&scheme);
+ xfree(&host);
+ xfree(&path);
+
+ return url;
+}
+
+ssize_t
+url_encode(unsigned char *str, size_t strlimit, unsigned char *dst,
+ size_t dstsize)
+{
+ size_t len, n;
+ bool encode = false;
+ char a, b;
+ unsigned char c;
+
+encode:
+ for (n = 0, len = 0; str[n] != '\0' && (strlimit ? n < strlimit : 1);
+ n++) {
+ c = str[n];
+
+ if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' ||
+ c == '~' || c == ' ') {
+ if (len >= dstsize)
+ return -1;
+ if (c == ' ')
+ c = '+';
+ dst[len] = c;
+ len++;
+ } else {
+ if (len + 3 >= dstsize)
+ return -1;
+ sprintf((char *)dst + len, "%%%02X", c);
+ len += 3;
+ }
+ }
+
+ dst[len] = '\0';
+ return len;
+}
+
+char *
+http_user_agent(void)
+{
+ static char agent[32] = { 0 };
+
+ if (agent[0] == '\0')
+ snprintf(agent, sizeof(agent), "Amend/%s", get_version(false));
+
+ return agent;
+}
+
+struct http_request *
+http_init_req(const char *verb, const char *surl, const char *headers,
+ const char *body, size_t body_len)
+{
+ struct url *url;
+ struct http_request *req;
+ size_t len, alen, hlen;
+ short err;
+ char ip_s[16], trail[2], clength[30];
+ char *oheaders = NULL, *mheaders = NULL;
+ ip_addr local_ip;
+ tcp_port local_port;
+
+ _TCPInit();
+
+ url = url_parse(surl);
+ if (url == NULL)
+ return NULL;
+
+ req = xmalloczero(sizeof(struct http_request));
+ if (req == NULL) {
+ warn("http: Failed allocating http_request");
+ return NULL;
+ }
+ req->url = url;
+ req->tcp_buf_size = (4 * 1500) + sizeof(req->chunk);
+ req->tcp_buf = xmalloc(req->tcp_buf_size);
+ if (req->tcp_buf == NULL) {
+ warn("http: Failed allocating tcp_buf");
+ xfree(&req);
+ return NULL;
+ }
+
+ err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf,
+ req->tcp_buf_size, nil, nil, nil, false);
+ if (err) {
+ warn("TCPCreate failed: %d", err);
+ goto error;
+ }
+
+ err = DNSResolveName(req->url->host, &req->host_ip, NULL);
+ if (err) {
+ warn("Couldn't resolve host %s (%d)", req->url->host, err);
+ goto error;
+ }
+
+ long2ip(req->host_ip, (char *)&ip_s);
+
+ alen = 256 + strlen(req->url->host);
+ oheaders = xmalloc(alen);
+ if (oheaders == NULL) {
+ warn("http: Failed allocating headers");
+ goto error;
+ }
+
+ if (body_len)
+ snprintf(clength, sizeof(clength),
+ "Content-Length: %lu\r\n", body_len);
+ else
+ clength[0] = '\0';
+
+ hlen = snprintf(oheaders, alen,
+ "Host: %s\r\n"
+ "User-Agent: %s\r\n"
+ "Accept: */*\r\n"
+ "%s",
+ req->url->host, http_user_agent(), clength);
+ if (hlen > alen) {
+ warn("http: Truncated headers");
+ goto error;
+ }
+
+ if (headers != NULL) {
+ hlen = http_merge_headers(oheaders, headers, &mheaders);
+ xfree(&oheaders);
+ oheaders = mheaders;
+ mheaders = NULL;
+ }
+
+ alen = 64 + strlen(req->url->path) + hlen;
+ req->message = xmalloc(alen);
+ if (req->message == NULL) {
+ warn("http: Failed allocating message");
+ goto error;
+ }
+
+ len = snprintf(req->message, alen,
+ "%s %s HTTP/1.0\r\n"
+ "%s"
+ "\r\n%s", verb, req->url->path, oheaders, body_len ? "\r\n" : "");
+ xfree(&oheaders);
+ if (len > alen) {
+ warn("http: Truncated message");
+ goto error;
+ }
+
+ err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, req->host_ip,
+ req->url->port, &local_ip, &local_port, nil, nil, false);
+ if (err) {
+ warn("Failed connecting to %s (%s) port %d: %d",
+ req->url->host, ip_s, req->url->port, err);
+ goto error;
+ }
+
+ memset(&req->tcp_wds, 0, sizeof(req->tcp_wds));
+ req->tcp_wds[0].ptr = req->message;
+ req->tcp_wds[0].length = len;
+
+ if (body_len) {
+ req->tcp_wds[1].ptr = (char *)body;
+ req->tcp_wds[1].length = body_len;
+ }
+
+ err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, nil, nil,
+ false);
+ if (err) {
+ warn("TCPSend to %s (%s) failed: %d", req->url->host, ip_s, err);
+ goto error;
+ }
+
+ return req;
+
+error:
+ http_req_free(&req);
+ return NULL;
+}
+
+struct http_request *
+http_get(const char *surl, const char *headers)
+{
+ return http_init_req("GET", surl, headers, NULL, 0);
+}
+
+struct http_request *
+http_post(const char *surl, const char *headers, const char *body,
+ size_t body_len)
+{
+ return http_init_req("POST", surl, headers, body, body_len);
+}
+
+size_t
+http_merge_headers(const char *lheaders, const char *rheaders,
+ char **mheaders)
+{
+ /* rheaders take precedence */
+ size_t lhlen, rhlen, mhlen;
+ size_t l, r, lastl, lastr;
+ char *h;
+ bool found;
+
+ lhlen = strlen(lheaders);
+ rhlen = strlen(rheaders);
+
+ h = *mheaders = xmalloczero(lhlen + rhlen + 1);
+ if (*mheaders == NULL)
+ panic("http: Failed allocating space for new headers");
+
+ /* add each in lheaders, unless it's in rheaders */
+ mhlen = 0;
+ for (l = 0, lastl = 0; l < lhlen; l++) {
+ *h++ = lheaders[l];
+ mhlen++;
+
+ if (lheaders[l] != ':')
+ continue;
+
+ found = false;
+ for (r = 0, lastr = 0; r < rhlen; r++) {
+ if (rheaders[r] == '\n') {
+ lastr = r + 1;
+ continue;
+ }
+
+ if (rheaders[r] != ':')
+ continue;
+
+ if (strncasecmp(lheaders + lastl, rheaders + lastr,
+ r - lastr + 1) == 0) {
+ /* take rvalue */
+ found = true;
+ for (r = r + 1; r < rhlen; r++) {
+ *h++ = rheaders[r];
+ mhlen++;
+ if (rheaders[r] == '\n')
+ break;
+ }
+ break;
+ }
+ }
+
+ for (l = l + 1; l < lhlen; l++) {
+ if (!found) {
+ *h++ = lheaders[l];
+ mhlen++;
+ }
+ if (lheaders[l] == '\n')
+ break;
+ }
+ lastl = l + 1;
+ }
+
+ (*mheaders)[mhlen] = '\0';
+
+ /* now add each in rheaders, unless it's in lheaders */
+ for (r = 0, lastr = 0; r < rhlen; r++) {
+ if (rheaders[r] != ':')
+ continue;
+
+ found = false;
+ for (l = 0, lastl = 0; l < lhlen; l++) {
+ if (lheaders[l] == '\n') {
+ lastl = l + 1;
+ continue;
+ }
+ if (lheaders[l] != ':')
+ continue;
+
+ if (strncasecmp(lheaders + lastl, rheaders + lastr,
+ l - lastl + 1) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ for (r = lastr; r < rhlen; r++) {
+ if (!found) {
+ *h++ = rheaders[r];
+ mhlen++;
+ }
+ if (rheaders[r] == '\n')
+ break;
+ }
+ lastr = r + 1;
+ }
+
+ (*mheaders)[mhlen] = '\0';
+
+ return mhlen;
+}
+
+ssize_t
+http_req_ingest(struct http_request *req)
+{
+ short err;
+ unsigned short rlen;
+
+ if (!req)
+ return -1;
+
+ err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
+ nil, nil, false);
+ if (err ||
+ req->tcp_status_pb.connectionState != ConnectionStateEstablished)
+ return -1;
+
+ if (req->tcp_status_pb.amtUnreadData == 0)
+ return 0;
+ if (req->chunk_len == sizeof(req->chunk))
+ return 0;
+
+ rlen = MIN(req->tcp_status_pb.amtUnreadData,
+ sizeof(req->chunk) - req->chunk_len);
+
+ err = _TCPRcv(&req->tcp_iopb, req->tcp_stream,
+ req->chunk + req->chunk_len, &rlen, nil, nil, false);
+ if (err)
+ return -1;
+
+ req->chunk_len += rlen;
+
+ return rlen;
+}
+
+ssize_t
+http_req_read_line(struct http_request *req, char *ret, size_t len)
+{
+ ssize_t n, copy = -1, skip = 0;
+ bool dead = false;
+
+ for (;;) {
+ if (!dead && http_req_ingest(req) < 0)
+ dead = true;
+
+ for (n = 1; n < req->chunk_len; n++) {
+ if (req->chunk[n - 1] == '\r' && req->chunk[n] == '\n') {
+ copy = n - 1;
+ skip = 2;
+ break;
+ } else if (n + 1 == len) {
+ copy = n + 1;
+ break;
+ }
+ }
+
+ if (copy == -1 && dead && req->chunk_len < len) {
+ copy = req->chunk_len;
+ break;
+ }
+ if (copy >= 0)
+ break;
+ }
+
+ if (ret != NULL && copy > 0)
+ memcpy(ret, req->chunk, copy);
+
+ if (req->chunk_len <= copy + skip)
+ req->chunk_len = 0;
+ else {
+ memmove(req->chunk, req->chunk + copy + skip,
+ req->chunk_len - copy - skip);
+ req->chunk_len -= copy + skip;
+ }
+
+ return copy;
+}
+
+bool
+http_req_skip_headers(struct http_request *req)
+{
+ ssize_t size;
+ size_t length;
+ char header[128];
+
+ for (;;) {
+ size = http_req_read_line(req, (char *)&header, sizeof(header));
+ if (size < 0)
+ return false;
+ if (size == 0)
+ return true;
+
+ if (size >= sizeof(header))
+ size = sizeof(header) - 1;
+ header[size] = '\0';
+
+ if (strncmp(header, "Content-Length: ", 16) == 0) {
+ if (sscanf(header, "Content-Length: %ld", &length) == 1)
+ req->content_len = length;
+ }
+ }
+
+ return false;
+}
+
+void
+http_req_free(void *reqptr)
+{
+ unsigned long *addr = (unsigned long *)reqptr;
+ void *ptr = (void *)*addr;
+ struct http_request *req = (struct http_request *)ptr;
+
+ if (req == NULL)
+ return;
+
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, nil, nil, false);
+
+ if (req->message != NULL)
+ xfree(&req->message);
+ xfree(&req->tcp_buf);
+ xfree(&req->url);
+ xfree(&req);
+
+ *addr = 0L;
+}
+
+/* strlcat but passes a known length */
+size_t
+strappend(char *str, size_t str_size, size_t str_len, char *append)
+{
+ if (str_len >= str_size - 1)
+ return str_len + strlen(append);
+
+ while (*append != '\0') {
+ if (str_len < str_size - 1) {
+ str[str_len] = *append;
+ append++;
+ }
+ str_len++;
+ }
+ if (str_len < str_size - 1)
+ str[str_len] = '\0';
+ else
+ str[str_size] = '\0';
+
+ return str_len;
+}
+
+bool
+pushover(char *api_token, char *user_key, char *options, char *title,
+ char *msg)
+{
+ static char http_body[1024], param[64];
+ struct http_request *http;
+ char *curopt;
+ ssize_t len, n, lastn, blen, param_len;
+ char c;
+
+ blen = strappend(http_body, sizeof(http_body), 0, "token=");
+ blen = strappend(http_body, sizeof(http_body), blen, api_token);
+ blen = strappend(http_body, sizeof(http_body), blen, "&user=");
+ blen = strappend(http_body, sizeof(http_body), blen, user_key);
+
+ if (title && title[0]) {
+ blen = strappend(http_body, sizeof(http_body), blen, "&title=");
+ len = url_encode((unsigned char *)title, 0,
+ (unsigned char *)http_body + blen, sizeof(http_body) - blen);
+ if (len == -1)
+ return false;
+ blen += len;
+ }
+
+ blen = strappend(http_body, sizeof(http_body), blen, "&message=");
+
+ if (blen >= sizeof(http_body))
+ return false;
+
+ len = url_encode((unsigned char *)msg, 0,
+ (unsigned char *)http_body + blen, sizeof(http_body) - blen);
+ if (len == -1)
+ return false;
+ blen += len;
+
+ if (blen >= sizeof(http_body))
+ return false;
+
+ param[0] = '\0';
+
+ for (n = 0, param_len = 0; options && options[0]; n++) {
+ c = options[n];
+
+ if (c == '=' || c == ',' || c == '\0') {
+ /* remove trailing space */
+ param[param_len] = '\0';
+ while (param_len > 0 && param[param_len - 1] == ' ') {
+ param[param_len - 1] = '\0';
+ param_len--;
+ }
+
+ if (c == '=')
+ blen = strappend(http_body, sizeof(http_body), blen,
+ "&");
+ else if (c == ',' || c == '\0')
+ blen = strappend(http_body, sizeof(http_body), blen,
+ "=");
+
+ if (param_len) {
+ if (blen >= sizeof(http_body))
+ return false;
+ len = url_encode((unsigned char *)param, param_len,
+ (unsigned char *)http_body + blen,
+ sizeof(http_body) - blen);
+ if (len == -1)
+ return false;
+ blen += len;
+ }
+
+ if (c == '\0')
+ break;
+
+ param_len = 0;
+ param[0] = '\0';
+ } else if (param_len < sizeof(param)) {
+ if (c == ' ' && param[0] == '\0')
+ continue;
+ param[param_len++] = c;
+ } else
+ return false;
+ }
+
+ blen = strappend(http_body, sizeof(http_body), blen, "\r\n");
+ if (blen > sizeof(http_body))
+ return false;
+
+ http = http_post("http://api.pushover.net/1/messages.json",
+ "Content-type: application/x-www-form-urlencoded", http_body,
+ blen);
+ http_req_skip_headers(http);
+ http_req_free(&http);
+
+ return true;
+}
--- http.h Mon Sep 23 15:31:26 2024
+++ http.h Mon Sep 23 15:31:26 2024
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __HTTP_H__
+#define __HTTP_H__
+
+#include "util.h"
+#include "tcp.h"
+
+struct url {
+ char *scheme;
+ char *host;
+ unsigned short port;
+ char *path;
+};
+
+struct http_request {
+ struct url *url;
+ ip_addr host_ip;
+
+ TCPiopb tcp_iopb;
+ StreamPtr tcp_stream;
+ unsigned char *tcp_buf;
+ size_t tcp_buf_size;
+ wdsEntry tcp_wds[3];
+ TCPStatusPB tcp_status_pb;
+
+ char *message;
+
+ size_t content_len;
+ short response_code;
+
+ char chunk[2048];
+ ssize_t chunk_len;
+ ssize_t chunk_off;
+};
+
+struct url * url_parse(const char *str);
+ssize_t url_encode(unsigned char *str, size_t strlimit, unsigned char *dst,
+ size_t dstsize);
+
+size_t http_merge_headers(const char *lheaders, const char *rheaders,
+ char **mheaders);
+struct http_request * http_get(const char *surl, const char *headers);
+struct http_request * http_post(const char *surl, const char *headers,
+ const char *body, size_t body_len);
+ssize_t http_req_read_line(struct http_request *req, char *ret, size_t len);
+bool http_req_skip_headers(struct http_request *req);
+void http_req_free(void *reqptr);
+
+bool pushover(char *api_token, char *user_key, char *options, char *title,
+ char *msg);
+
+#endif
\ No newline at end of file
--- irc.c Sun Sep 22 17:14:39 2024
+++ irc.c Mon Sep 23 15:41:48 2024
@@ -563,8 +563,8 @@ irc_process_server(struct irc_connection *conn)
chatter_printf(conn->chatter, conn, user->nick,
"$B[%s($0%s@%s$B)]$0$/ %s",
user->nick, user->username, user->hostname, msg.msg);
- if (!conn->chatter->focusable->visible)
- notify();
+ notify(conn->chatter->focusable, msg.msg,
+ "Private message from %s", user->nick);
} else {
/* message to channel we're in */
char *us;
@@ -588,8 +588,8 @@ irc_process_server(struct irc_connection *conn)
chatter_printf(conn->chatter, conn, msg.arg[0],
"<$U%s$0>$/ %s",
user->nick, msg.msg);
- if (!conn->chatter->focusable->visible)
- notify();
+ notify(conn->chatter->focusable, msg.msg,
+ "Mention in %s by %s", msg.arg[0], user->nick);
} else
chatter_printf(conn->chatter, conn, msg.arg[0],
"$/<%s> %s",
@@ -1071,8 +1071,8 @@ irc_process_server(struct irc_connection *conn)
chatter_printf(conn->chatter, conn, NULL,
"$B***$0 $B%s$0$/ %s online",
msg.msg, strstr(msg.msg, ",") ? "are" : "is");
- if (conn->chatter->focusable && !conn->chatter->focusable->visible)
- notify();
+ if (conn->chatter->focusable)
+ notify(conn->chatter->focusable, NULL, NULL);
return true;
case 731:
/* monitor offline */
--- main.c Fri Sep 20 20:55:14 2024
+++ main.c Mon Sep 23 15:45:39 2024
@@ -17,6 +17,7 @@
#include <string.h>
#include <stdarg.h>
#include "chatter.h"
+#include "http.h"
#include "irc.h"
#include "settings.h"
#include "tcp.h"
@@ -218,7 +219,7 @@ show_connect_dialog(void)
{
bool valid;
- valid = settings_edit(!settings_load());
+ valid = connect_dialog(!settings_load());
update_menu();
if (!valid)
@@ -263,6 +264,10 @@ handle_menu(long menu_id)
show_connect_dialog();
ret = true;
break;
+ case FILE_MENU_SETTINGS_ID:
+ settings_dialog(!settings_load());
+ ret = true;
+ break;
case FILE_MENU_QUIT_ID:
ret = true;
if (focusables_quit())
@@ -471,14 +476,31 @@ handle_exit(void)
}
void
-notify(void)
+notify(struct focusable *focusable, char *msg, char *titlefmt, ...)
{
- memset(¬ification, 0, sizeof(notification));
- notification.qType = nmType;
- notification.nmMark = 1;
- notification.nmSound = (Handle)-1;
- notification.nmIcon = GetResource('SICN', NOTIFICATION_ICON_ID);
- NMInstall(¬ification);
+ static char title[250];
+ va_list argptr;
+
+ if (focusable && !focusable->visible) {
+ memset(¬ification, 0, sizeof(notification));
+ notification.qType = nmType;
+ notification.nmMark = 1;
+ notification.nmSound = (Handle)-1;
+ notification.nmIcon = GetResource('SICN', NOTIFICATION_ICON_ID);
+ NMInstall(¬ification);
+ }
+
+ if (msg && screen_saver_running()) {
+ va_start(argptr, titlefmt);
+ vsnprintf(title, sizeof(title), titlefmt, argptr);
+ va_end(argptr);
+
+ if (settings.pushover_api_token[0] && settings.pushover_user_key[0])
+ pushover(settings.pushover_api_token,
+ settings.pushover_user_key, settings.pushover_options, title,
+ msg);
+ /* otherwise maybe stop the screen saver? */
+ }
}
void
--- settings.c Fri Sep 20 20:44:55 2024
+++ settings.c Mon Sep 23 14:10:12 2024
@@ -51,42 +51,61 @@ struct setting {
unsigned long offset;
unsigned long size;
char password_storage[256];
-} settings_defs[] = {
+};
+
+struct setting connect_defs[] = {
{ "Server Name", SETTING_STRING, 0, 1, 0,
- DEFAULT_SERVER_NAME, SETTINGS_SERVER_ID,
+ DEFAULT_SERVER_NAME, CONNECT_SERVER_ID,
offsetof(struct settings, server),
member_size(struct settings, server) },
{ "Server Port", SETTING_USHORT, 0, 1, 65535,
- DEFAULT_PORT, SETTINGS_PORT_ID,
+ DEFAULT_PORT, CONNECT_PORT_ID,
offsetof(struct settings, port),
member_size(struct settings, port) },
{ "Server Password", SETTING_PASSWORD, 0, 0, 0,
- "", SETTINGS_PASSWORD_ID,
+ "", CONNECT_PASSWORD_ID,
offsetof(struct settings, password),
member_size(struct settings, password) },
{ "Nickname", SETTING_STRING, 0, 1, 0,
- "", SETTINGS_NICK_ID,
+ "", CONNECT_NICK_ID,
offsetof(struct settings, nick),
member_size(struct settings, nick) },
{ "Ident", SETTING_STRING, 0, 1, 0,
- DEFAULT_IDENT, SETTINGS_IDENT_ID,
+ DEFAULT_IDENT, CONNECT_IDENT_ID,
offsetof(struct settings, ident),
member_size(struct settings, ident) },
{ "Realname", SETTING_STRING, 0, 1, 0,
- DEFAULT_REALNAME, SETTINGS_REALNAME_ID,
+ DEFAULT_REALNAME, CONNECT_REALNAME_ID,
offsetof(struct settings, realname),
member_size(struct settings, realname) },
{ "Hide MOTD", SETTING_BOOL, 0, 0, 1,
- DEFAULT_HIDE_MOTD, SETTINGS_HIDE_MOTD_ID,
+ DEFAULT_HIDE_MOTD, CONNECT_HIDE_MOTD_ID,
offsetof(struct settings, hide_motd),
member_size(struct settings, hide_motd) },
{ "Channel", SETTING_STRING, 0, 0, 0,
- DEFAULT_CHANNEL, SETTINGS_CHANNEL_ID,
+ DEFAULT_CHANNEL, CONNECT_CHANNEL_ID,
offsetof(struct settings, channel),
member_size(struct settings, channel) },
};
+struct setting settings_defs[] = {
+ { "Pushover API Token", SETTING_STRING, 0, 0, 30,
+ "", SETTINGS_PUSHOVER_API_ID,
+ offsetof(struct settings, pushover_api_token),
+ member_size(struct settings, pushover_api_token) },
+ { "Pushover User Key", SETTING_STRING, 0, 0, 30,
+ "", SETTINGS_PUSHOVER_USER_ID,
+ offsetof(struct settings, pushover_user_key),
+ member_size(struct settings, pushover_user_key) },
+ { "Other Pushover Options", SETTING_STRING, 0, 0, 0,
+ "", SETTINGS_PUSHOVER_OPTS_ID,
+ offsetof(struct settings, pushover_options),
+ member_size(struct settings, pushover_options) },
+};
+
void settings_find_prefs_folder(short *vrefnum, long *dirid);
+bool settings_edit(struct setting *defs, size_t defs_count, short dlog_id,
+ bool use_defaults);
bool
settings_load(void)
@@ -196,8 +215,23 @@ create_failed:
}
bool
-settings_edit(bool use_defaults)
+connect_dialog(bool use_defaults)
{
+ return settings_edit(connect_defs, nitems(connect_defs),
+ CONNECT_DLOG_ID, use_defaults);
+}
+
+bool
+settings_dialog(bool use_defaults)
+{
+ return settings_edit(settings_defs, nitems(settings_defs),
+ SETTINGS_DLOG_ID, use_defaults);
+}
+
+bool
+settings_edit(struct setting *defs, size_t defs_count, short dlog_id,
+ bool use_defaults)
+{
struct settings tsettings = settings;
struct setting *s;
DialogTHndl dlgh;
@@ -212,18 +246,18 @@ settings_edit(bool use_defaults)
Str255 stmp;
/* center dialog in screen */
- dlgh = (DialogTHndl)xGetResource('DLOG', SETTINGS_DLOG_ID);
+ dlgh = (DialogTHndl)xGetResource('DLOG', dlog_id);
HLock(dlgh);
center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left,
(**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top,
true, &(**dlgh).boundsRect);
HUnlock(dlgh);
- if ((dlg = GetNewDialog(SETTINGS_DLOG_ID, nil, (WindowPtr)-1)) == NULL)
- panic("Can't find settings DLOG %d", SETTINGS_DLOG_ID);
+ if ((dlg = GetNewDialog(dlog_id, nil, (WindowPtr)-1)) == NULL)
+ panic("Can't find settings DLOG %d", dlog_id);
- for (n = 0; n < nitems(settings_defs); n++) {
- s = &settings_defs[n];
+ for (n = 0; n < defs_count; n++) {
+ s = &defs[n];
sdata = (char *)&tsettings + s->offset;
GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect);
@@ -303,8 +337,8 @@ get_input:
save = false;
verify:
- for (n = 0; n < nitems(settings_defs); n++) {
- s = &settings_defs[n];
+ for (n = 0; n < defs_count; n++) {
+ s = &defs[n];
sdata = (char *)&tsettings + s->offset;
GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect);
--- settings.h Tue Sep 10 22:04:44 2024
+++ settings.h Mon Sep 23 12:55:14 2024
@@ -22,15 +22,15 @@
#define SETTINGS_FILENAME "\pWallops Preferences"
#define SETTINGS_FILE_CREATOR 'WLOP'
-#define SETTINGS_DLOG_ID 129
-#define SETTINGS_SERVER_ID 2
-#define SETTINGS_PORT_ID 3
-#define SETTINGS_PASSWORD_ID 4
-#define SETTINGS_NICK_ID 5
-#define SETTINGS_IDENT_ID 6
-#define SETTINGS_REALNAME_ID 7
-#define SETTINGS_HIDE_MOTD_ID 8
-#define SETTINGS_CHANNEL_ID 9
+#define CONNECT_DLOG_ID 128
+#define CONNECT_SERVER_ID 2
+#define CONNECT_PORT_ID 3
+#define CONNECT_PASSWORD_ID 4
+#define CONNECT_NICK_ID 5
+#define CONNECT_IDENT_ID 6
+#define CONNECT_REALNAME_ID 7
+#define CONNECT_HIDE_MOTD_ID 8
+#define CONNECT_CHANNEL_ID 9
#define DEFAULT_SERVER_NAME "irc.libera.chat"
#define DEFAULT_PORT "6667"
@@ -39,6 +39,11 @@
#define DEFAULT_CHANNEL "#cyberpals"
#define DEFAULT_HIDE_MOTD "0"
+#define SETTINGS_DLOG_ID 129
+#define SETTINGS_PUSHOVER_API_ID 2
+#define SETTINGS_PUSHOVER_USER_ID 3
+#define SETTINGS_PUSHOVER_OPTS_ID 4
+
struct settings {
short version;
#define SETTINGS_VERSION 1
@@ -51,12 +56,16 @@ struct settings {
char channel[32];
unsigned char hide_motd;
unsigned long ignores;
+ char pushover_api_token[32];
+ char pushover_user_key[32];
+ char pushover_options[256];
};
extern struct settings settings;
bool settings_load(void);
void settings_save(struct settings *tsettings);
-bool settings_edit(bool use_defaults);
+bool connect_dialog(bool use_defaults);
+bool settings_dialog(bool use_defaults);
#endif
\ No newline at end of file
--- tcp.c Fri Aug 30 09:49:55 2024
+++ tcp.c Mon Sep 23 15:19:30 2024
@@ -197,7 +197,7 @@ _TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr
pb->tcpStream = stream;
pb->ioResult = 1;
- pb->csParam.open.ulpTimeoutValue = 30;
+ pb->csParam.open.ulpTimeoutValue = 10;
pb->csParam.open.ulpTimeoutAction = 1;
pb->csParam.open.validityFlags = 0xC0;
#if 0
--- wallops.π.r Sun Sep 22 17:36:59 2024
+++ wallops.π.r Tue Sep 24 12:40:08 2024
@@ -5,8 +5,9 @@ data 'MENU' (128) {
};
data 'MENU' (129) {
- $"0081 0000 0000 0000 0000 FFFF FFFB 0446" /* .Å.............F */
+ $"0081 0000 0000 0000 0000 FFFF FFF7 0446" /* .Å.............F */
$"696C 650A 436F 6E6E 6563 742E 2E2E 004E" /* ile.Connect....N */
+ $"0000 0B53 6574 7469 6E67 732E 2E2E 0000" /* ...Settings..... */
$"0000 012D 0000 0000 0451 7569 7400 5100" /* ...-.....Quit.Q. */
$"0000" /* .. */
};
@@ -54,26 +55,26 @@ data 'DITL' (128) {
};
data 'DITL' (129, "CONNECT_DITL") {
- $"0010 0000 0000 00FA 0096 010E 00E0 0407" /* .........ñ...... */
+ $"0010 0000 0000 00D7 0096 00EB 00E0 0407" /* .......◊.ñ...... */
$"436F 6E6E 6563 7400 0000 0000 000A 0096" /* Connect........ñ */
- $"001A 0144 1000 0000 0000 0028 0096 0038" /* ...D.......(.ñ.8 */
- $"00CA 1004 3636 3637 0000 0000 0046 0096" /* . ..6667.....F.ñ */
- $"0056 00F4 1000 0000 0000 0064 0096 0074" /* .V.........d.ñ.t */
- $"00F4 1000 0000 0000 0082 0096 0092 00F4" /* .........Ç.ñ.í.. */
- $"1000 0000 0000 00A0 0096 00B0 0144 1000" /* .......†.ñ.∞.D.. */
- $"0000 0000 00BE 0096 00D0 00B4 0500 0000" /* .....æ.ñ.–.¥.... */
- $"0000 00DC 0096 00EC 0144 1000 0000 0000" /* .....ñ...D...... */
+ $"001A 0144 1000 0000 0000 0023 0096 0033" /* ...D.......#.ñ.3 */
+ $"00CA 1004 3636 3637 0000 0000 003C 0096" /* . ..6667.....<.ñ */
+ $"004C 00F4 1000 0000 0000 0055 0096 0065" /* .L.........U.ñ.e */
+ $"00F4 1000 0000 0000 006E 0096 007E 00F4" /* .........n.ñ.~.. */
+ $"1000 0000 0000 0087 0096 0097 0144 1000" /* .......á.ñ.ó.D.. */
+ $"0000 0000 00A0 0096 00B2 00B4 0500 0000" /* .....†.ñ.≤.¥.... */
+ $"0000 00B9 0096 00C9 0144 1000 0000 0000" /* ...π.ñ.….D...... */
$"000A 000A 001A 0055 8807 5365 7276 6572" /* .......Uà.Server */
- $"3A72 0000 0000 0028 000A 0038 005E 880C" /* :r.....(...8.^à. */
+ $"3A72 0000 0000 0023 000A 0033 005E 880C" /* :r.....#...3.^à. */
$"5365 7276 6572 2050 6F72 743A 0000 0000" /* Server Port:.... */
- $"0046 000A 0056 0089 8810 5365 7276 6572" /* .F...V.âà.Server */
- $"2050 6173 7377 6F72 643A 0000 0000 0064" /* Password:.....d */
- $"000A 0074 0055 8805 4E69 636B 3A3A 0000" /* ...t.Uà.Nick::.. */
- $"0000 0082 000A 0092 0055 8806 4964 656E" /* ...Ç...í.Uà.Iden */
- $"743A 0000 0000 00A0 000A 00B0 0055 880A" /* t:.....†...∞.Uà. */
- $"5265 616C 204E 616D 653A 0000 0000 00BE" /* Real Name:.....æ */
- $"000A 00CE 0055 880A 4869 6465 204D 4F54" /* ...Œ.Uà.Hide MOT */
- $"443A 0000 0000 00DC 000A 00ED 0072 880D" /* D:...........rଠ*/
+ $"003C 000A 004C 0089 8810 5365 7276 6572" /* .<...L.âà.Server */
+ $"2050 6173 7377 6F72 643A 0000 0000 0055" /* Password:.....U */
+ $"000A 0065 0055 8805 4E69 636B 3A3A 0000" /* ...e.Uà.Nick::.. */
+ $"0000 006E 000A 007E 0055 8806 4964 656E" /* ...n...~.Uà.Iden */
+ $"743A 0000 0000 0087 000A 0097 0055 880A" /* t:.....á...ó.Uà. */
+ $"5265 616C 204E 616D 653A 0000 0000 00A0" /* Real Name:.....† */
+ $"000A 00B0 0055 880A 4869 6465 204D 4F54" /* ...∞.Uà.Hide MOT */
+ $"443A 0000 0000 00B9 000A 00CA 0072 880D" /* D:.....π... .rଠ*/
$"4A6F 696E 2043 6861 6E6E 656C 3A6E" /* Join Channel:n */
};
@@ -84,15 +85,34 @@ data 'DITL' (130, "ABOUT_DITL", purgeable, preload) {
$"0000 0000 FFD8 0106 011E C002 C180" /* .....ÿ....¿.¡Ä */
};
-data 'DLOG' (129, "CONNECT_DLOG") {
- $"0034 0056 0149 01A7 0004 0000 0100 0000" /* .4.V.I.ß........ */
- $"0000 0081 1143 6F6E 6E65 6374 2074 6F20" /* ...Å.Connect to */
- $"5365 7276 6572" /* Server */
+data 'DITL' (131, "SETTINGS_DITL") {
+ $"0006 0000 0000 005A 00A1 006E 00EB 0404" /* .......Z.°.n.... */
+ $"5361 7665 0000 0000 000A 00BE 001A 017E" /* Save.......æ...~ */
+ $"1000 0000 0000 0023 00BE 0033 017E 1000" /* .......#.æ.3.~.. */
+ $"0000 0000 003C 00BE 004C 017E 1000 0000" /* .....<.æ.L.~.... */
+ $"0000 000A 000A 001A 009F 8813 5075 7368" /* .........üà.Push */
+ $"6F76 6572 2041 5049 2054 6F6B 656E 3A12" /* over API Token:. */
+ $"0000 0000 0023 000A 0034 0096 8812 5075" /* .....#...4.ñà.Pu */
+ $"7368 6F76 6572 2055 7365 7220 4B65 793A" /* shover User Key: */
+ $"0000 0000 003C 000A 004D 00B2 8817 4F74" /* .....<...M.≤à.Ot */
+ $"6865 7220 5075 7368 6F76 6572 204F 7074" /* her Pushover Opt */
+ $"696F 6E73 3A00" /* ions:. */
};
+data 'DLOG' (129, "SETTINGS_DLOG") {
+ $"006A 0040 00E0 01CA 0004 0000 0100 0000" /* .j.@... ........ */
+ $"0000 0083 0853 6574 7469 6E67 73" /* ...É.Settings */
+};
+
data 'DLOG' (130, "ABOUT") {
$"0032 0024 0120 01CB 0001 0000 0000 0000" /* .2.$. .À........ */
$"0000 0082 0000 280A" /* ...Ç..(. */
+};
+
+data 'DLOG' (128, "CONNECT_DLOG") {
+ $"0034 0056 0128 01AA 0004 0000 0100 0000" /* .4.V.(.™........ */
+ $"0000 0081 1143 6F6E 6E65 6374 2074 6F20" /* ...Å.Connect to */
+ $"5365 7276 6572" /* Server */
};
data 'vers' (1) {