aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clients/bokftui.c1
-rw-r--r--clients/client.c3
-rw-r--r--clients/screens_bokslut.c2
-rw-r--r--clients/screens_ib.c2
-rw-r--r--clients/screens_invoices.c2
-rw-r--r--clients/screens_payroll.c5
-rw-r--r--clients/screens_templates.c2
-rw-r--r--clients/screens_vouchers.c2
-rw-r--r--clients/tui.c35
-rw-r--r--docs/STATE.md17
-rw-r--r--docs/TUI-GUIDELINES.md2
-rwxr-xr-xscripts/tui-golden.py60
-rw-r--r--src/smtp.c3
-rw-r--r--src/tax_table.c3
-rw-r--r--src/tls_ca.h31
15 files changed, 147 insertions, 23 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c
index 2dc23ba..69d6690 100644
--- a/clients/bokftui.c
+++ b/clients/bokftui.c
@@ -346,6 +346,7 @@ static void usage(FILE *f)
static void reset_modify_keys(void)
{
+ putp("\033[<u");
putp("\033[>4m");
fflush(stdout);
}
diff --git a/clients/client.c b/clients/client.c
index fd119f1..30f2a67 100644
--- a/clients/client.c
+++ b/clients/client.c
@@ -1,4 +1,5 @@
#include "client.h"
+#include "tls_ca.h"
#include <arpa/inet.h>
#include <errno.h>
@@ -126,7 +127,7 @@ static int tls_connect_addr(const char *addrport, struct client_conn *out)
}
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
- SSL_CTX_set_default_verify_paths(ctx);
+ tls_load_default_cas(ctx);
const char *ca = getenv("BOKFD_TLS_CA");
if (ca && *ca && SSL_CTX_load_verify_locations(ctx, ca, NULL) != 1) {
set_tls_error("TLS CA file");
diff --git a/clients/screens_bokslut.c b/clients/screens_bokslut.c
index df47196..3f69982 100644
--- a/clients/screens_bokslut.c
+++ b/clients/screens_bokslut.c
@@ -242,7 +242,7 @@ void bokslut_screen(struct app *a)
ff[7].cap = sizeof rate;
ff[7].kind = TUI_F_TEXT;
tui_form_hint("upp/ned/Tab = flytta Enter = ändra/utför F5 = visa"
- " bokslutsplan ^Enter = bokför planen (frågar"
+ " bokslutsplan ^Enter/F9 = bokför planen (frågar"
" först) Esc/q = tillbaka ^C = avsluta");
int r = tui_form_run_actions("Bokslut", ff, nf + 2, can_edit, acts, 5,
NULL, &sel);
diff --git a/clients/screens_ib.c b/clients/screens_ib.c
index ca35562..8950667 100644
--- a/clients/screens_ib.c
+++ b/clients/screens_ib.c
@@ -272,7 +272,7 @@ static int ib_form(struct app *a, char **old_acc, int64_t *old_amt, int nold)
for (;;) {
int rr = tui_rt_run("Ingående balans", &ib.rt,
"Tab = byta fält F5 = validera ^X = rensa rad"
- " ^Enter = spara Esc = avbryt");
+ " ^Enter/F9 = spara Esc = avbryt");
if (rr == -1)
return 0;
char msg[256];
diff --git a/clients/screens_invoices.c b/clients/screens_invoices.c
index b8cc8dd..ca59eef 100644
--- a/clients/screens_invoices.c
+++ b/clients/screens_invoices.c
@@ -1092,7 +1092,7 @@ static int64_t invoices_new(struct app *a)
tui_rt_set_fields(&f.rt, ff, 7);
const char *hint =
"Enter = välj/ändra Tab = byta fält F5 = förhandsvisa"
- " ^Enter = utfärda Esc = avbryt";
+ " ^Enter/F9 = utfärda Esc = avbryt";
int64_t out = 0;
for (;;) {
int rr = tui_rt_run("Ny faktura", &f.rt, hint);
diff --git a/clients/screens_payroll.c b/clients/screens_payroll.c
index 9bfaf38..23d765f 100644
--- a/clients/screens_payroll.c
+++ b/clients/screens_payroll.c
@@ -1101,7 +1101,8 @@ static int64_t payroll_run_screen(struct app *a, int64_t id)
{ status, -1, NULL },
{ totals, -1, NULL },
{ "Förhandsvisa (F5)", 1, NULL },
- { "Bokför körning (^Enter)", post_reason ? 0 : 1, post_reason },
+ { "Bokför körning (^Enter/F9)", post_reason ? 0 : 1,
+ post_reason },
{ "Lönebesked (PDF)", payslip_reason ? 0 : 1, payslip_reason },
{ "AGI-underlag", agi_reason ? 0 : 1, agi_reason },
{ "Betala skatt & avgifter", pay_reason ? 0 : 1, pay_reason },
@@ -1123,7 +1124,7 @@ static int64_t payroll_run_screen(struct app *a, int64_t id)
snprintf(title, sizeof title, "Ny lönekörning");
int editable = can_write && !posted;
tui_form_hint("upp/ned/Tab = flytta Enter = ändra/utför"
- " F5 = förhandsvisa ^Enter = bokför"
+ " F5 = förhandsvisa ^Enter/F9 = bokför"
" Esc/q = tillbaka ^C = avsluta");
int ret = tui_form_run_actions(title, ff, 2, editable, acts, 7, NULL,
&focus);
diff --git a/clients/screens_templates.c b/clients/screens_templates.c
index b2cd7ab..5777742 100644
--- a/clients/screens_templates.c
+++ b/clients/screens_templates.c
@@ -234,7 +234,7 @@ static int template_form(struct app *a, const char *load_name)
tui_rt_set_fields(&tf.rt, ff, 3);
const char *title = load_name ? "Redigera mall" : "Ny mall";
const char *hint = "Enter = ändra fält Tab = byta fält F5 = validera"
- " ^X = rensa rad ^Enter = spara Esc = avbryt";
+ " ^X = rensa rad ^Enter/F9 = spara Esc = avbryt";
for (;;) {
int rr = tui_rt_run(title, &tf.rt, hint);
if (rr == -1)
diff --git a/clients/screens_vouchers.c b/clients/screens_vouchers.c
index fedffe9..5e1b006 100644
--- a/clients/screens_vouchers.c
+++ b/clients/screens_vouchers.c
@@ -836,7 +836,7 @@ int64_t vouchers_new_prefill(struct app *a, const struct voucher_prefill *p)
tui_rt_normalize(&vf.rt);
const char *hint = "Enter = ändra fält Tab = byta fält F4 = mall"
" ^F = bifoga fil F5 = validera ^X = rensa rad"
- " ^Enter = bokför Esc = avbryt";
+ " ^Enter/F9 = bokför Esc = avbryt";
for (;;) {
int rr = tui_rt_run("Nytt verifikat", &vf.rt, hint);
if (rr == -1)
diff --git a/clients/tui.c b/clients/tui.c
index 031fad0..30761a8 100644
--- a/clients/tui.c
+++ b/clients/tui.c
@@ -159,9 +159,34 @@ int tui_sticky_split(char **lines, int n, char **head, int *nhead)
void tui_keys_setup(void)
{
- /* ^Enter has no control code; enable xterm modifyOtherKeys and bind the
- sequence it produces. Terminals without it ignore the request. */
+ /* ^Enter has no control code; enable xterm modifyOtherKeys level 2 (level
+ 1 leaves control+Enter alone) and the Kitty keyboard protocol, then bind
+ the sequences they produce. Terminals with neither keep F9 working. */
+ putp("\033[>4;2m");
+ putp("\033[>1u");
+ fflush(stdout);
define_key("\033[27;5;13~", TUI_KEY_CTRL_ENTER);
+ define_key("\033[13;5u", TUI_KEY_CTRL_ENTER);
+ define_key("\033[27;2;9~", KEY_BTAB);
+ define_key("\033[9;2u", KEY_BTAB);
+ define_key("\033[27u", 27);
+ define_key("\033[27;1u", 27);
+ define_key("\033[27;5;97~", 1);
+ define_key("\033[97;5u", 1);
+ define_key("\033[27;5;99~", 3);
+ define_key("\033[99;5u", 3);
+ define_key("\033[27;5;101~", 5);
+ define_key("\033[101;5u", 5);
+ define_key("\033[27;5;102~", 6);
+ define_key("\033[102;5u", 6);
+ define_key("\033[27;5;110~", 14);
+ define_key("\033[110;5u", 14);
+ define_key("\033[27;5;114~", 18);
+ define_key("\033[114;5u", 18);
+ define_key("\033[27;5;117~", 21);
+ define_key("\033[117;5u", 21);
+ define_key("\033[27;5;120~", 24);
+ define_key("\033[120;5u", 24);
/* Home/End arrive as CSI or SS3 depending on the terminal */
define_key("\033[1~", KEY_HOME);
define_key("\033OH", KEY_HOME);
@@ -184,8 +209,6 @@ void tui_keys_setup(void)
init_pair(TUI_CP_RULE, COLOR_BLUE, -1);
init_pair(TUI_CP_STATUS, COLOR_CYAN, -1);
}
- putp("\033[>4;1m");
- fflush(stdout);
}
@@ -1592,7 +1615,7 @@ static int form_run(const char *title, struct tui_form_field *f, int nf,
h = na > 0
? (can_edit
? "upp/ned/Tab = flytta Enter = ändra/utför"
- " F5 = uppdatera ^Enter = spara"
+ " F5 = uppdatera ^Enter/F9 = spara"
" Esc/q = tillbaka ^C = avsluta"
: "upp/ned/Tab = flytta Enter = utför"
" F5 = uppdatera Esc/q = tillbaka"
@@ -1600,7 +1623,7 @@ static int form_run(const char *title, struct tui_form_field *f, int nf,
" (endast behöriga kan ändra)")
: (can_edit
? "upp/ned/Home/End Enter = ändra"
- " F5 = uppdatera ^Enter = spara"
+ " F5 = uppdatera ^Enter/F9 = spara"
" Esc/q = tillbaka ^C = avsluta"
: "upp/ned/Home/End F5 = uppdatera"
" Esc/q = tillbaka ^C = avsluta"
diff --git a/docs/STATE.md b/docs/STATE.md
index 39fa44e..929ddf1 100644
--- a/docs/STATE.md
+++ b/docs/STATE.md
@@ -14,14 +14,19 @@ unit tests and the docs consistency check.
## Resume here (2026-09-21)
-- **Deployed**: `v0.1.56`, healthy on `nas` (Alpine runtime, static aarch64
+- **Deployed**: `v0.1.59`, healthy on `nas` (Alpine runtime, static aarch64
binaries cross-compiled on this machine — ~20 s, the host only assembles
the image; image 33.7 MB, no `libssl3`). `v0.1.55` brought the payroll
server waves (schema v11: employees, tax tables, lönebesked) and the
- payroll TUI; `v0.1.56` brought the **Bolaget** hub (the Företag section is
- gone, the registers live under Bolaget). The live database migrated
- v9 → v11 on the v0.1.55 startup with the automatic pre-migration snapshot
- in `var/db/backup/`. `main` and the tags are pushed to `nas`.
+ payroll TUI; `v0.1.56` the **Bolaget** hub; `v0.1.57` the flat main menu
+ without section headings, the **System** hub (Skattetabeller, Revision)
+ and the client-side remembered attachment directory; `v0.1.58` loads the
+ system CA bundle explicitly so the static binaries can verify TLS (the
+ Skatteverket fetch, SMTP, static clients); `v0.1.59` fixes form saving on
+ modern terminals (modifyOtherKeys level 2, Kitty keyboard protocol, and
+ `^Enter/F9` in the hints). The live database migrated v9 → v11 on the
+ v0.1.55 startup with the automatic pre-migration snapshot in
+ `var/db/backup/`. `main` and the tags are pushed to `nas`.
- **Next tasks (payroll follow-ups)**: fold `payroll.settings_get/set` into
`settings.get/set` (cmd_settings.c was busy during wave 1); settle the
over-80k % rule (SKV 433 leaves it ambiguous — currently a clear
@@ -86,7 +91,7 @@ Original entries for the struck items are in `docs/DECISIONS.md`.
## Environment / how to run
-- **Deployed**: `scripts/deploy.sh` (latest `v0.1.56`, healthy on nas).
+- **Deployed**: `scripts/deploy.sh` (latest `v0.1.59`, healthy on nas).
Live daemon `tls:bokf.makandra.eu:8788`, token
`~/.config/bokf/migration-token` (scopes `read,write`; owner-only actions
like closing years must be done by the human in the TUI). Git remote
diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md
index 94c3b5c..190a5c5 100644
--- a/docs/TUI-GUIDELINES.md
+++ b/docs/TUI-GUIDELINES.md
@@ -33,7 +33,7 @@ there.
| `Ctrl+F` | Attach a file via the file browser (voucher form and voucher detail) |
| `k` | Underlag: link the highlighted attachment to a voucher picked from a list |
| `Ctrl+X` | Clear the current row — only inside row editors (never "new") |
-| `Ctrl+Enter` | Save/post the current form. Enabled via xterm `modifyOtherKeys`; terminals that cannot send it keep `F9` working |
+| `Ctrl+Enter` | Save/post the current form. Enabled via xterm `modifyOtherKeys` level 2 or the Kitty keyboard protocol; terminals that send neither keep `F9` working, and the hints show `^Enter/F9` |
Every screen prints its keys in the footer via `hints()`. If a key exists, the
footer shows it; if the footer shows it, the key works. Control keys are
diff --git a/scripts/tui-golden.py b/scripts/tui-golden.py
index 9df5ed4..f5d5b7f 100755
--- a/scripts/tui-golden.py
+++ b/scripts/tui-golden.py
@@ -56,7 +56,9 @@ KEYS = {
"ctrlc": "\x03",
"ctrln": "\x0e",
"ctrlenter": "\x1b[27;5;13~",
+ "ctrlenter-kitty": "\x1b[13;5u",
"f5": "\x1b[15~",
+ "f9": "\x1b[20~",
}
# {org_name} {org_nr} {fy_label} {fy_start} {fy_end} are substituted at run
@@ -234,6 +236,58 @@ SCENARIOS = [
"expect": ["Anställda", "Testanställd", "Ny anställd"],
},
{
+ "name": "employee-new-f9",
+ "screen": "employees",
+ "steps": [
+ {
+ "keys": ["ctrln"],
+ "expect": ["Ny anställd", "Personnummer"],
+ },
+ {
+ "keys": ["enter", "Testperson", "enter"],
+ "expect": ["Ny anställd", "Testperson"],
+ },
+ {
+ "keys": ["down", "enter", "19900101-1234", "enter"],
+ "expect": ["19900101-1234"],
+ },
+ {
+ "keys": ["f9"],
+ "expect": ["Anställd skapad."],
+ },
+ {
+ "keys": ["enter"],
+ "expect": ["Anställda", "Testperson"],
+ },
+ ],
+ },
+ {
+ "name": "employee-new-kitty",
+ "screen": "employees",
+ "steps": [
+ {
+ "keys": ["ctrln"],
+ "expect": ["Ny anställd", "Personnummer"],
+ },
+ {
+ "keys": ["enter", "Kitty Person", "enter"],
+ "expect": ["Kitty Person"],
+ },
+ {
+ "keys": ["down", "enter", "19900202-5678", "enter"],
+ "expect": ["19900202-5678"],
+ },
+ {
+ "keys": ["ctrlenter-kitty"],
+ "expect": ["Anställd skapad."],
+ },
+ {
+ "keys": ["enter"],
+ "expect": ["Anställda", "Kitty Person"],
+ },
+ ],
+ },
+ {
"name": "company",
"screen": "company",
"expect": ["Bolaget", "Företagsuppgifter", "Fakturauppgifter",
@@ -947,6 +1001,12 @@ def main(argv):
report_failure(name, missing, "", before)
failures += 1
continue
+ if (b"\x1b[>4;2m" not in app.raw or
+ b"\x1b[>1u" not in app.raw):
+ report_failure(name, ["keyboard protocol enable "
+ "sequence"], "", before)
+ failures += 1
+ continue
current = before
for step in sc.get("steps", []):
keys = resolve_keys(step["keys"])
diff --git a/src/smtp.c b/src/smtp.c
index 6a90424..18834fa 100644
--- a/src/smtp.c
+++ b/src/smtp.c
@@ -18,6 +18,7 @@
#include <openssl/ssl.h>
#include <openssl/x509.h>
+#include "tls_ca.h"
#include "util.h"
#define SMTP_TIMEOUT_SEC 30
@@ -345,7 +346,7 @@ static int tls_start(struct smtp_conn *c, const char *host, char *err,
SSL_CTX_set_options(c->ctx, SSL_OP_NO_COMPRESSION |
SSL_OP_NO_RENEGOTIATION);
SSL_CTX_set_verify(c->ctx, SSL_VERIFY_PEER, NULL);
- if (SSL_CTX_set_default_verify_paths(c->ctx) != 1) {
+ if (tls_load_default_cas(c->ctx) != 1) {
set_err(err, errlen, "smtp: cannot load system CA certificates");
return -1;
}
diff --git a/src/tax_table.c b/src/tax_table.c
index 1cf40c1..6405a70 100644
--- a/src/tax_table.c
+++ b/src/tax_table.c
@@ -18,6 +18,7 @@
#include <openssl/x509.h>
#include "db.h"
+#include "tls_ca.h"
#include "version.h"
#define TT_LINE_LEN 49
@@ -478,7 +479,7 @@ static int tt_get_once(const struct tt_url *u, struct buf *body, int *status,
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION | SSL_OP_NO_RENEGOTIATION);
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
- if (SSL_CTX_set_default_verify_paths(ctx) != 1) {
+ if (tls_load_default_cas(ctx) != 1) {
set_err(err, "cannot load system CA certificates");
goto done;
}
diff --git a/src/tls_ca.h b/src/tls_ca.h
new file mode 100644
index 0000000..3b31345
--- /dev/null
+++ b/src/tls_ca.h
@@ -0,0 +1,31 @@
+#ifndef BOKF_TLS_CA_H
+#define BOKF_TLS_CA_H
+
+#include <openssl/ssl.h>
+
+/* Load the system trust store. A statically linked OpenSSL keeps the build
+ machine's compiled-in directory (e.g. Debian's /usr/lib/ssl), which may
+ not exist where the binary runs, so the common bundle locations are also
+ tried explicitly. Returns 1 when any store was loaded. */
+static inline int tls_load_default_cas(SSL_CTX *ctx)
+{
+ int ok = SSL_CTX_set_default_verify_paths(ctx) == 1;
+ static const char *const files[] = {
+ "/etc/ssl/certs/ca-certificates.crt",
+ "/etc/pki/tls/certs/ca-bundle.crt",
+ NULL,
+ };
+ static const char *const dirs[] = {
+ "/etc/ssl/certs",
+ NULL,
+ };
+ for (int i = 0; files[i]; i++)
+ if (SSL_CTX_load_verify_locations(ctx, files[i], NULL) == 1)
+ ok = 1;
+ for (int i = 0; dirs[i]; i++)
+ if (SSL_CTX_load_verify_locations(ctx, NULL, dirs[i]) == 1)
+ ok = 1;
+ return ok;
+}
+
+#endif