Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions data/gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
<gresource prefix="/io/elementary/settings/system">
<file alias="OperatingSystemView.css" compressed="true">OperatingSystemView.css</file>
</gresource>

<gresource prefix="/io/elementary/settings/system/icons">
<file alias="32x32/categories/system-logs-symbolic.svg" compressed="true" preprocess="xml-stripblanks">system-logs-symbolic.svg</file>
</gresource>
</gresources>
41 changes: 41 additions & 0 deletions data/system-logs-symbolic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions src/LogDialog/LogCell.vala
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
85 changes: 85 additions & 0 deletions src/LogDialog/LogDetailsView.vala
Original file line number Diff line number Diff line change
@@ -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;
}
}
147 changes: 147 additions & 0 deletions src/LogDialog/LogDialog.vala
Original file line number Diff line number Diff line change
@@ -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 = "<b>%s</b>".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 ();
}
}
}
46 changes: 46 additions & 0 deletions src/LogDialog/SystemdLogEntry.vala
Original file line number Diff line number Diff line change
@@ -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 ());
}
}
Loading