add colour configuration
and chance the default colours to something a bit saner
This commit is contained in:
parent
8a65d8b99b
commit
74ea4e17bb
4 changed files with 380 additions and 6 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
zig-out/
|
||||
zig-cache/
|
||||
pinentry-wayprompt
|
||||
wayprompt-cli
|
||||
|
|
281
src/ini.zig
Normal file
281
src/ini.zig
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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"))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue