Compare commits
2 Commits
master
...
add-custom
Author | SHA1 | Date |
---|---|---|
OFF0 | fff034a432 | 2 years ago |
OFF0 | 586b7716c2 | 2 years ago |
@ -1,15 +0,0 @@
|
|||||||
# https://clang.llvm.org/docs/ClangFormat.html
|
|
||||||
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
|
|
||||||
---
|
|
||||||
IndentWidth: 4
|
|
||||||
ContinuationIndentWidth: 4
|
|
||||||
UseTab: Never
|
|
||||||
ColumnLimit: 102
|
|
||||||
PointerAlignment: Right
|
|
||||||
BreakBeforeBraces: Linux
|
|
||||||
AlignAfterOpenBracket: DontAlign
|
|
||||||
BinPackArguments: false
|
|
||||||
BinPackParameters: false
|
|
||||||
AllowAllArgumentsOnNextLine: true
|
|
||||||
AllowAllParametersOfDeclarationOnNextLine: true
|
|
||||||
AllowShortFunctionsOnASingleLine: Empty
|
|
@ -1,3 +0,0 @@
|
|||||||
# zig fmt and clang-format all lib/ and src/
|
|
||||||
698e5b6f76f4cec712e147e86f6c373f811e28d0
|
|
||||||
2ecd44a7dbd03645de71a76ef5763ebc2bbe707e
|
|
@ -1,35 +0,0 @@
|
|||||||
clone:
|
|
||||||
git:
|
|
||||||
image: woodpeckerci/plugin-git
|
|
||||||
# https://woodpecker-ci.org/plugins/Git%20Clone
|
|
||||||
settings:
|
|
||||||
# tags are required for aarch64 release builds for semver
|
|
||||||
tags: true
|
|
||||||
lfs: false
|
|
||||||
recursive: false
|
|
||||||
pipeline:
|
|
||||||
lint:
|
|
||||||
image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
commands:
|
|
||||||
- ./tools/fmt-check.sh
|
|
||||||
test:
|
|
||||||
image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
commands:
|
|
||||||
- zig build test
|
|
||||||
sdl2:
|
|
||||||
image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
commands:
|
|
||||||
- zig build -Ddriver=sdl2
|
|
||||||
x11:
|
|
||||||
image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
commands:
|
|
||||||
- zig build -Ddriver=x11
|
|
||||||
aarch64:
|
|
||||||
image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
commands:
|
|
||||||
- zig build -Ddriver=fbev -Dtarget=aarch64-linux-musl -Doptimize=ReleaseSafe -Dstrip
|
|
||||||
- sha256sum zig-out/bin/nd zig-out/bin/ngui
|
|
||||||
playground:
|
|
||||||
image: git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
commands:
|
|
||||||
- zig build guiplay btcrpc lndhc
|
|
@ -1,102 +1,5 @@
|
|||||||
# nakamochi daemon and gui (ndg)
|
build for rpi:
|
||||||
|
|
||||||
release build for linux/aarch64, a raspberry pi 4:
|
zig build -Dtarget=aarch64-linux-musl -Ddriver=fbev -Drelease-safe -Dstrip
|
||||||
|
|
||||||
zig build -Dtarget=aarch64-linux-musl -Ddriver=fbev -Doptimize=ReleaseSafe -Dstrip
|
otherwise just `zig build` on dev host
|
||||||
|
|
||||||
a dev build for a native arch linux host running Xorg can be compiled simply
|
|
||||||
with `zig build`. otherwise, for macOS or non-X11 platforms use SDL2:
|
|
||||||
|
|
||||||
zig build -Ddriver=sdl2
|
|
||||||
|
|
||||||
## local development
|
|
||||||
|
|
||||||
you'll need [zig v0.11.x](https://ziglang.org/download/).
|
|
||||||
if working on the gui, also [SDL2](https://www.libsdl.org/).
|
|
||||||
|
|
||||||
note that compiling the daemon on macOS is currently unsupported since
|
|
||||||
it requires some linux primitives.
|
|
||||||
|
|
||||||
compiling is expected to be as easy as
|
|
||||||
|
|
||||||
# only gui
|
|
||||||
zig build ngui
|
|
||||||
# only daemon
|
|
||||||
zig build nd
|
|
||||||
# everything at once
|
|
||||||
zig build
|
|
||||||
|
|
||||||
the output is placed in `./zig-out/bin` directory. for example, to run the gui,
|
|
||||||
simply execute `./zig-out/bin/ngui`.
|
|
||||||
|
|
||||||
the build script has a few project-specific options. list them all with
|
|
||||||
a `zig build --help` command. for instance, to reduce LVGL logging verbosity in
|
|
||||||
debug build mode, one can set this extra build flag:
|
|
||||||
|
|
||||||
zig build ngui -Dlvgl_loglevel=warn
|
|
||||||
|
|
||||||
run all tests with
|
|
||||||
|
|
||||||
zig build test
|
|
||||||
|
|
||||||
or a filtered subset using `test-filter`:
|
|
||||||
|
|
||||||
zig build test -Dtest-filter=xxx
|
|
||||||
|
|
||||||
significant contributors may find adding [.git-blame-ignore-revs](.git-blame-ignore-revs)
|
|
||||||
file to their git config useful, to skip very likely irrelevant commits
|
|
||||||
when browsing `git blame`:
|
|
||||||
|
|
||||||
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
|
||||||
|
|
||||||
see also the [contributing](#contributing) section.
|
|
||||||
|
|
||||||
## CI automated checks
|
|
||||||
|
|
||||||
the CI runs code format checks, tests and builds for fbdev+evdev on aarch64
|
|
||||||
and SDL2. it requires a container image with zig and clang tools such as
|
|
||||||
clang-format.
|
|
||||||
|
|
||||||
to make a new image and switch the CI to use it, first modify the
|
|
||||||
[ci-containerfile](tools/ci-containerfile) and produce the image locally:
|
|
||||||
|
|
||||||
podman build --rm -t ndg-ci -f ./tools/ci-containerfile \
|
|
||||||
--build-arg ZIGURL=https://ziglang.org/download/0.12.0/zig-linux-x86_64-0.12.0.tar.xz
|
|
||||||
|
|
||||||
then tag it with the target URL, for example:
|
|
||||||
|
|
||||||
podman tag localhost/ndg-ci git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
|
|
||||||
generate an [access token](https://git.qcode.ch/user/settings/applications),
|
|
||||||
login to the container registry and push the image to remote:
|
|
||||||
|
|
||||||
podman login git.qcode.ch
|
|
||||||
podman push git.qcode.ch/nakamochi/ci-zig0.12.0:v1
|
|
||||||
|
|
||||||
the image will be available at
|
|
||||||
https://git.qcode.ch/nakamochi/-/packages/
|
|
||||||
|
|
||||||
finally, delete the access token from
|
|
||||||
https://git.qcode.ch/user/settings/applications
|
|
||||||
|
|
||||||
what's left is to update the CI [build pipeline](.woodpecker.yml) and delete
|
|
||||||
the older version of the image.
|
|
||||||
|
|
||||||
## contributing
|
|
||||||
|
|
||||||
to contribute, create a pull request or send a patch with
|
|
||||||
[git send-mail](https://git-scm.com/docs/git-send-email) to alex-dot-cloudware.io.
|
|
||||||
|
|
||||||
before sending a change, please make sure tests pass:
|
|
||||||
|
|
||||||
zig build test
|
|
||||||
|
|
||||||
and all code is formatted: zig code with `zig fmt` and C according to the
|
|
||||||
style described by [.clang-format](.clang-format) file. if `clang-format` tool
|
|
||||||
is installed, all formatting can be checked with:
|
|
||||||
|
|
||||||
./tools/fmt-check.sh
|
|
||||||
|
|
||||||
note that only C files in `src/` are formatted.
|
|
||||||
leave third party libraries as is - it is easier to update and upgrade when
|
|
||||||
the original style is preserved, even if it doesn't match this project.
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
.{
|
|
||||||
.name = "ndg",
|
|
||||||
.version = "0.8.1",
|
|
||||||
.dependencies = .{
|
|
||||||
.nif = .{
|
|
||||||
.path = "lib/nif",
|
|
||||||
},
|
|
||||||
.ini = .{
|
|
||||||
.path = "lib/ini",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.paths = .{
|
|
||||||
"build.zig",
|
|
||||||
"build.zig.zon",
|
|
||||||
"src",
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
*.zig text=auto eol=lf
|
|
||||||
*.zig text=auto eol=lf
|
|
@ -1 +0,0 @@
|
|||||||
github: MasterQ32
|
|
@ -1,2 +0,0 @@
|
|||||||
zig-cache/
|
|
||||||
zig-out/
|
|
@ -1,19 +0,0 @@
|
|||||||
Copyright (c) 2021 Felix "xq" Queißner
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
||||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
||||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
||||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -1,81 +0,0 @@
|
|||||||
# INI parser library
|
|
||||||
|
|
||||||
This is a very simple ini-parser library that provides:
|
|
||||||
- Raw record reading
|
|
||||||
- Leading/trailing whitespace removal
|
|
||||||
- comments based on `;` and `#`
|
|
||||||
- Zig API
|
|
||||||
- C API
|
|
||||||
|
|
||||||
## Usage example
|
|
||||||
|
|
||||||
### Zig
|
|
||||||
|
|
||||||
```zig
|
|
||||||
const std = @import("std");
|
|
||||||
const ini = @import("ini");
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
const file = try std.fs.cwd().openFile("example.ini", .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
var parser = ini.parse(std.testing.allocator, file.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
var writer = std.io.getStdOut().writer();
|
|
||||||
|
|
||||||
while (try parser.next()) |record| {
|
|
||||||
switch (record) {
|
|
||||||
.section => |heading| try writer.print("[{s}]\n", .{heading}),
|
|
||||||
.property => |kv| try writer.print("{s} = {s}\n", .{ kv.key, kv.value }),
|
|
||||||
.enumeration => |value| try writer.print("{s}\n", .{value}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### C
|
|
||||||
|
|
||||||
```c
|
|
||||||
#include <ini.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
FILE * f = fopen("example.ini", "rb");
|
|
||||||
if(!f)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
struct ini_Parser parser;
|
|
||||||
ini_create_file(&parser, f);
|
|
||||||
|
|
||||||
struct ini_Record record;
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
enum ini_Error error = ini_next(&parser, &record);
|
|
||||||
if(error != INI_SUCCESS)
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
switch(record.type) {
|
|
||||||
case INI_RECORD_NUL: goto done;
|
|
||||||
case INI_RECORD_SECTION:
|
|
||||||
printf("[%s]\n", record.section);
|
|
||||||
break;
|
|
||||||
case INI_RECORD_PROPERTY:
|
|
||||||
printf("%s = %s\n", record.property.key, record.property.value);
|
|
||||||
break;
|
|
||||||
case INI_RECORD_ENUMERATION:
|
|
||||||
printf("%s\n", record.enumeration);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
done:
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
ini_destroy(&parser);
|
|
||||||
fclose(f);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,73 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
|
||||||
const target = b.standardTargetOptions(.{});
|
|
||||||
|
|
||||||
_ = b.addModule("ini", .{
|
|
||||||
.root_source_file = .{
|
|
||||||
.path = "src/ini.zig",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const lib = b.addStaticLibrary(.{
|
|
||||||
.name = "ini",
|
|
||||||
.root_source_file = .{ .path = "src/lib.zig" },
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
lib.bundle_compiler_rt = true;
|
|
||||||
lib.addIncludePath(.{ .path = "src" });
|
|
||||||
lib.linkLibC();
|
|
||||||
|
|
||||||
b.installArtifact(lib);
|
|
||||||
|
|
||||||
const example_c = b.addExecutable(.{
|
|
||||||
.name = "example-c",
|
|
||||||
.optimize = optimize,
|
|
||||||
.target = target,
|
|
||||||
});
|
|
||||||
example_c.addCSourceFile(.{
|
|
||||||
.file = .{
|
|
||||||
.path = "example/example.c",
|
|
||||||
},
|
|
||||||
.flags = &.{
|
|
||||||
"-Wall",
|
|
||||||
"-Wextra",
|
|
||||||
"-pedantic",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
example_c.addIncludePath(.{ .path = "src" });
|
|
||||||
example_c.linkLibrary(lib);
|
|
||||||
example_c.linkLibC();
|
|
||||||
|
|
||||||
b.installArtifact(example_c);
|
|
||||||
|
|
||||||
const example_zig = b.addExecutable(.{
|
|
||||||
.name = "example-zig",
|
|
||||||
.root_source_file = .{ .path = "example/example.zig" },
|
|
||||||
.optimize = optimize,
|
|
||||||
.target = target,
|
|
||||||
});
|
|
||||||
example_zig.root_module.addImport("ini", b.modules.get("ini").?);
|
|
||||||
|
|
||||||
b.installArtifact(example_zig);
|
|
||||||
|
|
||||||
var main_tests = b.addTest(.{
|
|
||||||
.root_source_file = .{ .path = "src/test.zig" },
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
var binding_tests = b.addTest(.{
|
|
||||||
.root_source_file = .{ .path = "src/lib-test.zig" },
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
binding_tests.addIncludePath(.{ .path = "src" });
|
|
||||||
binding_tests.linkLibrary(lib);
|
|
||||||
binding_tests.linkLibC();
|
|
||||||
|
|
||||||
const test_step = b.step("test", "Run library tests");
|
|
||||||
test_step.dependOn(&main_tests.step);
|
|
||||||
test_step.dependOn(&binding_tests.step);
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
.{
|
|
||||||
.name = "libini",
|
|
||||||
.version = "0.0.0",
|
|
||||||
.dependencies = .{},
|
|
||||||
.paths = .{
|
|
||||||
"build.zig",
|
|
||||||
"build.zig.zon",
|
|
||||||
"src",
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
#include <ini.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
FILE * f = fopen("example.ini", "rb");
|
|
||||||
if(!f)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
struct ini_Parser parser;
|
|
||||||
ini_create_file(&parser, f);
|
|
||||||
|
|
||||||
struct ini_Record record;
|
|
||||||
while(true)
|
|
||||||
{
|
|
||||||
enum ini_Error error = ini_next(&parser, &record);
|
|
||||||
if(error != INI_SUCCESS)
|
|
||||||
goto cleanup;
|
|
||||||
|
|
||||||
switch(record.type) {
|
|
||||||
case INI_RECORD_NUL: goto done;
|
|
||||||
case INI_RECORD_SECTION:
|
|
||||||
printf("[%s]\n", record.section);
|
|
||||||
break;
|
|
||||||
case INI_RECORD_PROPERTY:
|
|
||||||
printf("%s = %s\n", record.property.key, record.property.value);
|
|
||||||
break;
|
|
||||||
case INI_RECORD_ENUMERATION:
|
|
||||||
printf("%s\n", record.enumeration);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
done:
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
ini_destroy(&parser);
|
|
||||||
fclose(f);
|
|
||||||
return 0;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
[Meta]
|
|
||||||
author = xq
|
|
||||||
library = ini
|
|
||||||
|
|
||||||
[Albums]
|
|
||||||
Thriller
|
|
||||||
Back in Black
|
|
||||||
Bat Out of Hell
|
|
||||||
The Dark Side of the Moon
|
|
@ -1,22 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const ini = @import("ini");
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
const file = try std.fs.cwd().openFile("example.ini", .{});
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
||||||
defer if (gpa.deinit() != .ok) @panic("memory leaked");
|
|
||||||
var parser = ini.parse(gpa.allocator(), file.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
var writer = std.io.getStdOut().writer();
|
|
||||||
|
|
||||||
while (try parser.next()) |record| {
|
|
||||||
switch (record) {
|
|
||||||
.section => |heading| try writer.print("[{s}]\n", .{heading}),
|
|
||||||
.property => |kv| try writer.print("{s} = {s}\n", .{ kv.key, kv.value }),
|
|
||||||
.enumeration => |value| try writer.print("{s}\n", .{value}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
#ifndef ZIG_INI_H
|
|
||||||
#define ZIG_INI_H
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdalign.h>
|
|
||||||
|
|
||||||
/// Opaque parser type. Consider the bytes in this struct
|
|
||||||
/// as "implementation defined".
|
|
||||||
/// This must also be fixed memory and must not be copied
|
|
||||||
/// after being initialized with `ini_create_*`!
|
|
||||||
struct ini_Parser
|
|
||||||
{
|
|
||||||
alignas(16) char opaque[128];
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ini_RecordType : int
|
|
||||||
{
|
|
||||||
INI_RECORD_NUL = 0,
|
|
||||||
INI_RECORD_SECTION = 1,
|
|
||||||
INI_RECORD_PROPERTY = 2,
|
|
||||||
INI_RECORD_ENUMERATION = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ini_KeyValuePair
|
|
||||||
{
|
|
||||||
char const * key;
|
|
||||||
char const * value;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ini_Record
|
|
||||||
{
|
|
||||||
enum ini_RecordType type;
|
|
||||||
union {
|
|
||||||
char const * section;
|
|
||||||
struct ini_KeyValuePair property;
|
|
||||||
char const * enumeration;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
enum ini_Error
|
|
||||||
{
|
|
||||||
INI_SUCCESS = 0,
|
|
||||||
INI_ERR_OUT_OF_MEMORY = 1,
|
|
||||||
INI_ERR_IO = 2,
|
|
||||||
INI_ERR_INVALID_DATA = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern void ini_create_buffer(
|
|
||||||
struct ini_Parser * parser,
|
|
||||||
char const * data,
|
|
||||||
size_t length
|
|
||||||
);
|
|
||||||
|
|
||||||
extern void ini_create_file(
|
|
||||||
struct ini_Parser * parser,
|
|
||||||
FILE * file
|
|
||||||
);
|
|
||||||
|
|
||||||
extern void ini_destroy(struct ini_Parser * parser);
|
|
||||||
|
|
||||||
extern enum ini_Error ini_next(struct ini_Parser * parser, struct ini_Record * record);
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,93 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
/// An entry in a ini file. Each line that contains non-whitespace text can
|
|
||||||
/// be categorized into a record type.
|
|
||||||
pub const Record = union(enum) {
|
|
||||||
/// A section heading enclosed in `[` and `]`. The brackets are not included.
|
|
||||||
section: [:0]const u8,
|
|
||||||
|
|
||||||
/// A line that contains a key-value pair separated by `=`.
|
|
||||||
/// Both key and value have the excess whitespace trimmed.
|
|
||||||
/// Both key and value allow escaping with C string syntax.
|
|
||||||
property: KeyValue,
|
|
||||||
|
|
||||||
/// A line that is either escaped as a C string or contains no `=`
|
|
||||||
enumeration: [:0]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const KeyValue = struct {
|
|
||||||
key: [:0]const u8,
|
|
||||||
value: [:0]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const whitespace = " \r\t\x00";
|
|
||||||
|
|
||||||
/// WARNING:
|
|
||||||
/// This function is not a general purpose function but
|
|
||||||
/// requires to be executed on slices of the line_buffer *after*
|
|
||||||
/// the NUL terminator appendix.
|
|
||||||
/// This function will override the character after the slice end,
|
|
||||||
/// so make sure there is one available!
|
|
||||||
fn insertNulTerminator(slice: []const u8) [:0]const u8 {
|
|
||||||
const mut_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(slice.ptr)));
|
|
||||||
mut_ptr[slice.len] = 0;
|
|
||||||
return mut_ptr[0..slice.len :0];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Parser(comptime Reader: type) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
line_buffer: std.ArrayList(u8),
|
|
||||||
reader: Reader,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.line_buffer.deinit();
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(self: *Self) !?Record {
|
|
||||||
while (true) {
|
|
||||||
self.reader.readUntilDelimiterArrayList(&self.line_buffer, '\n', 4096) catch |err| switch (err) {
|
|
||||||
error.EndOfStream => {
|
|
||||||
if (self.line_buffer.items.len == 0)
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
else => |e| return e,
|
|
||||||
};
|
|
||||||
try self.line_buffer.append(0); // append guaranteed space for sentinel
|
|
||||||
|
|
||||||
const line = if (std.mem.indexOfAny(u8, self.line_buffer.items, ";#")) |index|
|
|
||||||
std.mem.trim(u8, self.line_buffer.items[0..index], whitespace)
|
|
||||||
else
|
|
||||||
std.mem.trim(u8, self.line_buffer.items, whitespace);
|
|
||||||
if (line.len == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (std.mem.startsWith(u8, line, "[") and std.mem.endsWith(u8, line, "]")) {
|
|
||||||
return Record{ .section = insertNulTerminator(line[1 .. line.len - 1]) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std.mem.indexOfScalar(u8, line, '=')) |index| {
|
|
||||||
return Record{
|
|
||||||
.property = KeyValue{
|
|
||||||
// note: the key *might* replace the '=' in the slice with 0!
|
|
||||||
.key = insertNulTerminator(std.mem.trim(u8, line[0..index], whitespace)),
|
|
||||||
.value = insertNulTerminator(std.mem.trim(u8, line[index + 1 ..], whitespace)),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Record{ .enumeration = insertNulTerminator(line) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new parser that can read the ini structure
|
|
||||||
pub fn parse(allocator: std.mem.Allocator, reader: anytype) Parser(@TypeOf(reader)) {
|
|
||||||
return Parser(@TypeOf(reader)){
|
|
||||||
.line_buffer = std.ArrayList(u8).init(allocator),
|
|
||||||
.reader = reader,
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const c = @cImport({
|
|
||||||
@cInclude("ini.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
test "parser create/destroy" {
|
|
||||||
var buffer: c.ini_Parser = undefined;
|
|
||||||
c.ini_create_buffer(&buffer, "", 0);
|
|
||||||
c.ini_destroy(&buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expectNull(record: c.ini_Record) !void {
|
|
||||||
try std.testing.expectEqual(c.INI_RECORD_NUL, record.type);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expectSection(heading: []const u8, record: c.ini_Record) !void {
|
|
||||||
try std.testing.expectEqual(c.INI_RECORD_SECTION, record.type);
|
|
||||||
try std.testing.expectEqualStrings(heading, std.mem.span(record.unnamed_0.section));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expectKeyValue(key: []const u8, value: []const u8, record: c.ini_Record) !void {
|
|
||||||
try std.testing.expectEqual(c.INI_RECORD_PROPERTY, record.type);
|
|
||||||
try std.testing.expectEqualStrings(key, std.mem.span(record.unnamed_0.property.key));
|
|
||||||
try std.testing.expectEqualStrings(value, std.mem.span(record.unnamed_0.property.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expectEnumeration(enumeration: []const u8, record: c.ini_Record) !void {
|
|
||||||
try std.testing.expectEqual(c.INI_RECORD_ENUMERATION, record.type);
|
|
||||||
try std.testing.expectEqualStrings(enumeration, std.mem.span(record.unnamed_0.enumeration));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parseNext(parser: *c.ini_Parser) !c.ini_Record {
|
|
||||||
var record: c.ini_Record = undefined;
|
|
||||||
const err = c.ini_next(parser, &record);
|
|
||||||
switch (err) {
|
|
||||||
c.INI_SUCCESS => return record,
|
|
||||||
c.INI_ERR_OUT_OF_MEMORY => return error.OutOfMemory,
|
|
||||||
c.INI_ERR_IO => return error.InputOutput,
|
|
||||||
c.INI_ERR_INVALID_DATA => return error.InvalidData,
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn commonTest(parser: *c.ini_Parser) !void {
|
|
||||||
try expectSection("Meta", try parseNext(parser));
|
|
||||||
try expectKeyValue("author", "xq", try parseNext(parser));
|
|
||||||
try expectKeyValue("library", "ini", try parseNext(parser));
|
|
||||||
|
|
||||||
try expectSection("Albums", try parseNext(parser));
|
|
||||||
|
|
||||||
try expectEnumeration("Thriller", try parseNext(parser));
|
|
||||||
try expectEnumeration("Back in Black", try parseNext(parser));
|
|
||||||
try expectEnumeration("Bat Out of Hell", try parseNext(parser));
|
|
||||||
try expectEnumeration("The Dark Side of the Moon", try parseNext(parser));
|
|
||||||
|
|
||||||
try expectNull(try parseNext(parser));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "buffer parser" {
|
|
||||||
const slice =
|
|
||||||
\\[Meta]
|
|
||||||
\\author = xq
|
|
||||||
\\library = ini
|
|
||||||
\\
|
|
||||||
\\[Albums]
|
|
||||||
\\Thriller
|
|
||||||
\\Back in Black
|
|
||||||
\\Bat Out of Hell
|
|
||||||
\\The Dark Side of the Moon
|
|
||||||
;
|
|
||||||
|
|
||||||
var parser: c.ini_Parser = undefined;
|
|
||||||
c.ini_create_buffer(&parser, slice, slice.len);
|
|
||||||
defer c.ini_destroy(&parser);
|
|
||||||
|
|
||||||
try commonTest(&parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "file parser" {
|
|
||||||
const file = c.fopen("example/example.ini", "rb") orelse unreachable;
|
|
||||||
defer _ = c.fclose(file);
|
|
||||||
|
|
||||||
var parser: c.ini_Parser = undefined;
|
|
||||||
c.ini_create_file(&parser, file);
|
|
||||||
defer c.ini_destroy(&parser);
|
|
||||||
|
|
||||||
try commonTest(&parser);
|
|
||||||
}
|
|
@ -1,158 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const ini = @import("ini.zig");
|
|
||||||
|
|
||||||
const c = @cImport({
|
|
||||||
@cInclude("ini.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
const Record = extern struct {
|
|
||||||
type: Type,
|
|
||||||
value: Data,
|
|
||||||
|
|
||||||
const Type = enum(c.ini_RecordType) {
|
|
||||||
nul = 0,
|
|
||||||
section = 1,
|
|
||||||
property = 2,
|
|
||||||
enumeration = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Data = extern union {
|
|
||||||
section: [*:0]const u8,
|
|
||||||
property: KeyValuePair,
|
|
||||||
enumeration: [*:0]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const KeyValuePair = extern struct {
|
|
||||||
key: [*:0]const u8,
|
|
||||||
value: [*:0]const u8,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const BufferParser = struct {
|
|
||||||
stream: std.io.FixedBufferStream([]const u8),
|
|
||||||
parser: ini.Parser(std.io.FixedBufferStream([]const u8).Reader),
|
|
||||||
};
|
|
||||||
|
|
||||||
const IniParser = union(enum) {
|
|
||||||
buffer: BufferParser,
|
|
||||||
file: ini.Parser(CReader),
|
|
||||||
};
|
|
||||||
|
|
||||||
const IniError = enum(c.ini_Error) {
|
|
||||||
success = 0,
|
|
||||||
out_of_memory = 1,
|
|
||||||
io = 2,
|
|
||||||
invalid_data = 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
comptime {
|
|
||||||
if (@sizeOf(c.ini_Parser) < @sizeOf(IniParser))
|
|
||||||
@compileError(std.fmt.comptimePrint("ini_Parser struct in header is too small. Please set the char array to at least {d} chars!", .{@sizeOf(IniParser)}));
|
|
||||||
if (@alignOf(c.ini_Parser) < @alignOf(IniParser))
|
|
||||||
@compileError("align mismatch: ini_Parser struct does not match IniParser");
|
|
||||||
|
|
||||||
if (@sizeOf(c.ini_Record) != @sizeOf(Record))
|
|
||||||
@compileError("size mismatch: ini_Record struct does not match Record!");
|
|
||||||
if (@alignOf(c.ini_Record) != @alignOf(Record))
|
|
||||||
@compileError("align mismatch: ini_Record struct does not match Record!");
|
|
||||||
|
|
||||||
if (@sizeOf(c.ini_KeyValuePair) != @sizeOf(Record.KeyValuePair))
|
|
||||||
@compileError("size mismatch: ini_KeyValuePair struct does not match Record.KeyValuePair!");
|
|
||||||
if (@alignOf(c.ini_KeyValuePair) != @alignOf(Record.KeyValuePair))
|
|
||||||
@compileError("align mismatch: ini_KeyValuePair struct does not match Record.KeyValuePair!");
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn ini_create_buffer(parser: *IniParser, data: [*]const u8, length: usize) void {
|
|
||||||
parser.* = IniParser{
|
|
||||||
.buffer = .{
|
|
||||||
.stream = std.io.fixedBufferStream(data[0..length]),
|
|
||||||
.parser = undefined,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
// this is required to have the parser store a pointer to the stream.
|
|
||||||
parser.buffer.parser = ini.parse(std.heap.c_allocator, parser.buffer.stream.reader());
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn ini_create_file(parser: *IniParser, file: *std.c.FILE) void {
|
|
||||||
parser.* = IniParser{
|
|
||||||
.file = ini.parse(std.heap.c_allocator, cReader(file)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn ini_destroy(parser: *IniParser) void {
|
|
||||||
switch (parser.*) {
|
|
||||||
.buffer => |*p| p.parser.deinit(),
|
|
||||||
.file => |*p| p.deinit(),
|
|
||||||
}
|
|
||||||
parser.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ParseError = error{ OutOfMemory, StreamTooLong } || CReader.Error;
|
|
||||||
|
|
||||||
fn mapError(err: ParseError) IniError {
|
|
||||||
return switch (err) {
|
|
||||||
error.OutOfMemory => IniError.out_of_memory,
|
|
||||||
error.StreamTooLong => IniError.invalid_data,
|
|
||||||
else => IniError.io,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export fn ini_next(parser: *IniParser, record: *Record) IniError {
|
|
||||||
const src_record_or_null: ?ini.Record = switch (parser.*) {
|
|
||||||
.buffer => |*p| p.parser.next() catch |e| return mapError(e),
|
|
||||||
.file => |*p| p.next() catch |e| return mapError(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (src_record_or_null) |src_record| {
|
|
||||||
record.* = switch (src_record) {
|
|
||||||
.section => |heading| Record{
|
|
||||||
.type = .section,
|
|
||||||
.value = .{ .section = heading.ptr },
|
|
||||||
},
|
|
||||||
.enumeration => |enumeration| Record{
|
|
||||||
.type = .enumeration,
|
|
||||||
.value = .{ .enumeration = enumeration.ptr },
|
|
||||||
},
|
|
||||||
.property => |property| Record{
|
|
||||||
.type = .property,
|
|
||||||
.value = .{ .property = .{
|
|
||||||
.key = property.key.ptr,
|
|
||||||
.value = property.value.ptr,
|
|
||||||
} },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
record.* = Record{
|
|
||||||
.type = .nul,
|
|
||||||
.value = undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return .success;
|
|
||||||
}
|
|
||||||
|
|
||||||
const CReader = std.io.Reader(*std.c.FILE, std.fs.File.ReadError, cReaderRead);
|
|
||||||
|
|
||||||
fn cReader(c_file: *std.c.FILE) CReader {
|
|
||||||
return .{ .context = c_file };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cReaderRead(c_file: *std.c.FILE, bytes: []u8) std.fs.File.ReadError!usize {
|
|
||||||
const amt_read = std.c.fread(bytes.ptr, 1, bytes.len, c_file);
|
|
||||||
if (amt_read >= 0) return amt_read;
|
|
||||||
switch (@as(std.os.E, @enumFromInt(std.c._errno().*))) {
|
|
||||||
.SUCCESS => unreachable,
|
|
||||||
.INVAL => unreachable,
|
|
||||||
.FAULT => unreachable,
|
|
||||||
.AGAIN => unreachable, // this is a blocking API
|
|
||||||
.BADF => unreachable, // always a race condition
|
|
||||||
.DESTADDRREQ => unreachable, // connect was never called
|
|
||||||
.DQUOT => return error.DiskQuota,
|
|
||||||
.FBIG => return error.FileTooBig,
|
|
||||||
.IO => return error.InputOutput,
|
|
||||||
.NOSPC => return error.NoSpaceLeft,
|
|
||||||
.PERM => return error.AccessDenied,
|
|
||||||
.PIPE => return error.BrokenPipe,
|
|
||||||
else => |err| return std.os.unexpectedErrno(err),
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,183 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const ini = @import("ini.zig");
|
|
||||||
const parse = ini.parse;
|
|
||||||
const Record = ini.Record;
|
|
||||||
|
|
||||||
fn expectNull(record: ?Record) !void {
|
|
||||||
try std.testing.expectEqual(@as(?Record, null), record);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expectSection(heading: []const u8, record: ?Record) !void {
|
|
||||||
try std.testing.expectEqualStrings(heading, record.?.section);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expectKeyValue(key: []const u8, value: []const u8, record: ?Record) !void {
|
|
||||||
try std.testing.expectEqualStrings(key, record.?.property.key);
|
|
||||||
try std.testing.expectEqualStrings(value, record.?.property.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expectEnumeration(enumeration: []const u8, record: ?Record) !void {
|
|
||||||
try std.testing.expectEqualStrings(enumeration, record.?.enumeration);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "empty file" {
|
|
||||||
var stream = std.io.fixedBufferStream("");
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "section" {
|
|
||||||
var stream = std.io.fixedBufferStream("[Hello]");
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectSection("Hello", try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "key-value-pair" {
|
|
||||||
for (&[_][]const u8{
|
|
||||||
"key=value",
|
|
||||||
" key=value",
|
|
||||||
"key=value ",
|
|
||||||
" key=value ",
|
|
||||||
"key =value",
|
|
||||||
" key =value",
|
|
||||||
"key =value ",
|
|
||||||
" key =value ",
|
|
||||||
"key= value",
|
|
||||||
" key= value",
|
|
||||||
"key= value ",
|
|
||||||
" key= value ",
|
|
||||||
"key = value",
|
|
||||||
" key = value",
|
|
||||||
"key = value ",
|
|
||||||
" key = value ",
|
|
||||||
}) |pattern| {
|
|
||||||
var stream = std.io.fixedBufferStream(pattern);
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectKeyValue("key", "value", try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
test "enumeration" {
|
|
||||||
var stream = std.io.fixedBufferStream("enum");
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectEnumeration("enum", try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "empty line skipping" {
|
|
||||||
var stream = std.io.fixedBufferStream("item a\r\n\r\n\r\nitem b");
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectEnumeration("item a", try parser.next());
|
|
||||||
try expectEnumeration("item b", try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "multiple sections" {
|
|
||||||
var stream = std.io.fixedBufferStream(" [Hello] \r\n[Foo Bar]\n[Hello!]\n");
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectSection("Hello", try parser.next());
|
|
||||||
try expectSection("Foo Bar", try parser.next());
|
|
||||||
try expectSection("Hello!", try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "multiple properties" {
|
|
||||||
var stream = std.io.fixedBufferStream("a = b\r\nc =\r\nkey value = core property");
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectKeyValue("a", "b", try parser.next());
|
|
||||||
try expectKeyValue("c", "", try parser.next());
|
|
||||||
try expectKeyValue("key value", "core property", try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "multiple enumeration" {
|
|
||||||
var stream = std.io.fixedBufferStream(" a \n b \r\n c ");
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectEnumeration("a", try parser.next());
|
|
||||||
try expectEnumeration("b", try parser.next());
|
|
||||||
try expectEnumeration("c", try parser.next());
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "mixed data" {
|
|
||||||
var stream = std.io.fixedBufferStream(
|
|
||||||
\\[Meta]
|
|
||||||
\\author = xq
|
|
||||||
\\library = ini
|
|
||||||
\\
|
|
||||||
\\[Albums]
|
|
||||||
\\Thriller
|
|
||||||
\\Back in Black
|
|
||||||
\\Bat Out of Hell
|
|
||||||
\\The Dark Side of the Moon
|
|
||||||
);
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectSection("Meta", try parser.next());
|
|
||||||
try expectKeyValue("author", "xq", try parser.next());
|
|
||||||
try expectKeyValue("library", "ini", try parser.next());
|
|
||||||
|
|
||||||
try expectSection("Albums", try parser.next());
|
|
||||||
|
|
||||||
try expectEnumeration("Thriller", try parser.next());
|
|
||||||
try expectEnumeration("Back in Black", try parser.next());
|
|
||||||
try expectEnumeration("Bat Out of Hell", try parser.next());
|
|
||||||
try expectEnumeration("The Dark Side of the Moon", try parser.next());
|
|
||||||
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "# comments" {
|
|
||||||
var stream = std.io.fixedBufferStream(
|
|
||||||
\\[section] # comment
|
|
||||||
\\key = value # comment
|
|
||||||
\\enum # comment
|
|
||||||
);
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectSection("section", try parser.next());
|
|
||||||
try expectKeyValue("key", "value", try parser.next());
|
|
||||||
try expectEnumeration("enum", try parser.next());
|
|
||||||
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
||||||
|
|
||||||
test "; comments" {
|
|
||||||
var stream = std.io.fixedBufferStream(
|
|
||||||
\\[section] ; comment
|
|
||||||
\\key = value ; comment
|
|
||||||
\\enum ; comment
|
|
||||||
);
|
|
||||||
var parser = parse(std.testing.allocator, stream.reader());
|
|
||||||
defer parser.deinit();
|
|
||||||
|
|
||||||
try expectSection("section", try parser.next());
|
|
||||||
try expectKeyValue("key", "value", try parser.next());
|
|
||||||
try expectEnumeration("enum", try parser.next());
|
|
||||||
|
|
||||||
try expectNull(try parser.next());
|
|
||||||
}
|
|
@ -1,3 +1,2 @@
|
|||||||
**/*.o
|
**/*.o
|
||||||
**/*.d
|
**/*.d
|
||||||
build/*
|
|
@ -1,11 +0,0 @@
|
|||||||
prefix="@CMAKE_INSTALL_PREFIX@"
|
|
||||||
includedir="${prefix}/@INC_INSTALL_DIR@"
|
|
||||||
libdir=${prefix}/lib
|
|
||||||
|
|
||||||
Name: lv-drivers
|
|
||||||
Description: Display controller and touchpad driver that can be directly used with LVGL
|
|
||||||
URL: https://lvgl.io/
|
|
||||||
Version: 9.0.0
|
|
||||||
Cflags: -I${includedir}
|
|
||||||
Libs: -L${libdir} -llv_drivers
|
|
||||||
Requires: lvgl
|
|
@ -1,5 +1,10 @@
|
|||||||
LV_DRIVERS_PATH ?= ${shell pwd}/lv_drivers
|
LV_DRIVERS_DIR_NAME ?= lv_drivers
|
||||||
|
|
||||||
CSRCS += $(shell find $(LV_DRIVERS_PATH) -type f -name '*.c')
|
override CFLAGS := -I$(LVGL_DIR) $(CFLAGS)
|
||||||
CFLAGS += "-I$(LV_DRIVERS_PATH)"
|
|
||||||
|
|
||||||
|
CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/*.c)
|
||||||
|
CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/wayland/*.c)
|
||||||
|
CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/indev/*.c)
|
||||||
|
CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/gtkdrv/*.c)
|
||||||
|
CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/display/*.c)
|
||||||
|
CSRCS += $(wildcard $(LVGL_DIR)/$(LV_DRIVERS_DIR_NAME)/sdl/*.c)
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file sdl_common_internal.h
|
|
||||||
* Provides SDL related functions which are only used internal.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SDL_COMMON_INTERNAL_H
|
|
||||||
#define SDL_COMMON_INTERNAL_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*********************
|
|
||||||
* INCLUDES
|
|
||||||
*********************/
|
|
||||||
#include "sdl_common.h"
|
|
||||||
|
|
||||||
#if USE_SDL || USE_SDL_GPU
|
|
||||||
|
|
||||||
#include SDL_INCLUDE_PATH
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* GLOBAL PROTOTYPES
|
|
||||||
**********************/
|
|
||||||
int quit_filter(void * userdata, SDL_Event * event);
|
|
||||||
|
|
||||||
void mouse_handler(SDL_Event * event);
|
|
||||||
void mousewheel_handler(SDL_Event * event);
|
|
||||||
uint32_t keycode_to_ctrl_key(SDL_Keycode sdl_key);
|
|
||||||
void keyboard_handler(SDL_Event * event);
|
|
||||||
|
|
||||||
#endif /* USE_SDL || USE_SDL_GPU */
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} /* extern "C" */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* SDL_COMMON_INTERNAL_H */
|
|
@ -1,652 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file smm.c
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#if USE_WAYLAND
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include "smm.h"
|
|
||||||
|
|
||||||
#define MAX_NAME_ATTEMPTS (5)
|
|
||||||
#define PREFER_NUM_BUFFERS (3)
|
|
||||||
|
|
||||||
#define ROUND_UP(n, b) (((((n) ? (n) : 1) + (b) - 1) / (b)) * (b))
|
|
||||||
#define LLHEAD(type) \
|
|
||||||
struct { \
|
|
||||||
struct type *first; \
|
|
||||||
struct type *last; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define LLLINK(type) \
|
|
||||||
struct { \
|
|
||||||
struct type *next; \
|
|
||||||
struct type *prev; \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define LL_FIRST(head) ((head)->first)
|
|
||||||
#define LL_LAST(head) ((head)->last)
|
|
||||||
#define LL_IS_EMPTY(head) (LL_FIRST(head) == NULL)
|
|
||||||
#define LL_NEXT(src, member) ((src)->member.next)
|
|
||||||
#define LL_PREV(src, member) ((src)->member.prev)
|
|
||||||
|
|
||||||
#define LL_INIT(head) do { \
|
|
||||||
(head)->first = NULL; \
|
|
||||||
(head)->last = NULL; \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define LL_ENQUEUE(head, src, member) do { \
|
|
||||||
(src)->member.next = NULL; \
|
|
||||||
(src)->member.prev = (head)->last; \
|
|
||||||
if ((head)->last == NULL) { \
|
|
||||||
(head)->first = (src); \
|
|
||||||
} else { \
|
|
||||||
(head)->last->member.next = (src); \
|
|
||||||
} \
|
|
||||||
(head)->last = (src); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define LL_DEQUEUE(entry, head, member) do { \
|
|
||||||
(entry) = LL_FIRST(head); \
|
|
||||||
LL_REMOVE(head, entry, member); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define LL_INSERT_AFTER(head, dest, src, member) do { \
|
|
||||||
(src)->member.prev = (dest); \
|
|
||||||
(src)->member.next = (dest)->member.next; \
|
|
||||||
if ((dest)->member.next != NULL) { \
|
|
||||||
(dest)->member.next->member.prev = (src); \
|
|
||||||
} else { \
|
|
||||||
(head)->last = (src); \
|
|
||||||
} \
|
|
||||||
(dest)->member.next = (src); \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define LL_REMOVE(head, src, member) do { \
|
|
||||||
if ((src)->member.prev != NULL) { \
|
|
||||||
(src)->member.prev->member.next = (src)->member.next; \
|
|
||||||
} else { \
|
|
||||||
(head)->first = (src)->member.next; \
|
|
||||||
} \
|
|
||||||
if ((src)->member.next != NULL) { \
|
|
||||||
(src)->member.next->member.prev = (src)->member.prev; \
|
|
||||||
} else { \
|
|
||||||
(head)->last = (src)->member.prev; \
|
|
||||||
} \
|
|
||||||
} while (0)
|
|
||||||
|
|
||||||
#define LL_FOREACH(entry, head, member) \
|
|
||||||
for ((entry) = LL_FIRST(head); \
|
|
||||||
(entry) != NULL; \
|
|
||||||
(entry) = LL_NEXT(entry, member))
|
|
||||||
|
|
||||||
struct smm_pool {
|
|
||||||
struct smm_pool_properties props;
|
|
||||||
LLHEAD(smm_buffer) allocd;
|
|
||||||
void *map;
|
|
||||||
size_t map_size;
|
|
||||||
bool map_outdated;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct smm_buffer {
|
|
||||||
struct smm_buffer_properties props;
|
|
||||||
bool group_resized;
|
|
||||||
LLLINK(smm_buffer) pool;
|
|
||||||
LLLINK(smm_buffer) use;
|
|
||||||
LLLINK(smm_buffer) age;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct smm_group {
|
|
||||||
struct smm_group_properties props;
|
|
||||||
size_t size;
|
|
||||||
unsigned char num_buffers;
|
|
||||||
LLHEAD(smm_buffer) unused;
|
|
||||||
LLHEAD(smm_buffer) inuse;
|
|
||||||
LLHEAD(smm_buffer) history;
|
|
||||||
LLLINK(smm_group) link;
|
|
||||||
};
|
|
||||||
|
|
||||||
static size_t calc_buffer_size(struct smm_buffer *buf);
|
|
||||||
static void purge_history(struct smm_buffer *buf);
|
|
||||||
static struct smm_buffer *get_from_pool(struct smm_group *grp);
|
|
||||||
static void return_to_pool(struct smm_buffer *buf);
|
|
||||||
static struct smm_pool *alloc_pool(void);
|
|
||||||
static void free_pool(struct smm_pool *pool);
|
|
||||||
static struct smm_buffer *alloc_buffer(struct smm_buffer *last, size_t offset);
|
|
||||||
static void free_buffer(struct smm_buffer *buf);
|
|
||||||
|
|
||||||
static struct {
|
|
||||||
unsigned long page_sz;
|
|
||||||
struct smm_events cbs;
|
|
||||||
struct smm_pool *active;
|
|
||||||
LLHEAD(smm_group) groups;
|
|
||||||
struct {
|
|
||||||
size_t active_used;
|
|
||||||
} statistics;
|
|
||||||
} smm_instance;
|
|
||||||
|
|
||||||
|
|
||||||
void smm_init(struct smm_events *evs)
|
|
||||||
{
|
|
||||||
memcpy(&smm_instance.cbs, evs, sizeof(struct smm_events));
|
|
||||||
srand((unsigned int)clock());
|
|
||||||
smm_instance.page_sz = (unsigned long)sysconf(_SC_PAGESIZE);
|
|
||||||
LL_INIT(&smm_instance.groups);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void smm_deinit(void)
|
|
||||||
{
|
|
||||||
struct smm_group *grp;
|
|
||||||
|
|
||||||
/* Destroy all buffer groups */
|
|
||||||
while (!LL_IS_EMPTY(&smm_instance.groups)) {
|
|
||||||
LL_DEQUEUE(grp, &smm_instance.groups, link);
|
|
||||||
smm_destroy(grp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void smm_setctx(void *ctx)
|
|
||||||
{
|
|
||||||
smm_instance.cbs.ctx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
smm_group_t *smm_create(void)
|
|
||||||
{
|
|
||||||
struct smm_group *grp;
|
|
||||||
|
|
||||||
/* Allocate and intialize a new buffer group */
|
|
||||||
grp = malloc(sizeof(struct smm_group));
|
|
||||||
if (grp != NULL) {
|
|
||||||
grp->size = smm_instance.page_sz;
|
|
||||||
grp->num_buffers = 0;
|
|
||||||
LL_INIT(&grp->unused);
|
|
||||||
LL_INIT(&grp->inuse);
|
|
||||||
LL_INIT(&grp->history);
|
|
||||||
|
|
||||||
/* Add to instance groups queue */
|
|
||||||
LL_ENQUEUE(&smm_instance.groups, grp, link);
|
|
||||||
}
|
|
||||||
|
|
||||||
return grp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void smm_resize(smm_group_t *grp, size_t sz)
|
|
||||||
{
|
|
||||||
struct smm_buffer *buf;
|
|
||||||
struct smm_group *rgrp = grp;
|
|
||||||
|
|
||||||
/* Round allocation size up to a sysconf(_SC_PAGE_SIZE) boundary */
|
|
||||||
rgrp->size = ROUND_UP(sz, smm_instance.page_sz);
|
|
||||||
|
|
||||||
/* Return all unused buffers to pool (to be re-allocated at the new size) */
|
|
||||||
while (!LL_IS_EMPTY(&rgrp->unused)) {
|
|
||||||
LL_DEQUEUE(buf, &rgrp->unused, use);
|
|
||||||
return_to_pool(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mark all buffers in use to be freed to pool when possible */
|
|
||||||
LL_FOREACH(buf, &rgrp->inuse, use) {
|
|
||||||
buf->group_resized = true;
|
|
||||||
purge_history(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void smm_destroy(smm_group_t *grp)
|
|
||||||
{
|
|
||||||
struct smm_buffer *buf;
|
|
||||||
struct smm_group *dgrp = grp;
|
|
||||||
|
|
||||||
/* Return unused buffers */
|
|
||||||
while (!LL_IS_EMPTY(&dgrp->unused)) {
|
|
||||||
LL_DEQUEUE(buf, &dgrp->unused, use);
|
|
||||||
return_to_pool(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return buffers that are still in use (ideally this queue should be empty
|
|
||||||
* at this time)
|
|
||||||
*/
|
|
||||||
while (!LL_IS_EMPTY(&dgrp->inuse)) {
|
|
||||||
LL_DEQUEUE(buf, &dgrp->inuse, use);
|
|
||||||
return_to_pool(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove from instance groups queue */
|
|
||||||
LL_REMOVE(&smm_instance.groups, dgrp, link);
|
|
||||||
free(dgrp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
smm_buffer_t *smm_acquire(smm_group_t *grp)
|
|
||||||
{
|
|
||||||
struct smm_buffer *buf;
|
|
||||||
struct smm_group *agrp = grp;
|
|
||||||
|
|
||||||
if (LL_IS_EMPTY(&agrp->unused)) {
|
|
||||||
/* No unused buffer available, so get a new one from pool */
|
|
||||||
buf = get_from_pool(agrp);
|
|
||||||
} else {
|
|
||||||
/* Otherwise, reuse an unused buffer */
|
|
||||||
LL_DEQUEUE(buf, &agrp->unused, use);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf != NULL) {
|
|
||||||
/* Add buffer to in-use queue */
|
|
||||||
LL_ENQUEUE(&agrp->inuse, buf, use);
|
|
||||||
|
|
||||||
/* Emit 'init buffer' event */
|
|
||||||
if (smm_instance.cbs.init_buffer != NULL) {
|
|
||||||
if (smm_instance.cbs.init_buffer(smm_instance.cbs.ctx, &buf->props)) {
|
|
||||||
smm_release(buf);
|
|
||||||
buf = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf != NULL) {
|
|
||||||
/* Remove from history */
|
|
||||||
purge_history(buf);
|
|
||||||
|
|
||||||
/* Add to history a-new */
|
|
||||||
LL_ENQUEUE(&agrp->history, buf, age);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void *smm_map(smm_buffer_t *buf)
|
|
||||||
{
|
|
||||||
struct smm_buffer *mbuf = buf;
|
|
||||||
struct smm_pool *pool = mbuf->props.pool;
|
|
||||||
void *map = pool->map;
|
|
||||||
|
|
||||||
if (pool->map_outdated) {
|
|
||||||
/* Update mapping to current pool size */
|
|
||||||
if (pool->map != NULL) {
|
|
||||||
munmap(pool->map, pool->map_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
map = mmap(NULL,
|
|
||||||
pool->props.size,
|
|
||||||
PROT_READ | PROT_WRITE,
|
|
||||||
MAP_SHARED,
|
|
||||||
pool->props.fd,
|
|
||||||
0);
|
|
||||||
|
|
||||||
if (map == MAP_FAILED) {
|
|
||||||
map = NULL;
|
|
||||||
pool->map = NULL;
|
|
||||||
} else {
|
|
||||||
pool->map = map;
|
|
||||||
pool->map_size = pool->props.size;
|
|
||||||
pool->map_outdated = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calculate buffer mapping (from offset in pool) */
|
|
||||||
if (map != NULL) {
|
|
||||||
map = (((char *)map) + mbuf->props.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void smm_release(smm_buffer_t *buf)
|
|
||||||
{
|
|
||||||
struct smm_buffer *rbuf = buf;
|
|
||||||
struct smm_group *grp = rbuf->props.group;
|
|
||||||
|
|
||||||
/* Remove from in-use queue */
|
|
||||||
LL_REMOVE(&grp->inuse, rbuf, use);
|
|
||||||
|
|
||||||
if (rbuf->group_resized) {
|
|
||||||
/* Buffer group was resized while this buffer was in-use, thus it must be
|
|
||||||
* returned to it's pool
|
|
||||||
*/
|
|
||||||
rbuf->group_resized = false;
|
|
||||||
return_to_pool(rbuf);
|
|
||||||
} else {
|
|
||||||
/* Move to unused queue */
|
|
||||||
LL_ENQUEUE(&grp->unused, rbuf, use);
|
|
||||||
|
|
||||||
/* Try to limit total number of buffers to preferred number */
|
|
||||||
while ((grp->num_buffers > PREFER_NUM_BUFFERS) &&
|
|
||||||
(!LL_IS_EMPTY(&grp->unused))) {
|
|
||||||
LL_DEQUEUE(rbuf, &grp->unused, use);
|
|
||||||
return_to_pool(rbuf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
smm_buffer_t *smm_latest(smm_group_t *grp)
|
|
||||||
{
|
|
||||||
struct smm_group *lgrp = grp;
|
|
||||||
|
|
||||||
return LL_LAST(&lgrp->history);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
smm_buffer_t *smm_next(smm_buffer_t *buf)
|
|
||||||
{
|
|
||||||
struct smm_buffer *ibuf;
|
|
||||||
struct smm_buffer *nbuf = buf;
|
|
||||||
struct smm_group *grp = nbuf->props.group;
|
|
||||||
|
|
||||||
LL_FOREACH(ibuf, &grp->history, age) {
|
|
||||||
if (ibuf == nbuf) {
|
|
||||||
ibuf = LL_NEXT(ibuf, age);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ibuf;
|
|
||||||
}
|
|
||||||
|
|
||||||
void purge_history(struct smm_buffer *buf)
|
|
||||||
{
|
|
||||||
struct smm_buffer *ibuf;
|
|
||||||
struct smm_group *grp = buf->props.group;
|
|
||||||
|
|
||||||
/* Remove from history (and any older) */
|
|
||||||
LL_FOREACH(ibuf, &grp->history, age) {
|
|
||||||
if (ibuf == buf) {
|
|
||||||
do {
|
|
||||||
LL_DEQUEUE(ibuf, &grp->history, age);
|
|
||||||
} while (ibuf != buf);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
size_t calc_buffer_size(struct smm_buffer *buf)
|
|
||||||
{
|
|
||||||
size_t buf_sz;
|
|
||||||
struct smm_pool *buf_pool = buf->props.pool;
|
|
||||||
|
|
||||||
if (buf == LL_LAST(&buf_pool->allocd)) {
|
|
||||||
buf_sz = (buf_pool->props.size - buf->props.offset);
|
|
||||||
} else {
|
|
||||||
buf_sz = (LL_NEXT(buf, pool)->props.offset - buf->props.offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf_sz;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct smm_buffer *get_from_pool(struct smm_group *grp)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
size_t buf_sz;
|
|
||||||
struct smm_buffer *buf;
|
|
||||||
struct smm_buffer *last = NULL;
|
|
||||||
|
|
||||||
/* TODO: Determine when to allocate a new active pool (i.e. memory shrink) */
|
|
||||||
|
|
||||||
if (smm_instance.active == NULL) {
|
|
||||||
/* Allocate a new active pool */
|
|
||||||
smm_instance.active = alloc_pool();
|
|
||||||
smm_instance.statistics.active_used = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (smm_instance.active == NULL) {
|
|
||||||
buf = NULL;
|
|
||||||
} else {
|
|
||||||
/* Search for a free buffer large enough for allocation */
|
|
||||||
LL_FOREACH(buf, &smm_instance.active->allocd, pool) {
|
|
||||||
last = buf;
|
|
||||||
if (buf->props.group == NULL) {
|
|
||||||
buf_sz = calc_buffer_size(buf);
|
|
||||||
if (buf_sz == grp->size) {
|
|
||||||
break;
|
|
||||||
} else if (buf_sz > grp->size) {
|
|
||||||
if ((buf != LL_LAST(&smm_instance.active->allocd)) &&
|
|
||||||
(LL_NEXT(buf, pool)->props.group == NULL)) {
|
|
||||||
/* Pull back next buffer to use unallocated size */
|
|
||||||
LL_NEXT(buf, pool)->props.offset -= (buf_sz - grp->size);
|
|
||||||
} else {
|
|
||||||
/* Allocate another buffer to hold unallocated size */
|
|
||||||
alloc_buffer(buf, buf->props.offset + grp->size);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf == NULL) {
|
|
||||||
/* No buffer found to meet allocation size, expand pool */
|
|
||||||
if ((last != NULL) &&
|
|
||||||
(last->props.group == NULL)) {
|
|
||||||
/* Use last free buffer */
|
|
||||||
buf_sz = (grp->size - buf_sz);
|
|
||||||
} else {
|
|
||||||
/* Allocate new buffer */
|
|
||||||
buf_sz = grp->size;
|
|
||||||
if (last == NULL) {
|
|
||||||
buf = alloc_buffer(NULL, 0);
|
|
||||||
} else {
|
|
||||||
buf = alloc_buffer(last, smm_instance.active->props.size);
|
|
||||||
}
|
|
||||||
last = buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (last != NULL) {
|
|
||||||
/* Expand pool backing memory */
|
|
||||||
ret = ftruncate(smm_instance.active->props.fd,
|
|
||||||
smm_instance.active->props.size + buf_sz);
|
|
||||||
if (ret) {
|
|
||||||
if (buf != NULL) {
|
|
||||||
free_buffer(buf);
|
|
||||||
buf = NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
smm_instance.active->props.size += buf_sz;
|
|
||||||
smm_instance.active->map_outdated = true;
|
|
||||||
buf = last;
|
|
||||||
|
|
||||||
if (!(smm_instance.active->props.size - buf_sz)) {
|
|
||||||
/* Emit 'new pool' event */
|
|
||||||
if ((smm_instance.cbs.new_pool != NULL) &&
|
|
||||||
(smm_instance.cbs.new_pool(smm_instance.cbs.ctx,
|
|
||||||
&smm_instance.active->props))) {
|
|
||||||
free_buffer(buf);
|
|
||||||
free_pool(smm_instance.active);
|
|
||||||
smm_instance.active = NULL;
|
|
||||||
buf = NULL;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Emit 'expand pool' event */
|
|
||||||
if (smm_instance.cbs.expand_pool != NULL) {
|
|
||||||
smm_instance.cbs.expand_pool(smm_instance.cbs.ctx,
|
|
||||||
&smm_instance.active->props);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf != NULL) {
|
|
||||||
/* Set buffer group */
|
|
||||||
memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *));
|
|
||||||
|
|
||||||
/* Emit 'new buffer' event */
|
|
||||||
if (smm_instance.cbs.new_buffer != NULL) {
|
|
||||||
if (smm_instance.cbs.new_buffer(smm_instance.cbs.ctx, &buf->props)) {
|
|
||||||
grp = NULL;
|
|
||||||
memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *));
|
|
||||||
buf = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf != NULL) {
|
|
||||||
/* Update active pool usage statistic */
|
|
||||||
smm_instance.statistics.active_used += grp->size;
|
|
||||||
grp->num_buffers++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void return_to_pool(struct smm_buffer *buf)
|
|
||||||
{
|
|
||||||
struct smm_group *grp = buf->props.group;
|
|
||||||
struct smm_pool *pool = buf->props.pool;
|
|
||||||
|
|
||||||
/* Emit 'free buffer' event */
|
|
||||||
if (smm_instance.cbs.free_buffer != NULL) {
|
|
||||||
smm_instance.cbs.free_buffer(smm_instance.cbs.ctx, &buf->props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buffer is no longer part of history */
|
|
||||||
purge_history(buf);
|
|
||||||
|
|
||||||
/* Buffer is no longer part of group */
|
|
||||||
grp->num_buffers--;
|
|
||||||
grp = NULL;
|
|
||||||
memcpy((void *)&buf->props.group, &grp, sizeof(struct smm_group *));
|
|
||||||
|
|
||||||
/* Update active pool usage statistic */
|
|
||||||
if (smm_instance.active == pool) {
|
|
||||||
smm_instance.statistics.active_used -= calc_buffer_size(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Coalesce with ungrouped buffers beside this one */
|
|
||||||
if ((buf != LL_LAST(&pool->allocd)) &&
|
|
||||||
(LL_NEXT(buf, pool)->props.group == NULL)) {
|
|
||||||
free_buffer(LL_NEXT(buf, pool));
|
|
||||||
}
|
|
||||||
if ((buf != LL_FIRST(&pool->allocd)) &&
|
|
||||||
(LL_PREV(buf, pool)->props.group == NULL)) {
|
|
||||||
buf = LL_PREV(buf, pool);
|
|
||||||
pool = buf->props.pool;
|
|
||||||
free_buffer(LL_NEXT(buf, pool));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free buffer (and pool), if only remaining buffer in pool */
|
|
||||||
if ((buf == LL_FIRST(&pool->allocd)) &&
|
|
||||||
(buf == LL_LAST(&pool->allocd))) {
|
|
||||||
free_buffer(buf);
|
|
||||||
|
|
||||||
/* Emit 'free pool' event */
|
|
||||||
if (smm_instance.cbs.free_pool != NULL) {
|
|
||||||
smm_instance.cbs.free_pool(smm_instance.cbs.ctx, &pool->props);
|
|
||||||
}
|
|
||||||
|
|
||||||
free_pool(pool);
|
|
||||||
if (smm_instance.active == pool) {
|
|
||||||
smm_instance.active = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct smm_pool *alloc_pool(void)
|
|
||||||
{
|
|
||||||
struct smm_pool *pool;
|
|
||||||
char name[] = ("/" SMM_FD_NAME "-XXXXX");
|
|
||||||
unsigned char attempts = 0;
|
|
||||||
bool opened = false;
|
|
||||||
|
|
||||||
pool = malloc(sizeof(struct smm_pool));
|
|
||||||
if (pool != NULL) {
|
|
||||||
do {
|
|
||||||
/* A randomized pool name should help reduce collisions */
|
|
||||||
sprintf(name + sizeof(SMM_FD_NAME) + 1, "%05X", rand() & 0xFFFF);
|
|
||||||
pool->props.fd = shm_open(name,
|
|
||||||
O_RDWR | O_CREAT | O_EXCL,
|
|
||||||
S_IRUSR | S_IWUSR);
|
|
||||||
if (pool->props.fd >= 0) {
|
|
||||||
shm_unlink(name);
|
|
||||||
pool->props.size = 0;
|
|
||||||
pool->map = NULL;
|
|
||||||
pool->map_size = 0;
|
|
||||||
pool->map_outdated = false;
|
|
||||||
LL_INIT(&pool->allocd);
|
|
||||||
opened = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if (errno != EEXIST) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
} while (attempts < MAX_NAME_ATTEMPTS);
|
|
||||||
|
|
||||||
if (!opened) {
|
|
||||||
free(pool);
|
|
||||||
pool = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void free_pool(struct smm_pool *pool)
|
|
||||||
{
|
|
||||||
if (pool->map != NULL) {
|
|
||||||
munmap(pool->map, pool->map_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
close(pool->props.fd);
|
|
||||||
free(pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct smm_buffer *alloc_buffer(struct smm_buffer *last, size_t offset)
|
|
||||||
{
|
|
||||||
struct smm_buffer *buf;
|
|
||||||
struct smm_buffer_properties initial_props = {
|
|
||||||
{NULL},
|
|
||||||
NULL,
|
|
||||||
smm_instance.active,
|
|
||||||
offset
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Allocate and intialize a new buffer (including linking in to pool) */
|
|
||||||
buf = malloc(sizeof(struct smm_buffer));
|
|
||||||
if (buf != NULL) {
|
|
||||||
memcpy(&buf->props, &initial_props, sizeof(struct smm_buffer_properties));
|
|
||||||
buf->group_resized = false;
|
|
||||||
|
|
||||||
if (last == NULL) {
|
|
||||||
LL_ENQUEUE(&smm_instance.active->allocd, buf, pool);
|
|
||||||
} else {
|
|
||||||
LL_INSERT_AFTER(&smm_instance.active->allocd, last, buf, pool);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void free_buffer(struct smm_buffer *buf)
|
|
||||||
{
|
|
||||||
struct smm_pool *buf_pool = buf->props.pool;
|
|
||||||
|
|
||||||
/* Remove from pool */
|
|
||||||
LL_REMOVE(&buf_pool->allocd, buf, pool);
|
|
||||||
free(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,67 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file smm.h
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#ifndef SMM_H
|
|
||||||
#define SMM_H
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#define SMM_FD_NAME "lvgl-wayland"
|
|
||||||
#define SMM_POOL_TAGS (1)
|
|
||||||
#define SMM_BUFFER_TAGS (2)
|
|
||||||
#define SMM_GROUP_TAGS (1)
|
|
||||||
|
|
||||||
#define SMM_POOL_PROPERTIES(p) ((const struct smm_pool_properties *)(p))
|
|
||||||
#define SMM_BUFFER_PROPERTIES(b) ((const struct smm_buffer_properties *)(b))
|
|
||||||
#define SMM_GROUP_PROPERTIES(g) ((const struct smm_group_properties *)(g))
|
|
||||||
#define SMM_TAG(o, n, v) \
|
|
||||||
do { \
|
|
||||||
void **smm_tag = (void **)((char *)o + (n * sizeof(void *))); \
|
|
||||||
*smm_tag = (v); \
|
|
||||||
} while(0)
|
|
||||||
|
|
||||||
typedef void smm_pool_t;
|
|
||||||
typedef void smm_buffer_t;
|
|
||||||
typedef void smm_group_t;
|
|
||||||
|
|
||||||
struct smm_events {
|
|
||||||
void *ctx;
|
|
||||||
bool (*new_pool)(void *ctx, smm_pool_t *pool);
|
|
||||||
void (*expand_pool)(void *ctx, smm_pool_t *pool);
|
|
||||||
void (*free_pool)(void *ctx, smm_pool_t *pool);
|
|
||||||
bool (*new_buffer)(void *ctx, smm_buffer_t *buf);
|
|
||||||
bool (*init_buffer)(void *ctx, smm_buffer_t *buf);
|
|
||||||
void (*free_buffer)(void *ctx, smm_buffer_t *buf);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct smm_pool_properties {
|
|
||||||
void *tag[SMM_POOL_TAGS];
|
|
||||||
size_t size;
|
|
||||||
int fd;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct smm_buffer_properties {
|
|
||||||
void *tag[SMM_BUFFER_TAGS];
|
|
||||||
smm_group_t *const group;
|
|
||||||
smm_pool_t *const pool;
|
|
||||||
size_t offset;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct smm_group_properties {
|
|
||||||
void *tag[SMM_GROUP_TAGS];
|
|
||||||
};
|
|
||||||
|
|
||||||
void smm_init(struct smm_events *evs);
|
|
||||||
void smm_setctx(void *ctx);
|
|
||||||
void smm_deinit(void);
|
|
||||||
smm_group_t *smm_create(void);
|
|
||||||
void smm_resize(smm_group_t *grp, size_t sz);
|
|
||||||
void smm_destroy(smm_group_t *grp);
|
|
||||||
smm_buffer_t *smm_acquire(smm_group_t *grp);
|
|
||||||
void *smm_map(smm_buffer_t *buf);
|
|
||||||
void smm_release(smm_buffer_t *buf);
|
|
||||||
smm_buffer_t *smm_latest(smm_group_t *grp);
|
|
||||||
smm_buffer_t *smm_next(smm_buffer_t *buf);
|
|
||||||
|
|
||||||
#endif
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,284 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file x11.c
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*********************
|
|
||||||
* INCLUDES
|
|
||||||
*********************/
|
|
||||||
#include "x11.h"
|
|
||||||
#if USE_X11
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <X11/Xlib.h>
|
|
||||||
#include <X11/Xutil.h>
|
|
||||||
|
|
||||||
/*********************
|
|
||||||
* DEFINES
|
|
||||||
*********************/
|
|
||||||
#ifndef KEYBOARD_BUFFER_SIZE
|
|
||||||
#define KEYBOARD_BUFFER_SIZE 64
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define MIN(A, B) ((A) < (B) ? (A) : (B))
|
|
||||||
#define MAX(A, B) ((A) > (B) ? (A) : (B))
|
|
||||||
|
|
||||||
#ifndef X11_OPTIMIZED_SCREEN_UPDATE
|
|
||||||
#define X11_OPTIMIZED_SCREEN_UPDATE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* TYPEDEFS
|
|
||||||
**********************/
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* STATIC VARIABLES
|
|
||||||
**********************/
|
|
||||||
static Display* display = NULL;
|
|
||||||
static Window window = (XID)-1;
|
|
||||||
static GC gc = NULL;
|
|
||||||
static XImage* ximage = NULL;
|
|
||||||
static lv_timer_t* timer = NULL;
|
|
||||||
|
|
||||||
static char kb_buffer[KEYBOARD_BUFFER_SIZE];
|
|
||||||
static lv_point_t mouse_pos = { 0, 0 };
|
|
||||||
static bool left_mouse_btn = false;
|
|
||||||
static bool right_mouse_btn = false;
|
|
||||||
static bool wheel_mouse_btn = false;
|
|
||||||
static int16_t wheel_cnt = 0;
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* MACROS
|
|
||||||
**********************/
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* STATIC FUNCTIONS
|
|
||||||
**********************/
|
|
||||||
static int predicate(Display* disp, XEvent* evt, XPointer arg) { return 1; }
|
|
||||||
|
|
||||||
static void x11_event_handler(lv_timer_t * t)
|
|
||||||
{
|
|
||||||
XEvent myevent;
|
|
||||||
KeySym mykey;
|
|
||||||
int n;
|
|
||||||
|
|
||||||
/* handle all outstanding X events */
|
|
||||||
while (XCheckIfEvent(display, &myevent, predicate, NULL)) {
|
|
||||||
switch(myevent.type)
|
|
||||||
{
|
|
||||||
case Expose:
|
|
||||||
if(myevent.xexpose.count==0)
|
|
||||||
{
|
|
||||||
XPutImage(display, window, gc, ximage, 0, 0, 0, 0, LV_HOR_RES, LV_VER_RES);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case MotionNotify:
|
|
||||||
mouse_pos.x = myevent.xmotion.x;
|
|
||||||
mouse_pos.y = myevent.xmotion.y;
|
|
||||||
break;
|
|
||||||
case ButtonPress:
|
|
||||||
switch (myevent.xbutton.button)
|
|
||||||
{
|
|
||||||
case Button1:
|
|
||||||
left_mouse_btn = true;
|
|
||||||
break;
|
|
||||||
case Button2:
|
|
||||||
wheel_mouse_btn = true;
|
|
||||||
break;
|
|
||||||
case Button3:
|
|
||||||
right_mouse_btn = true;
|
|
||||||
break;
|
|
||||||
case Button4:
|
|
||||||
wheel_cnt--; // Scrolled up
|
|
||||||
break;
|
|
||||||
case Button5:
|
|
||||||
wheel_cnt++; // Scrolled down
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LV_LOG_WARN("unhandled button press : %d", myevent.xbutton.button);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ButtonRelease:
|
|
||||||
switch (myevent.xbutton.button)
|
|
||||||
{
|
|
||||||
case Button1:
|
|
||||||
left_mouse_btn = false;
|
|
||||||
break;
|
|
||||||
case Button2:
|
|
||||||
wheel_mouse_btn = false;
|
|
||||||
break;
|
|
||||||
case Button3:
|
|
||||||
right_mouse_btn = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case KeyPress:
|
|
||||||
n = XLookupString(&myevent.xkey, &kb_buffer[0], sizeof(kb_buffer), &mykey, NULL);
|
|
||||||
kb_buffer[n] = '\0';
|
|
||||||
break;
|
|
||||||
case KeyRelease:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LV_LOG_WARN("unhandled x11 event: %d", myevent.type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void lv_x11_hide_cursor()
|
|
||||||
{
|
|
||||||
XColor black = { .red = 0, .green = 0, .blue = 0 };
|
|
||||||
char empty_data[] = { 0 };
|
|
||||||
|
|
||||||
Pixmap empty_bitmap = XCreateBitmapFromData(display, window, empty_data, 1, 1);
|
|
||||||
Cursor inv_cursor = XCreatePixmapCursor(display, empty_bitmap, empty_bitmap, &black, &black, 0, 0);
|
|
||||||
XDefineCursor(display, window, inv_cursor);
|
|
||||||
XFreeCursor(display, inv_cursor);
|
|
||||||
XFreePixmap(display, empty_bitmap);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* GLOBAL FUNCTIONS
|
|
||||||
**********************/
|
|
||||||
|
|
||||||
void lv_x11_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
|
|
||||||
{
|
|
||||||
#if X11_OPTIMIZED_SCREEN_UPDATE
|
|
||||||
static lv_area_t upd_area = {
|
|
||||||
.x1 = 0xFFFF,
|
|
||||||
.x2 = 0,
|
|
||||||
.y1 = 0xFFFF,
|
|
||||||
.y2 = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/* build display update area until lv_disp_flush_is_last */
|
|
||||||
upd_area.x1 = MIN(upd_area.x1, area->x1);
|
|
||||||
upd_area.x2 = MAX(upd_area.x2, area->x2);
|
|
||||||
upd_area.y1 = MIN(upd_area.y1, area->y1);
|
|
||||||
upd_area.y2 = MAX(upd_area.y2, area->y2);
|
|
||||||
#endif // X11_OPTIMIZED_SCREEN_UPDATE
|
|
||||||
|
|
||||||
for (lv_coord_t y = area->y1; y <= area->y2; y++)
|
|
||||||
{
|
|
||||||
uint32_t dst_offs = area->x1 + y * LV_HOR_RES;
|
|
||||||
uint32_t* dst_data = &((uint32_t*)ximage->data)[dst_offs];
|
|
||||||
for (lv_coord_t x = area->x1; x <= area->x2; x++, color_p++, dst_data++)
|
|
||||||
{
|
|
||||||
*dst_data = lv_color_to32(*color_p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lv_disp_flush_is_last(disp_drv))
|
|
||||||
{
|
|
||||||
#if X11_OPTIMIZED_SCREEN_UPDATE
|
|
||||||
/* refresh collected display update area only */
|
|
||||||
lv_coord_t upd_w = upd_area.x2 - upd_area.x1 + 1;
|
|
||||||
lv_coord_t upd_h = upd_area.y2 - upd_area.y1 + 1;
|
|
||||||
XPutImage(display, window, gc, ximage, upd_area.x1, upd_area.y1, upd_area.x1, upd_area.y1, upd_w, upd_h);
|
|
||||||
/* invalidate collected area */
|
|
||||||
upd_area.x1 = 0xFFFF;
|
|
||||||
upd_area.x2 = 0;
|
|
||||||
upd_area.y1 = 0xFFFF;
|
|
||||||
upd_area.y2 = 0;
|
|
||||||
#else
|
|
||||||
/* refresh full display */
|
|
||||||
XPutImage(display, window, gc, ximage, 0, 0, 0, 0, LV_HOR_RES, LV_VER_RES);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
lv_disp_flush_ready(disp_drv);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lv_x11_get_pointer(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
|
|
||||||
{
|
|
||||||
(void) indev_drv; // Unused
|
|
||||||
|
|
||||||
data->point = mouse_pos;
|
|
||||||
data->state = left_mouse_btn ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void lv_x11_get_mousewheel(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
|
|
||||||
{
|
|
||||||
(void) indev_drv; // Unused
|
|
||||||
|
|
||||||
data->state = wheel_mouse_btn ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
|
||||||
data->enc_diff = wheel_cnt;
|
|
||||||
wheel_cnt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void lv_x11_get_keyboard(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
|
|
||||||
{
|
|
||||||
(void) indev_drv; // Unused
|
|
||||||
|
|
||||||
size_t len = strlen(kb_buffer);
|
|
||||||
if (len > 0)
|
|
||||||
{
|
|
||||||
data->state = LV_INDEV_STATE_PRESSED;
|
|
||||||
data->key = kb_buffer[0];
|
|
||||||
memmove(kb_buffer, kb_buffer + 1, len);
|
|
||||||
data->continue_reading = (len > 0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
data->state = LV_INDEV_STATE_RELEASED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void lv_x11_init(char const* title, lv_coord_t width, lv_coord_t height)
|
|
||||||
{
|
|
||||||
/* setup display/screen */
|
|
||||||
display = XOpenDisplay(NULL);
|
|
||||||
int screen = DefaultScreen(display);
|
|
||||||
|
|
||||||
/* drawing contexts for an window */
|
|
||||||
unsigned long myforeground = BlackPixel(display, screen);
|
|
||||||
unsigned long mybackground = WhitePixel(display, screen);
|
|
||||||
|
|
||||||
/* create window */
|
|
||||||
window = XCreateSimpleWindow(display, DefaultRootWindow(display),
|
|
||||||
0, 0, width, height,
|
|
||||||
0, myforeground, mybackground);
|
|
||||||
|
|
||||||
/* window manager properties (yes, use of StdProp is obsolete) */
|
|
||||||
XSetStandardProperties(display, window, title, NULL, None, NULL, 0, NULL);
|
|
||||||
|
|
||||||
/* allow receiving mouse and keyboard events */
|
|
||||||
XSelectInput(display, window, PointerMotionMask|ButtonPressMask|ButtonReleaseMask|KeyPressMask|KeyReleaseMask|ExposureMask);
|
|
||||||
|
|
||||||
/* graphics context */
|
|
||||||
gc = XCreateGC(display, window, 0, 0);
|
|
||||||
|
|
||||||
lv_x11_hide_cursor();
|
|
||||||
|
|
||||||
/* create cache XImage */
|
|
||||||
Visual* visual = XDefaultVisual(display, screen);
|
|
||||||
int dplanes = DisplayPlanes(display, screen);
|
|
||||||
ximage = XCreateImage(display, visual, dplanes, ZPixmap, 0,
|
|
||||||
malloc(width * height * sizeof(uint32_t)), width, height, 32, 0);
|
|
||||||
|
|
||||||
timer = lv_timer_create(x11_event_handler, 10, NULL);
|
|
||||||
|
|
||||||
/* finally bring window on top of the other windows */
|
|
||||||
XMapRaised(display, window);
|
|
||||||
}
|
|
||||||
|
|
||||||
void lv_x11_deinit(void)
|
|
||||||
{
|
|
||||||
lv_timer_del(timer);
|
|
||||||
|
|
||||||
free(ximage->data);
|
|
||||||
|
|
||||||
XDestroyImage(ximage);
|
|
||||||
ximage = NULL;
|
|
||||||
|
|
||||||
XFreeGC(display, gc);
|
|
||||||
gc = NULL;
|
|
||||||
XDestroyWindow(display, window);
|
|
||||||
window = (XID)-1;
|
|
||||||
|
|
||||||
XCloseDisplay(display);
|
|
||||||
display = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // USE_X11
|
|
@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file x11.h
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef X11_H
|
|
||||||
#define X11_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*********************
|
|
||||||
* INCLUDES
|
|
||||||
*********************/
|
|
||||||
#ifndef LV_DRV_NO_CONF
|
|
||||||
#ifdef LV_CONF_INCLUDE_SIMPLE
|
|
||||||
#include "lv_drv_conf.h"
|
|
||||||
#else
|
|
||||||
#include "../../lv_drv_conf.h"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if USE_X11
|
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
|
||||||
#include "lvgl.h"
|
|
||||||
#else
|
|
||||||
#include "lvgl/lvgl.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*********************
|
|
||||||
* DEFINES
|
|
||||||
*********************/
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* TYPEDEFS
|
|
||||||
**********************/
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* GLOBAL PROTOTYPES
|
|
||||||
**********************/
|
|
||||||
void lv_x11_init(char const* title, lv_coord_t width, lv_coord_t height);
|
|
||||||
void lv_x11_deinit(void);
|
|
||||||
void lv_x11_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
|
|
||||||
void lv_x11_get_pointer(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
|
|
||||||
void lv_x11_get_mousewheel(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
|
|
||||||
void lv_x11_get_keyboard(lv_indev_drv_t *indev_drv, lv_indev_data_t *data);
|
|
||||||
|
|
||||||
/**********************
|
|
||||||
* MACROS
|
|
||||||
**********************/
|
|
||||||
|
|
||||||
#endif /* USE_X11 */
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
} /* extern "C" */
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* X11_H */
|
|
@ -1,23 +1,23 @@
|
|||||||
name: Push LVGL release to Espressif Component Service
|
name: Push LVGL release to Espressif Component Service
|
||||||
|
|
||||||
# If the commit is tagged, it will be uploaded. Other scenario silently fail.
|
# If the commit is tagged, it will be uploaded. Other scenario silently fail.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches:
|
||||||
- v*
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload_components:
|
upload_components:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@master
|
||||||
with:
|
with:
|
||||||
submodules: "recursive"
|
submodules: "recursive"
|
||||||
|
|
||||||
- name: Upload component to component registry
|
- name: Upload component to component registry
|
||||||
uses: espressif/upload-components-ci-action@v1
|
uses: espressif/github-actions/upload_components@master
|
||||||
with:
|
with:
|
||||||
name: "lvgl"
|
name: "lvgl"
|
||||||
version: ${{ github.ref_name }}
|
version: "git"
|
||||||
namespace: "lvgl"
|
namespace: "lvgl"
|
||||||
api_token: ${{ secrets.ESP_IDF_COMPONENT_API_TOKEN }}
|
api_token: ${{ secrets.ESP_IDF_COMPONENT_API_TOKEN }}
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
# Tiny TTF font engine
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Use https://github.com/nothings/stb to render TrueType fonts in LVGL.
|
|
||||||
|
|
||||||
When enabled in `lv_conf.h` with `LV_USE_TINY_TTF`
|
|
||||||
`lv_tiny_ttf_create_data(data, data_size, font_size)` can be used to
|
|
||||||
create a TTF font instance at the specified font size. You can then
|
|
||||||
use that font anywhere `lv_font_t` is accepted.
|
|
||||||
|
|
||||||
By default, the TTF or OTF file must be embedded as an array, either in
|
|
||||||
a header, or loaded into RAM in order to function.
|
|
||||||
|
|
||||||
However, if `LV_TINY_TTF_FILE_SUPPORT` is enabled,
|
|
||||||
`lv_tiny_ttf_create_file(path, font_size)` will also be available,
|
|
||||||
allowing tiny_ttf to stream from a file. The file must remain open the
|
|
||||||
entire time the font is being used, and streaming on demand may be
|
|
||||||
considerably slower.
|
|
||||||
|
|
||||||
After a font is created, you can change the font size in pixels by using
|
|
||||||
`lv_tiny_ttf_set_size(font, font_size)`.
|
|
||||||
|
|
||||||
By default, a font will use up to 4KB of cache to speed up rendering
|
|
||||||
glyphs. This maximum can be changed by using
|
|
||||||
`lv_tiny_ttf_create_data_ex(data, data_size, font_size, cache_size)`
|
|
||||||
or `lv_tiny_ttf_create_file_ex(path, font_size, cache_size)` (when
|
|
||||||
available). The cache size is indicated in bytes.
|
|
||||||
|
|
||||||
## API
|
|
||||||
|
|
||||||
```eval_rst
|
|
||||||
.. doxygenfile:: lv_tiny_ttf.h
|
|
||||||
:project: lvgl
|
|
||||||
```
|
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue