diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 0a562216a..a80f6db06 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -23,7 +23,7 @@ jobs:
- name: Install Dependencies
run: |
apt update
- apt install -y meson libadwaita-1-dev libfwupd-dev libgranite-7-dev libgtk-4-dev libgtop2-dev libgudev-1.0-dev libudisks2-dev libswitchboard-3-dev libappstream-dev libpackagekit-glib2-dev libpolkit-gobject-1-dev libsoup-3.0-dev valac
+ apt install -y meson libadwaita-1-dev libfwupd-dev libgranite-7-dev libgtk-4-dev libgtop2-dev libgudev-1.0-dev libudisks2-dev libswitchboard-3-dev libappstream-dev libpackagekit-glib2-dev libpolkit-gobject-1-dev libsoup-3.0-dev libsystemd-dev valac
- name: Build
env:
DESTDIR: out
diff --git a/data/gresource.xml b/data/gresource.xml
index ddaa79c17..e1c556eb8 100644
--- a/data/gresource.xml
+++ b/data/gresource.xml
@@ -3,4 +3,8 @@
OperatingSystemView.css
+
+
+ system-logs-symbolic.svg
+
diff --git a/data/system-logs-symbolic.svg b/data/system-logs-symbolic.svg
new file mode 100644
index 000000000..3cff47aea
--- /dev/null
+++ b/data/system-logs-symbolic.svg
@@ -0,0 +1,41 @@
+
+
diff --git a/src/LogDialog/LogCell.vala b/src/LogDialog/LogCell.vala
new file mode 100644
index 000000000..8e7a2106d
--- /dev/null
+++ b/src/LogDialog/LogCell.vala
@@ -0,0 +1,45 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
+ */
+
+public class About.LogCell : Granite.Bin {
+ public enum CellType {
+ ORIGIN,
+ MESSAGE
+ }
+
+ public CellType cell_type { get; construct; }
+
+ private Gtk.Label label;
+
+ public LogCell (CellType cell_type) {
+ Object (cell_type: cell_type);
+ }
+
+ construct {
+ label = new Gtk.Label (null) {
+ ellipsize = END,
+ single_line_mode = true,
+ halign = START,
+ };
+
+ if (cell_type == ORIGIN) {
+ label.max_width_chars = 10;
+ label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
+ }
+
+ child = label;
+ }
+
+ public void bind (SystemdLogEntry entry) {
+ switch (cell_type) {
+ case ORIGIN:
+ label.label = entry.origin;
+ break;
+ case MESSAGE:
+ label.label = entry.message;
+ break;
+ }
+ }
+}
diff --git a/src/LogDialog/LogDetailsView.vala b/src/LogDialog/LogDetailsView.vala
new file mode 100644
index 000000000..5e8391b6b
--- /dev/null
+++ b/src/LogDialog/LogDetailsView.vala
@@ -0,0 +1,85 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
+ */
+
+public class About.LogDetailsView : Adw.NavigationPage {
+ public SystemdLogEntry entry {
+ set {
+ origin.label = value.origin;
+ timestamp.label = value.dt.format ("%s %s".printf (
+ Granite.DateTime.get_default_date_format (),
+ Granite.DateTime.get_default_time_format (false, true)
+ ));
+ message.label = value.message;
+ title = value.origin;
+ }
+ }
+
+ private Gtk.Label origin;
+ private Gtk.Label timestamp;
+ private Gtk.Label message;
+
+ construct {
+ var origin_label = new Gtk.Label (_("Sender:")) {
+ halign = END
+ };
+ origin_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
+
+ origin = new Gtk.Label (null) {
+ halign = START,
+ wrap = true,
+ wrap_mode = WORD_CHAR
+ };
+
+ var timestamp_label = new Gtk.Label (_("Timestamp:")) {
+ halign = END
+ };
+ timestamp_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
+
+ timestamp = new Gtk.Label (null) {
+ halign = START,
+ wrap = true,
+ wrap_mode = WORD_CHAR
+ };
+
+ var message_label = new Gtk.Label (_("Message:")) {
+ halign = END
+ };
+ message_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
+
+ message = new Gtk.Label (null) {
+ halign = START,
+ wrap = true,
+ wrap_mode = WORD_CHAR
+ };
+
+ var grid = new Gtk.Grid () {
+ column_spacing = 3,
+ row_spacing = 6,
+ margin_start = 6,
+ margin_end = 6,
+ margin_top = 6,
+ margin_bottom = 6,
+ halign = CENTER
+ };
+ grid.attach (origin_label, 0, 0);
+ grid.attach (origin, 1, 0);
+ grid.attach (timestamp_label, 0, 1);
+ grid.attach (timestamp, 1, 1);
+ grid.attach (message_label, 0, 2);
+ grid.attach (message, 1, 2);
+
+ var scrolled = new Gtk.ScrolledWindow () {
+ child = grid
+ };
+
+ var toolbar_view = new Adw.ToolbarView () {
+ content = scrolled,
+ top_bar_style = RAISED
+ };
+ toolbar_view.add_top_bar (new Adw.HeaderBar ());
+
+ child = toolbar_view;
+ }
+}
diff --git a/src/LogDialog/LogDialog.vala b/src/LogDialog/LogDialog.vala
new file mode 100644
index 000000000..2edcb6d9a
--- /dev/null
+++ b/src/LogDialog/LogDialog.vala
@@ -0,0 +1,147 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
+ */
+
+public class About.LogDialog : Granite.Dialog {
+ private SystemdLogModel model;
+ private LogDetailsView details_view;
+ private Adw.NavigationView navigation_view;
+
+ construct {
+ title = _("System Logs");
+ modal = true;
+ default_height = 500;
+ default_width = 500;
+
+ var title_label = new Gtk.Label (
+ _("System Logs")
+ ) {
+ hexpand = true,
+ xalign = 0
+ };
+ title_label.add_css_class (Granite.STYLE_CLASS_TITLE_LABEL);
+
+ var refresh_button = new Gtk.Button.from_icon_name ("view-refresh-symbolic") {
+ tooltip_text = _("Load new entries")
+ };
+
+ var top_box = new Gtk.Box (HORIZONTAL, 6);
+ top_box.append (title_label);
+ top_box.append (refresh_button);
+
+ var search_entry = new Gtk.SearchEntry ();
+
+ model = new SystemdLogModel ();
+
+ var selection_model = new Gtk.NoSelection (model);
+
+ var header_factory = new Gtk.SignalListItemFactory ();
+ header_factory.setup.connect (setup_header);
+ header_factory.bind.connect (bind_header);
+
+ var origin_factory = new Gtk.SignalListItemFactory ();
+ origin_factory.setup.connect (setup_origin);
+ origin_factory.bind.connect (bind);
+
+ var origin_column = new Gtk.ColumnViewColumn (_("Sender"), origin_factory);
+
+ var message_factory = new Gtk.SignalListItemFactory ();
+ message_factory.setup.connect (setup_message);
+ message_factory.bind.connect (bind);
+
+ var message_column = new Gtk.ColumnViewColumn (_("Message"), message_factory) {
+ expand = true
+ };
+
+ var column_view = new Gtk.ColumnView (selection_model) {
+ header_factory = header_factory,
+ single_click_activate = true,
+ };
+ column_view.append_column (origin_column);
+ column_view.append_column (message_column);
+
+ var scrolled = new Gtk.ScrolledWindow () {
+ child = column_view,
+ hscrollbar_policy = NEVER,
+ max_content_height = 400,
+ propagate_natural_height = true
+ };
+
+ var list_page = new Adw.NavigationPage (scrolled, "list");
+
+ details_view = new LogDetailsView ();
+
+ navigation_view = new Adw.NavigationView ();
+ navigation_view.add (list_page);
+
+ var frame = new Gtk.Frame (null) {
+ child = navigation_view,
+ hexpand = true,
+ vexpand = true
+ };
+
+ var box = new Gtk.Box (VERTICAL, 12);
+ box.append (top_box);
+ box.append (search_entry);
+ box.append (frame);
+
+ get_content_area ().append (box);
+
+ add_button (_("Close"), Gtk.ResponseType.CLOSE);
+
+ refresh_button.clicked.connect (model.refresh);
+ search_entry.search_changed.connect (on_search_changed);
+ column_view.activate.connect (on_activate);
+ scrolled.edge_reached.connect (on_edge_reached);
+
+ response.connect (() => close ());
+ }
+
+ private void setup_header (Object obj) {
+ var item = (Gtk.ListHeader) obj;
+ item.child = new Gtk.Label (null) {
+ halign = START,
+ use_markup = true
+ };
+ }
+
+ private void bind_header (Object obj) {
+ var item = (Gtk.ListHeader) obj;
+ var entry = (SystemdLogEntry) item.item;
+ var label = (Gtk.Label) item.child;
+ label.label = "%s".printf (entry.relative_time);
+ }
+
+ private void setup_origin (Object obj) {
+ var item = (Gtk.ListItem) obj;
+ item.child = new LogCell (ORIGIN);
+ }
+
+ private void setup_message (Object obj) {
+ var item = (Gtk.ListItem) obj;
+ item.child = new LogCell (MESSAGE);
+ }
+
+ private void bind (Object obj) {
+ var item = (Gtk.ListItem) obj;
+ var entry = (SystemdLogEntry) item.item;
+ var cell = (LogCell) item.child;
+ cell.bind (entry);
+ }
+
+ private void on_search_changed (Gtk.SearchEntry entry) {
+ model.search (entry.text);
+ }
+
+ private void on_activate (uint pos) {
+ details_view.entry = (SystemdLogEntry) model.get_item (pos);
+ navigation_view.push (details_view);
+ }
+
+ private void on_edge_reached (Gtk.PositionType pos) {
+ if (pos == BOTTOM) {
+ model.load_chunk ();
+ }
+ }
+}
diff --git a/src/LogDialog/SystemdLogEntry.vala b/src/LogDialog/SystemdLogEntry.vala
new file mode 100644
index 000000000..8f47cad72
--- /dev/null
+++ b/src/LogDialog/SystemdLogEntry.vala
@@ -0,0 +1,46 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
+ */
+
+ public class About.SystemdLogEntry : GLib.Object {
+ public string origin { get; construct; }
+ public string message { get; construct; }
+ public DateTime dt { get; construct; }
+ public string relative_time { get; construct; }
+
+ public uint section_start { get; set; }
+
+ public SystemdLogEntry (string origin, string message, DateTime time) {
+ Object (
+ origin: origin, message: message, dt: time,
+ relative_time: format_time (time)
+ );
+ }
+
+ public bool matches (string term) {
+ return origin.contains (term) || message.contains (term);
+ }
+
+ private static string format_time (DateTime time) {
+ var diff = SystemdLogModel.get_stable_now ().difference (time);
+ if (diff < TimeSpan.SECOND) {
+ return _("Now");
+ } else if (diff < TimeSpan.MINUTE) {
+ var seconds = diff / TimeSpan.SECOND;
+ return dngettext (GETTEXT_PACKAGE, "%ds ago", "%ds ago", (ulong) seconds).printf ((int) seconds);
+ } else if (diff < TimeSpan.HOUR) {
+ var minutes = diff / TimeSpan.MINUTE;
+ var seconds = (diff - minutes * TimeSpan.MINUTE) / TimeSpan.SECOND;
+
+ if (seconds == 0) {
+ return dngettext (GETTEXT_PACKAGE, "%dm ago", "%dm ago", (ulong) minutes).printf ((int) minutes);
+ }
+
+ // I think the plural form is according to the last one??
+ return dngettext (GETTEXT_PACKAGE, "%dm %ds ago", "%dm %ds ago", (ulong) seconds).printf ((int) minutes, (int) seconds);
+ }
+
+ return time.format (Granite.DateTime.get_default_time_format ());
+ }
+}
diff --git a/src/LogDialog/SystemdLogModel.vala b/src/LogDialog/SystemdLogModel.vala
new file mode 100644
index 000000000..d652ecf9f
--- /dev/null
+++ b/src/LogDialog/SystemdLogModel.vala
@@ -0,0 +1,240 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
+ */
+
+public class About.SystemdLogModel : GLib.Object, GLib.ListModel, Gtk.SectionModel {
+ private static DateTime? now;
+
+ public static DateTime get_stable_now () {
+ return now;
+ }
+
+ private const int CHUNK_SIZE = 200;
+ private const int64 CHUNK_TIME = 1000; // 1 millisecond
+
+ private Systemd.Journal journal;
+ private Systemd.Id128 current_boot_id;
+ private uint64 current_tail_time = 0;
+ private string current_search_term = "";
+
+ // These fields are for the listmodel and section model implementation
+ // and only used for loading and in the implementations
+ private Gee.ArrayList entries;
+ private bool eof = false;
+ private bool loading = false;
+ private int current_section_start = 0;
+ private DateTime? current_section_time;
+ private HashTable section_end_for_start = new HashTable (null, null);
+
+ construct {
+ entries = new Gee.ArrayList ();
+
+ int res = Systemd.Journal.open_namespace (out journal, null, LOCAL_ONLY);
+ if (res != 0) {
+ critical ("%s", strerror (-res));
+ return;
+ }
+
+ res = Systemd.Id128.boot (out current_boot_id);
+ if (res != 0) {
+ critical ("%s", strerror (-res));
+ return;
+ }
+
+ init ();
+ }
+
+ private void reset () {
+ if (!entries.is_empty) {
+ var removed = entries.size;
+ entries.clear ();
+ items_changed (0, removed, 0);
+ }
+
+ eof = false;
+ loading = false; // Cancels ongoing load
+ current_section_start = 0;
+ current_section_time = null;
+ section_end_for_start.remove_all ();
+
+ journal.flush_matches ();
+ }
+
+ private void init () {
+ now = new DateTime.now_utc ();
+
+ //TODO: Add exact matches, allow to filter by boot
+ journal.add_match ("_BOOT_ID=%s".printf (current_boot_id.str).data);
+ journal.add_conjunction ();
+
+ if (current_tail_time == 0) {
+ journal.seek_tail ();
+ journal.previous ();
+ int res = journal.get_realtime_usec (out current_tail_time);
+ if (res != 0) {
+ critical ("Failed to get tail realtime: %s", strerror (-res));
+ return;
+ }
+ } else {
+ journal.seek_realtime_usec (current_tail_time);
+ journal.previous ();
+ }
+
+ load_chunk ();
+ }
+
+ public void load_chunk () {
+ if (eof || loading) {
+ return;
+ }
+
+ loading = true;
+
+ var start_items = get_n_items ();
+
+ Idle.add (() => {
+ if (!loading) { // We were cancelled
+ return Source.REMOVE;
+ }
+
+ load_timed ();
+ loading = !eof && get_n_items () - start_items < CHUNK_SIZE;
+ return loading ? Source.CONTINUE : Source.REMOVE;
+ });
+ }
+
+ private void load_timed () {
+ if (eof) {
+ return;
+ }
+
+ var start_n_items = entries.size;
+ var start_time = get_monotonic_time ();
+
+ while (get_monotonic_time () - start_time < CHUNK_TIME) {
+ if (!load_next_entry ()) {
+ eof = true;
+ break;
+ }
+ }
+
+ items_changed (start_n_items, 0, entries.size - start_n_items);
+ }
+
+ private bool load_next_entry () {
+ int res = journal.previous ();
+ if (res == 0) {
+ return false;
+ }
+
+ if (res < 0) {
+ critical ("Failed to go to next aka previous entry: %s", strerror (-res));
+ return false;
+ }
+
+ unowned uint8[] data;
+ unowned uint8[] comm_data;
+ res = journal.get_data ("MESSAGE", out data);
+ if (res != 0) {
+ critical ("Failed to get message: %s", strerror (-res));
+ return true; // Don't eof just skip it
+ }
+
+ res = journal.get_data ("_COMM", out comm_data);
+ if (res != 0) {
+ comm_data = "_COMM=kernel".data;
+ }
+
+ var origin = ((string) comm_data).offset ("_COMM=".length);
+ var message = ((string) data).offset ("MESSAGE=".length);
+
+ uint64 time;
+ res = journal.get_realtime_usec (out time);
+ if (res != 0) {
+ critical ("Failed to get time: %s", strerror (-res));
+ time = 0;
+ }
+
+ var dt = new DateTime.from_unix_utc ((int64) (time / TimeSpan.SECOND));
+
+ var entry = new SystemdLogEntry (origin, message, dt);
+
+ // Filter if we're searching. We drop them and don't add them and use a filter model
+ // because when searching for e.g. a non existent term this would fill up memory *quick*
+ if (current_search_term.strip () != "" && !entry.matches (current_search_term)) {
+ return true;
+ }
+
+ // Update sections (group entries that have a timestamp from the same second)
+ if (!update_current_range (dt)) {
+ section_end_for_start[current_section_start] = entries.size;
+ current_section_start = entries.size;
+ }
+ entry.section_start = current_section_start;
+
+ entries.add (entry);
+
+ return true;
+ }
+
+ private bool update_current_range (DateTime dt) {
+ if (current_section_time == null) {
+ current_section_time = dt;
+ return true;
+ }
+
+ if (current_section_time.difference (dt) <= TimeSpan.SECOND) {
+ return true;
+ } else {
+ current_section_time = dt;
+ return false;
+ }
+ }
+
+ public void search (string term) {
+ reset ();
+ //TODO: tokenize etc.
+ current_search_term = term;
+ init ();
+ }
+
+ public void refresh () {
+ reset ();
+ current_tail_time = 0;
+ init ();
+ }
+
+ public Object? get_item (uint position) {
+ if (position >= entries.size) {
+ return null;
+ } else {
+ return entries[(int) position];
+ }
+ }
+
+ public Type get_item_type () {
+ return typeof (SystemdLogEntry);
+ }
+
+ public uint get_n_items () {
+ return entries.size;
+ }
+
+ public void get_section (uint for_position, out uint section_start, out uint section_end) {
+ if (for_position >= entries.size) {
+ // Documentation mandates this
+ section_start = entries.size;
+ section_end = uint.MAX;
+ return;
+ }
+
+ section_start = entries[(int) for_position].section_start;
+
+ if (section_start in section_end_for_start) {
+ section_end = section_end_for_start[section_start];
+ } else {
+ section_end = entries.size;
+ }
+ }
+}
diff --git a/src/Plug.vala b/src/Plug.vala
index 346f3fba2..ea16f2b23 100644
--- a/src/Plug.vala
+++ b/src/Plug.vala
@@ -52,6 +52,7 @@ public class About.Plug : Switchboard.Plug {
public override Gtk.Widget get_widget () {
if (toolbarview == null) {
+ Gtk.IconTheme.get_for_display (Gdk.Display.get_default ()).add_resource_path ("/io/elementary/settings/system/icons");
operating_system_view = new OperatingSystemView ();
var hardware_view = new HardwareView ();
diff --git a/src/Views/OperatingSystemView.vala b/src/Views/OperatingSystemView.vala
index 6a9f62b36..b427ed039 100644
--- a/src/Views/OperatingSystemView.vala
+++ b/src/Views/OperatingSystemView.vala
@@ -174,6 +174,13 @@ public class About.OperatingSystemView : Gtk.Box {
kernel_version_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL);
kernel_version_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
+ var log_button = new Gtk.Button.from_icon_name ("system-logs-symbolic") {
+ tooltip_text = _("System logs"),
+ halign = END,
+ valign = BASELINE_CENTER,
+ hexpand = false
+ };
+
packages = new Gtk.StringList (null);
updates_image = new Gtk.Image () {
@@ -350,11 +357,12 @@ public class About.OperatingSystemView : Gtk.Box {
};
software_grid.attach (logo_overlay, 0, 0, 1, 4);
software_grid.attach (title, 1, 0);
+ software_grid.attach (log_button, 2, 0);
- software_grid.attach (kernel_version_label, 1, 2);
- software_grid.attach (updates_list, 1, 3);
- software_grid.attach (sponsor_list, 1, 4);
- software_grid.attach (links_list, 1, 5);
+ software_grid.attach (kernel_version_label, 1, 2, 2);
+ software_grid.attach (updates_list, 1, 3, 2);
+ software_grid.attach (sponsor_list, 1, 4, 2);
+ software_grid.attach (links_list, 1, 5, 2);
var clamp = new Adw.Clamp () {
child = software_grid,
@@ -396,6 +404,12 @@ public class About.OperatingSystemView : Gtk.Box {
launch_uri (((SponsorUsRow) row).uri);
});
+ log_button.clicked.connect (() => {
+ new LogDialog () {
+ transient_for = (Gtk.Window) get_root ()
+ }.present ();
+ });
+
links_list.row_activated.connect ((row) => {
launch_uri (((LinkRow) row).uri);
});
diff --git a/src/meson.build b/src/meson.build
index 57bd2a4a8..ebc457450 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -4,6 +4,11 @@ plug_files = files(
'DBus' / 'Drivers.vala',
'Interfaces/FirmwareClient.vala',
'Interfaces/LoginManager.vala',
+ 'LogDialog' / 'LogCell.vala',
+ 'LogDialog' / 'LogDetailsView.vala',
+ 'LogDialog' / 'LogDialog.vala',
+ 'LogDialog' / 'SystemdLogEntry.vala',
+ 'LogDialog' / 'SystemdLogModel.vala',
'Utils/ARMPartDecoder.vala',
'Views' / 'DriversView.vala',
'Views/FirmwareReleaseView.vala',
@@ -12,7 +17,7 @@ plug_files = files(
'Views/OperatingSystemView.vala',
'Widgets/FirmwareUpdateRow.vala',
'Widgets' / 'DriverRow.vala',
- 'Widgets' / 'UpdateDetailsDialog.vala'
+ 'Widgets' / 'UpdateDetailsDialog.vala',
)
switchboard_dep = dependency('switchboard-3')
@@ -42,6 +47,7 @@ shared_module(
dependency('libadwaita-1'),
dependency('libgtop-2.0'),
dependency('libsoup-3.0'),
+ dependency('libsystemd'),
dependency('packagekit-glib2'),
dependency('gudev-1.0'),
dependency('udisks2'),
diff --git a/vapi/libsystemd.vapi b/vapi/libsystemd.vapi
new file mode 100644
index 000000000..35e2fabfe
--- /dev/null
+++ b/vapi/libsystemd.vapi
@@ -0,0 +1,128 @@
+[CCode (lower_case_cprefix = "sd_")]
+namespace Systemd {
+ [Compact, CCode (cname = "sd_journal", cheader_filename = "systemd/sd-journal.h", free_function = "sd_journal_close")]
+ public class Journal {
+ [CCode (cname = "int", cprefix = "LOG_", lower_case_cprefix = "sd_journal_", cheader_filename = "systemd/sd-journal.h,syslog.h", has_type_id = false)]
+ public enum Priority {
+ EMERG,
+ ALERT,
+ CRIT,
+ ERR,
+ WARNING,
+ NOTICE,
+ INFO,
+ DEBUG;
+
+ [PrintfFormat]
+ public int print (string format, ...);
+ public int printv (string format, va_list ap);
+
+ [CCode (instance_pos = 1.5)]
+ public int stream_fd (string identifier, bool level_prefix);
+ [CCode (cname = "_vala_sd_journal_stream")]
+ public GLib.FileStream? stream (string identifier, bool level_prefix) {
+ int fd = this.stream_fd (identifier, level_prefix);
+ return (fd < 0) ? null : GLib.FileStream.fdopen (fd, "w");
+ }
+ }
+
+ public static int send (string format, ...);
+ public static int sendv (Posix.iovector[] iov);
+ public static int perror (string message);
+
+ [Flags, CCode (cname = "int", cprefix = "SD_JOURNAL_", has_type_id = false)]
+ public enum OpenFlags {
+ LOCAL_ONLY,
+ RUNTIME_ONLY,
+ SYSTEM,
+ CURRENT_USER,
+ OS_ROOT,
+ ALL_NAMESPACES,
+ INCLUDE_DEFAULT_NAMESPACE,
+ TAKE_DIRECTORY_FD,
+ ASSUME_IMMUTABLE
+ }
+ public static int open (out Systemd.Journal ret, Systemd.Journal.OpenFlags flags);
+ public static int open_namespace (out Systemd.Journal ret, string? name_space, Systemd.Journal.OpenFlags flags);
+ public static int open_directory (out Systemd.Journal ret, string path, Systemd.Journal.OpenFlags flags);
+ public static int open_files (out Systemd.Journal ret, [CCode (array_length = false, array_null_terminated = true)] string[] paths, Systemd.Journal.OpenFlags flags);
+
+ public int previous ();
+ public int next ();
+
+ public int previous_skip (uint64 skip);
+ public int next_skip (uint64 skip);
+
+ public int get_realtime_usec (out uint64 ret);
+ public int get_monotonic_usec (out uint64 ret, out Systemd.Id128 ret_boot_id);
+
+ public int set_data_threshold (size_t sz);
+ public int get_data_threshold (out size_t sz);
+
+ public int get_data (string field, [CCode (type = "const void**", array_length_type = "size_t")] out unowned uint8[] data);
+ public int enumerate_data ([CCode (type = "const void**", array_length_type = "size_t")] out unowned uint8[] data);
+ public void restart_data ();
+
+ public int add_match ([CCode (array_length_type = "size_t")] uint8[] data);
+ public int add_disjunction ();
+ public int add_conjunction ();
+ public void flush_matches ();
+
+ public int seek_head ();
+ public int seek_tail ();
+ public int seek_monotonic_usec (Systemd.Id128 boot_id, uint64 usec);
+ public int seek_realtime_usec (uint64 usec);
+ public int seek_cursor (string cursor);
+
+ public int get_cursor (out unowned string cursor);
+ public int test_cursor (string cursor);
+
+ public int get_cutoff_realtime_usec (out uint64 from, out uint64 to);
+ public int get_cutoff_monotonic_usec (Systemd.Id128 boot_id, out uint64 from, out uint64 to);
+
+ public int get_usage (out uint64 bytes);
+
+ public int query_unique (string field);
+ public int enumerate_unique ([CCode (type = "const void**", array_length_type = "size_t")] out unowned uint8[] data);
+ public void restart_unique ();
+
+ public int get_fd ();
+ public int get_events ();
+ public int get_timeout (out uint64 timeout_usec);
+ public int process ();
+ public int wait (uint64 timeout_usec);
+ public int reliable_fd ();
+
+ public int get_catalog (out unowned string text);
+ public int get_catalog_for_message_id (Systemd.Id128 id, out unowned string ret);
+ }
+
+ [SimpleType, CCode (cname = "sd_id128_t", lower_case_cprefix = "sd_id128_", cheader_filename = "systemd/sd-id128.h", default_value = "SD_ID128_NULL")]
+ public struct Id128 {
+ public uint8 bytes[16];
+ public uint64 qwords[2];
+
+ [CCode (cname = "SD_ID128_NULL")]
+ public const Systemd.Id128 NULL;
+
+ [CCode (cname = "sd_id128_randomize")]
+ public static int random (out Systemd.Id128 ret);
+ [CCode (cname = "sd_id128_get_machine")]
+ public static int machine (out Systemd.Id128 ret);
+ [CCode (cname = "sd_id128_get_boot")]
+ public static int boot (out Systemd.Id128 ret);
+ public static int from_string (string s, out Systemd.Id128 ret);
+
+ [CCode (cname = "_vala_sd_id128_to_string")]
+ public string to_string () {
+ return this.str;
+ }
+
+ public static bool equal (Systemd.Id128 a, Systemd.Id128 b);
+
+ public unowned string str {
+ [CCode (cname = "SD_ID128_TO_STRING")]
+ get;
+ }
+ }
+}