add colour configuration

and chance the default colours to something a bit saner
This commit is contained in:
Leon Henrik Plickat 2022-09-23 00:40:49 +02:00
parent 8a65d8b99b
commit 74ea4e17bb
4 changed files with 380 additions and 6 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
zig-out/
zig-cache/
pinentry-wayprompt
wayprompt-cli

281
src/ini.zig Normal file
View file

@ -0,0 +1,281 @@
// This file is part of nfm, the neat file manager.
//
// Copyright © 2021 Leon Henrik Plickat
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 3 as published
// by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std");
const ascii = std.ascii;
const io = std.io;
const mem = std.mem;
fn IniTok(comptime T: type) type {
return struct {
const Content = union(enum) {
section: []const u8,
assign: struct {
variable: []const u8,
value: []const u8,
},
};
const Self = @This();
reader: T,
stack_buffer: [1024]u8 = undefined,
// TODO maybe there should be an additional heap buffer for very long lines.
pub fn next(self: *Self, line: *usize) !?Content {
while (true) {
line.* += 1;
if (try self.reader.readUntilDelimiterOrEof(&self.stack_buffer, '\n')) |__buf| {
if (__buf.len == 1) {
if (__buf[0] == '#') {
continue;
} else {
return error.InvalidLine;
}
}
if (__buf.len == 0) continue;
const buf = blk: {
const _buf = mem.trim(u8, __buf, &ascii.spaces);
if (_buf.len == 0) continue;
if (_buf[0] == '#') continue;
for (_buf[1..]) |char, i| {
if (char == '#' and _buf[i - 1] != '\\') {
break :blk mem.trim(u8, _buf[0 .. i + 1], &ascii.spaces);
}
}
break :blk _buf;
};
// Is this line a section header?
if (buf[0] == '[') {
if (buf[buf.len - 1] != ']') return error.InvalidLine;
if (buf.len < 3) return error.InvalidLine;
return Content{ .section = buf[1 .. buf.len - 1] };
}
// Is this line an assignment?
var eq_pos = blk: {
for (buf) |char, i| {
if (char == '=') {
if (i == buf.len - 1) return error.InvalidLine;
break :blk i;
}
}
return error.InvalidLine;
};
return Content{
.assign = .{
.variable = blk: {
const variable = mem.trim(u8, buf[0..eq_pos], &ascii.spaces);
if (variable.len == 0) return error.InvalidLine;
break :blk variable;
},
.value = blk: {
const value = mem.trim(u8, buf[eq_pos + 1 ..], &ascii.spaces);
if (value.len < 2 or value[value.len - 1] != ';') return error.InvalidLine;
// TODO[zig] should be inline, but that crashes the zig compiler right now
for ([_]u8{ '\'', '"' }) |q| {
if (value[0] == q) {
if (value[value.len - 2] == q and value.len > 3) {
break :blk value[1 .. value.len - 2];
} else {
return error.InvalidLine;
}
}
}
break :blk value[0 .. value.len - 1];
},
},
};
} else {
return null;
}
}
}
};
}
pub fn tokenize(reader: anytype) IniTok(@TypeOf(reader)) {
return .{
.reader = reader,
};
}
test "ini tokenizer good input" {
const reader = io.fixedBufferStream(
\\[header] # I am a comment
\\a = b;
\\
\\c=d;
\\e= f;# Another comment
\\
\\[header2]
\\[header3]
\\#
\\
\\hello = this has spaces;
\\hello = this one; is weird;
\\hello = test=test;
\\
).reader();
var it = tokenize(reader);
var line: usize = 0;
if (try it.next(&line)) |a| {
try std.testing.expect(a == .section);
try std.testing.expectEqualSlices(u8, "header", a.section);
try std.testing.expect(line == 1);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .assign);
try std.testing.expectEqualSlices(u8, "a", a.assign.variable);
try std.testing.expectEqualSlices(u8, "b", a.assign.value);
try std.testing.expect(line == 2);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .assign);
try std.testing.expectEqualSlices(u8, "c", a.assign.variable);
try std.testing.expectEqualSlices(u8, "d", a.assign.value);
try std.testing.expect(line == 4);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .assign);
try std.testing.expectEqualSlices(u8, "e", a.assign.variable);
try std.testing.expectEqualSlices(u8, "f", a.assign.value);
try std.testing.expect(line == 5);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .section);
try std.testing.expectEqualSlices(u8, "header2", a.section);
try std.testing.expect(line == 7);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .section);
try std.testing.expectEqualSlices(u8, "header3", a.section);
try std.testing.expect(line == 8);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .assign);
try std.testing.expectEqualSlices(u8, "hello", a.assign.variable);
try std.testing.expectEqualSlices(u8, "this has spaces", a.assign.value);
try std.testing.expect(line == 11);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .assign);
try std.testing.expectEqualSlices(u8, "hello", a.assign.variable);
try std.testing.expectEqualSlices(u8, "this one; is weird", a.assign.value);
try std.testing.expect(line == 12);
} else {
unreachable;
}
if (try it.next(&line)) |a| {
try std.testing.expect(a == .assign);
try std.testing.expectEqualSlices(u8, "hello", a.assign.variable);
try std.testing.expectEqualSlices(u8, "test=test", a.assign.value);
try std.testing.expect(line == 13);
} else {
unreachable;
}
try std.testing.expect((try it.next(&line)) == null);
}
test "ini tokenizer bad input" {
{
const reader = io.fixedBufferStream(
\\[section
\\
).reader();
var it = tokenize(reader);
var line: usize = 0;
try std.testing.expectError(error.InvalidLine, it.next(&line));
try std.testing.expect(line == 1);
}
{
const reader = io.fixedBufferStream(
\\section]
\\
).reader();
var it = tokenize(reader);
var line: usize = 0;
try std.testing.expectError(error.InvalidLine, it.next(&line));
try std.testing.expect(line == 1);
}
{
const reader = io.fixedBufferStream(
\\[]
\\
).reader();
var it = tokenize(reader);
var line: usize = 0;
try std.testing.expectError(error.InvalidLine, it.next(&line));
try std.testing.expect(line == 1);
}
{
const reader = io.fixedBufferStream(
\\ =B;
\\
).reader();
var it = tokenize(reader);
var line: usize = 0;
try std.testing.expectError(error.InvalidLine, it.next(&line));
try std.testing.expect(line == 1);
}
{
const reader = io.fixedBufferStream(
\\a =
\\
).reader();
var it = tokenize(reader);
var line: usize = 0;
try std.testing.expectError(error.InvalidLine, it.next(&line));
try std.testing.expect(line == 1);
}
{
const reader = io.fixedBufferStream(
\\a = ;
\\
).reader();
var it = tokenize(reader);
var line: usize = 0;
try std.testing.expectError(error.InvalidLine, it.next(&line));
try std.testing.expect(line == 1);
}
}

View file

@ -226,6 +226,11 @@ fn parseInput(writer: io.BufferedWriter(4096, fs.File.Writer).Writer, line: []co
ascii.eqlIgnoreCase(command, "clearpassphrase") or // Undocumented, but present in "default" pinentry.
ascii.eqlIgnoreCase(command, "setrepeat") or // TODO prompt twice for the password, compare them and only accept when equal.
ascii.eqlIgnoreCase(command, "setrepeaterror") or
// A qualitybar is technically easy to implement: The argument to the
// command is the text next to the bar, which we'd probably ignore.
// If set, we can send "INQUIRE QUALITY <pin>" after every keypress and
// the client will respond with "<integer>\nEND\n". Would require doing
// so in the wayland event loop though.
ascii.eqlIgnoreCase(command, "setqualitybar") or
ascii.eqlIgnoreCase(command, "setqualitybar_tt"))
{

View file

@ -6,6 +6,8 @@ const log = std.log.scoped(.wayprompt);
const io = std.io;
const fmt = std.fmt;
const math = std.math;
const fs = std.fs;
const meta = std.meta;
const pixman = @import("pixman");
@ -17,13 +19,13 @@ const Context = struct {
loop: bool = true,
gpa: heap.GeneralPurposeAllocator(.{}) = .{},
background_colour: pixman.Color = pixmanColourFromRGB("0x666666") catch @compileError("Bad colour!"),
border_colour: pixman.Color = pixmanColourFromRGB("0x333333") catch @compileError("Bad colour!"),
text_colour: pixman.Color = pixmanColourFromRGB("0xffffff") catch @compileError("Bad colour!"),
background_colour: pixman.Color = pixmanColourFromRGB("0xffffff") catch @compileError("Bad colour!"),
border_colour: pixman.Color = pixmanColourFromRGB("0x000000") catch @compileError("Bad colour!"),
text_colour: pixman.Color = pixmanColourFromRGB("0x000000") catch @compileError("Bad colour!"),
error_text_colour: pixman.Color = pixmanColourFromRGB("0xff0000") catch @compileError("Bad colour!"),
pinarea_background_colour: pixman.Color = pixmanColourFromRGB("0x999999") catch @compileError("Bad colour!"),
pinarea_border_colour: pixman.Color = pixmanColourFromRGB("0x7F7F7F") catch @compileError("Bad colour!"),
pinarea_square_colour: pixman.Color = pixmanColourFromRGB("0xCCCCCC") catch @compileError("Bad colour!"),
pinarea_background_colour: pixman.Color = pixmanColourFromRGB("0xd0d0d0") catch @compileError("Bad colour!"),
pinarea_border_colour: pixman.Color = pixmanColourFromRGB("0x000000") catch @compileError("Bad colour!"),
pinarea_square_colour: pixman.Color = pixmanColourFromRGB("0x808080") catch @compileError("Bad colour!"),
title: ?[]const u8 = null,
prompt: ?[]const u8 = null,
@ -66,6 +68,8 @@ pub fn main() !u8 {
defer _ = context.gpa.deinit();
defer context.reset();
parseConfig() catch return 1;
const exec_name = blk: {
const full_command_name = mem.span(os.argv[0]);
var i: usize = full_command_name.len - 1;
@ -103,6 +107,89 @@ pub fn main() !u8 {
return 0;
}
fn parseConfig() !void {
const alloc = context.gpa.allocator();
const path = blk: {
if (os.getenv("XDG_CONFIG_HOME")) |xdg_config_home| {
break :blk try fs.path.join(alloc, &[_][]const u8{
xdg_config_home,
"wayprompt/config.ini",
});
} else if (os.getenv("HOME")) |home| {
break :blk try fs.path.join(alloc, &[_][]const u8{
home,
".config/wayprompt/config.ini",
});
} else {
break :blk try alloc.dupe(u8, "/etc/wayprompt/config.ini");
}
};
defer alloc.free(path);
os.access(path, os.R_OK) catch return;
const file = try fs.cwd().openFile(path, .{});
defer file.close();
const Section = enum { none, colours };
var section: Section = .none;
var buffer = std.io.bufferedReader(file.reader());
var it = ini.tokenize(buffer.reader());
var line: usize = 0;
while (it.next(&line) catch |err| {
if (err == error.InvalidLine) {
log.err("{s}:{}: Syntax error.", .{ path, line });
return error.BadConfig;
} else {
return err;
}
}) |content| {
switch (content) {
.section => |sect| section = blk: {
const sec = meta.stringToEnum(Section, sect);
if (sec == null or sec.? == .none) {
log.err("{s}:{}: Unknown section '{s}'.", .{ path, line, sec });
return error.BadConfig;
}
break :blk sec.?;
},
.assign => |as| switch (section) {
.none => {
log.err("{s}:{}: Assignments must be part of a section.", .{ path, line });
return error.BadConfig;
},
.colours => assignColour(as.variable, as.value) catch |err| {
switch (err) {
error.BadColour => log.err("{s}:{}: Bad colour: '{s}'", .{ path, line, as.value }),
error.UnknownColour => log.err("{s}:{}: Unknown colour: '{s}'", .{ path, line, as.variable }),
else => log.err("{s}:{}: Error while parsing colour: '{s}': {}", .{ path, line, as.variable, err }),
}
return error.BadConfig;
},
},
}
}
}
fn assignColour(variable: []const u8, value: []const u8) !void {
if (mem.eql(u8, variable, "background")) {
context.background_colour = try pixmanColourFromRGB(value);
} else if (mem.eql(u8, variable, "border")) {
context.border_colour = try pixmanColourFromRGB(value);
} else if (mem.eql(u8, variable, "text")) {
context.text_colour = try pixmanColourFromRGB(value);
} else if (mem.eql(u8, variable, "error-text")) {
context.error_text_colour = try pixmanColourFromRGB(value);
} else if (mem.eql(u8, variable, "pin-background")) {
context.pinarea_background_colour = try pixmanColourFromRGB(value);
} else if (mem.eql(u8, variable, "pin-border")) {
context.pinarea_border_colour = try pixmanColourFromRGB(value);
} else if (mem.eql(u8, variable, "pin-square")) {
context.pinarea_square_colour = try pixmanColourFromRGB(value);
} else {
return error.UnknownColour;
}
}
// Copied and adapted from https://git.sr.ht/~novakane/zelbar, same license.
fn pixmanColourFromRGB(descr: []const u8) !pixman.Color {
if (descr.len != "0xRRGGBB".len) return error.BadColour;