diff --git a/src/application/kordophone-application.vala b/src/application/kordophone-application.vala
index 0d5f465..758e4dd 100644
--- a/src/application/kordophone-application.vala
+++ b/src/application/kordophone-application.vala
@@ -20,6 +20,10 @@ public class KordophoneApp : Adw.Application
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
+
+ // Warm up dbus connections
+ Settings.get_instance();
+ Repository.get_instance();
}
protected override void activate () {
diff --git a/src/application/main-window.vala b/src/application/main-window.vala
index 685c775..ccb44fe 100644
--- a/src/application/main-window.vala
+++ b/src/application/main-window.vala
@@ -24,6 +24,15 @@ public class MainWindow : Adw.ApplicationWindow
var transcript_page = new NavigationPage (transcript_view, "Transcript");
split_view.content = transcript_page;
+
+ var show_settings_action = new SimpleAction ("settings", null);
+ show_settings_action.activate.connect(show_settings);
+ add_action(show_settings_action);
+ }
+
+ private void show_settings () {
+ var dialog = new PreferencesWindow (this);
+ dialog.present (this);
}
private void conversation_selected(string? conversation_guid) {
diff --git a/src/application/preferences-window.vala b/src/application/preferences-window.vala
new file mode 100644
index 0000000..d5f2681
--- /dev/null
+++ b/src/application/preferences-window.vala
@@ -0,0 +1,73 @@
+using Adw;
+using Gtk;
+
+public class PreferencesWindow : Adw.PreferencesDialog {
+ private Adw.EntryRow server_url_row;
+ private Adw.EntryRow username_row;
+ private Adw.PasswordEntryRow password_row;
+ private Settings settings;
+
+ public PreferencesWindow (Gtk.Window parent) {
+ Object (
+ title: "Settings"
+ );
+
+ add_css_class ("settings-dialog");
+
+ var page = new PreferencesPage ();
+ page.margin_top = 14;
+ page.margin_bottom = 14;
+ page.margin_start = 50;
+ page.margin_end = 50;
+ add (page);
+
+ var connection_group = new PreferencesGroup ();
+ connection_group.title = "Connection Settings";
+ page.add (connection_group);
+
+ server_url_row = new Adw.EntryRow ();
+ server_url_row.title = "Server URL";
+ connection_group.add (server_url_row);
+
+ username_row = new Adw.EntryRow ();
+ username_row.title = "Username";
+ connection_group.add (username_row);
+
+ password_row = new Adw.PasswordEntryRow ();
+ password_row.title = "Password";
+ connection_group.add (password_row);
+
+ settings = Settings.get_instance();
+ settings.settings_ready.connect(load_settings);
+ if (settings.is_connected) {
+ message("settings is connected");
+ load_settings();
+ }
+ }
+
+ private void load_settings() {
+ try {
+ username_row.text = settings.get_username();
+ server_url_row.text = settings.get_server_url();
+ password_row.text = settings.get_password();
+ } catch (Error e) {
+ warning("Failed to load settings: %s", e.message);
+ }
+
+ setup_change_callbacks();
+ }
+
+ private void setup_change_callbacks() {
+ server_url_row.changed.connect(() => {
+ settings.set_server_url(server_url_row.text);
+ });
+
+ username_row.changed.connect(() => {
+ settings.set_username(username_row.text);
+ });
+
+ password_row.changed.connect(() => {
+ settings.set_password(password_row.text);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/conversation-list/conversation-list-view.vala b/src/conversation-list/conversation-list-view.vala
index 90a0450..cf2fd52 100644
--- a/src/conversation-list/conversation-list-view.vala
+++ b/src/conversation-list/conversation-list-view.vala
@@ -48,6 +48,16 @@ public class ConversationListView : Adw.Bin
});
header_bar.pack_end (refresh_button);
+ // Setup application menu
+ var app_menu = new Menu ();
+ app_menu.append ("Refresh", "refresh");
+ app_menu.append ("Settings...", "win.settings");
+ app_menu.append ("Quit", "app.quit");
+
+ var menu_button = new Gtk.MenuButton ();
+ menu_button.menu_model = app_menu;
+ header_bar.pack_end (menu_button);
+
// Set up model and bind to list
conversation_model = new ConversationListModel ();
conversation_model.items_changed.connect (on_items_changed);
diff --git a/src/conversation-list/conversation-row.vala b/src/conversation-list/conversation-row.vala
index 5ddb121..5f75b80 100644
--- a/src/conversation-list/conversation-row.vala
+++ b/src/conversation-list/conversation-row.vala
@@ -7,8 +7,11 @@ public class ConversationRow : Adw.ActionRow {
public ConversationRow(Conversation conversation) {
this.conversation = conversation;
- title = conversation.display_name;
- subtitle = conversation.last_message_preview;
+
+ title = conversation.display_name.strip();
+ title_lines = 1;
+
+ subtitle = conversation.last_message_preview.strip();
subtitle_lines = 1;
add_css_class("conversation-row");
diff --git a/src/meson.build b/src/meson.build
index f143b9d..73c3bdb 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -3,7 +3,8 @@ dependencies = [
dependency('libadwaita-1', required : true),
dependency('gio-2.0', required : true),
dependency('gee-0.8', required : true),
- dependency('gio-unix-2.0', required : true)
+ dependency('gio-unix-2.0', required : true),
+ dependency('libsecret-1', required : true),
]
gnome = import('gnome')
@@ -16,9 +17,12 @@ resources = gnome.compile_resources(
sources = [
'application/kordophone-application.vala',
'application/main-window.vala',
+ 'application/preferences-window.vala',
'service/interface/dbusservice.vala',
+ 'service/dbus-service-base.vala',
'service/repository.vala',
+ 'service/settings.vala',
'conversation-list/conversation-list-view.vala',
'conversation-list/conversation-list-model.vala',
diff --git a/src/models/conversation.vala b/src/models/conversation.vala
index c7c68a0..563d84f 100644
--- a/src/models/conversation.vala
+++ b/src/models/conversation.vala
@@ -20,7 +20,7 @@ public class Conversation : Object {
}
if (participants.length > 1) {
- return string.join(", ", participants);
+ return string.joinv(", ", participants);
}
return "Untitled";
diff --git a/src/resources/style.css b/src/resources/style.css
index f3cadf4..bcad32c 100644
--- a/src/resources/style.css
+++ b/src/resources/style.css
@@ -28,4 +28,7 @@
.message-input-entry {
font-size: 1.1rem;
-}
\ No newline at end of file
+}
+
+
+
diff --git a/src/service/dbus-service-base.vala b/src/service/dbus-service-base.vala
new file mode 100644
index 0000000..f0a2ffa
--- /dev/null
+++ b/src/service/dbus-service-base.vala
@@ -0,0 +1,60 @@
+public abstract class DBusServiceBase : Object {
+ protected uint dbus_watch_id;
+ public bool is_connected { get; private set; default = false; }
+
+ protected const string DBUS_PATH = "/net/buzzert/kordophonecd/daemon";
+ protected const string DBUS_NAME = "net.buzzert.kordophonecd";
+
+ protected DBusServiceBase() {
+ connect_to_dbus.begin((obj, res) => {
+ connect_to_dbus.end(res);
+ });
+ }
+
+ ~DBusServiceBase() {
+ if (dbus_watch_id > 0) {
+ Bus.unwatch_name(dbus_watch_id);
+ }
+ }
+
+ protected abstract void setup_signals();
+ protected abstract async Object? get_proxy() throws Error;
+ protected abstract string get_service_name();
+
+ private async void connect_to_dbus() {
+ try {
+ debug("Trying to connect to %s service at path: %s", get_service_name(), DBUS_PATH);
+
+ var proxy = yield get_proxy();
+ if (proxy == null) {
+ throw new Error(1337, 1, "Failed to get proxy");
+ }
+
+ // If we get here, connection succeeded
+ debug("Connected to %s service at path: %s", get_service_name(), DBUS_PATH);
+ is_connected = true;
+
+ setup_signals();
+
+ } catch (Error e) {
+ debug("Failed to connect to %s at %s: %s", get_service_name(), DBUS_PATH, e.message);
+ }
+
+ if (!is_connected) {
+ warning("Failed to connect to %s on any known path", get_service_name());
+
+ // Watch for the service to appear
+ dbus_watch_id = Bus.watch_name(BusType.SESSION,
+ DBUS_NAME,
+ BusNameWatcherFlags.AUTO_START,
+ () => {
+ connect_to_dbus.begin();
+ },
+ null);
+ }
+ }
+
+ protected Error create_not_connected_error() {
+ return new Error(1337, 1, @"$(get_service_name()) not connected");
+ }
+}
\ No newline at end of file
diff --git a/src/service/interface/dbusservice.vala b/src/service/interface/dbusservice.vala
index ded23fd..df38113 100644
--- a/src/service/interface/dbusservice.vala
+++ b/src/service/interface/dbusservice.vala
@@ -13,9 +13,6 @@ namespace DBusService {
[DBus (name = "Username")]
public abstract string username { owned get; set; }
- [DBus (name = "CredentialItem")]
- public abstract GLib.ObjectPath credential_item { owned get; set; }
-
[DBus (name = "SetServer")]
public abstract void set_server(string url, string user) throws DBusError, IOError;
diff --git a/src/service/interface/xml/net.buzzert.kordophonecd.Server.xml b/src/service/interface/xml/net.buzzert.kordophonecd.Server.xml
index 01fb1c3..7623714 100644
--- a/src/service/interface/xml/net.buzzert.kordophonecd.Server.xml
+++ b/src/service/interface/xml/net.buzzert.kordophonecd.Server.xml
@@ -80,12 +80,6 @@
-
-
-
-
-
diff --git a/src/service/repository.vala b/src/service/repository.vala
index f828cf2..f0e5d69 100644
--- a/src/service/repository.vala
+++ b/src/service/repository.vala
@@ -1,8 +1,7 @@
using GLib;
using Gee;
-public class Repository : Object
-{
+public class Repository : DBusServiceBase {
public signal void conversations_updated();
public signal void messages_updated(string conversation_guid);
@@ -15,24 +14,38 @@ public class Repository : Object
}
private static Repository instance = null;
- private DBusService.Repository dbus_repository;
- private uint dbus_watch_id;
+ private DBusService.Repository? dbus_repository;
private Repository() {
- connect_to_dbus.begin((obj, res) => {
- connect_to_dbus.end(res);
- });
+ base();
}
- ~Repository() {
- if (dbus_watch_id > 0) {
- Bus.unwatch_name(dbus_watch_id);
- }
+ protected override string get_service_name() {
+ return "Repository";
+ }
+
+ protected override async Object? get_proxy() throws Error {
+ dbus_repository = yield Bus.get_proxy(BusType.SESSION, DBUS_NAME, DBUS_PATH);
+ dbus_repository.get_version(); // Test the connection
+ return dbus_repository;
+ }
+
+ protected override void setup_signals() {
+ dbus_repository.conversations_updated.connect(() => {
+ conversations_updated();
+ });
+
+ dbus_repository.messages_updated.connect((conversation_guid) => {
+ messages_updated(conversation_guid);
+ });
+
+ // Initial load
+ conversations_updated();
}
public Conversation[] get_conversations() throws Error {
- if (dbus_repository == null) {
- throw new Error(1337, 1, "Repository not connected");
+ if (!is_connected || dbus_repository == null) {
+ throw create_not_connected_error();
}
var conversations = dbus_repository.get_conversations();
@@ -46,8 +59,8 @@ public class Repository : Object
}
public Message[] get_messages(string conversation_guid, string last_message_id = "") throws Error {
- if (dbus_repository == null) {
- throw new Error(1337, 1, "Repository not connected");
+ if (!is_connected || dbus_repository == null) {
+ throw create_not_connected_error();
}
var messages = dbus_repository.get_messages(conversation_guid, last_message_id);
@@ -61,56 +74,10 @@ public class Repository : Object
}
public string send_message(string conversation_guid, string message) throws Error {
- if (dbus_repository == null) {
- throw new Error(1337, 1, "Repository not connected");
+ if (!is_connected || dbus_repository == null) {
+ throw create_not_connected_error();
}
return dbus_repository.send_message(conversation_guid, message);
}
-
- private async void connect_to_dbus() {
- bool connected = false;
- const string path = "/net/buzzert/kordophonecd/daemon";
-
- try {
- debug("Trying to connect to DBus service at path: %s", path);
- dbus_repository = yield Bus.get_proxy(BusType.SESSION,
- "net.buzzert.kordophonecd",
- path);
-
- // Test the connection
- dbus_repository.get_version();
-
- // If we get here, connection succeeded
- debug("Connected to DBus service at path: %s", path);
- connected = true;
-
- // Listen for updates
- dbus_repository.conversations_updated.connect(() => {
- conversations_updated();
- });
-
- dbus_repository.messages_updated.connect((conversation_guid) => {
- messages_updated(conversation_guid);
- });
-
- // Initial load
- conversations_updated();
- } catch (Error e) {
- debug("Failed to connect to kordophonecd at %s: %s", path, e.message);
- }
-
- if (!connected) {
- warning("Failed to connect to kordophonecd on any known path");
-
- // Watch for the service to appear
- dbus_watch_id = Bus.watch_name(BusType.SESSION,
- "net.buzzert.kordophonecd",
- BusNameWatcherFlags.AUTO_START,
- () => {
- connect_to_dbus.begin();
- },
- null);
- }
- }
}
diff --git a/src/service/settings.vala b/src/service/settings.vala
new file mode 100644
index 0000000..07adfd0
--- /dev/null
+++ b/src/service/settings.vala
@@ -0,0 +1,115 @@
+using GLib;
+
+public class Settings : DBusServiceBase {
+ public signal void config_changed();
+ public signal void settings_ready();
+
+ public static Settings get_instance() {
+ if (instance == null) {
+ instance = new Settings();
+ }
+ return instance;
+ }
+
+ private static Settings instance = null;
+ private DBusService.Settings? dbus_settings;
+ private Secret.Service secret_service;
+
+ private Settings() {
+ base();
+
+ try {
+ secret_service = Secret.Service.get_sync(Secret.ServiceFlags.OPEN_SESSION);
+ } catch (Error e) {
+ warning("Failed to get secret service: %s", e.message);
+ }
+ }
+
+ protected override string get_service_name() {
+ return "Settings";
+ }
+
+ protected override async Object? get_proxy() throws Error {
+ dbus_settings = yield Bus.get_proxy(BusType.SESSION, DBUS_NAME, DBUS_PATH);
+ return dbus_settings;
+ }
+
+ protected override void setup_signals() {
+ dbus_settings.config_changed.connect(() => {
+ config_changed();
+ });
+
+ settings_ready();
+ }
+
+ public string get_server_url() throws Error {
+ if (!is_connected || dbus_settings == null) {
+ throw create_not_connected_error();
+ }
+ return dbus_settings.server_u_r_l;
+ }
+
+ public void set_server_url(string url) throws Error {
+ if (!is_connected || dbus_settings == null) {
+ throw create_not_connected_error();
+ }
+ dbus_settings.server_u_r_l = url;
+ }
+
+ public string get_username() throws Error {
+ if (!is_connected || dbus_settings == null) {
+ throw create_not_connected_error();
+ }
+ return dbus_settings.username;
+ }
+
+ public void set_username(string username) throws Error {
+ if (!is_connected || dbus_settings == null) {
+ throw create_not_connected_error();
+ }
+ dbus_settings.username = username;
+ }
+
+ public void set_server(string url, string username) throws Error {
+ if (!is_connected || dbus_settings == null) {
+ throw create_not_connected_error();
+ }
+ dbus_settings.set_server(url, username);
+ }
+
+ private HashTable password_attributes() {
+ var attributes = new HashTable(str_hash, str_equal);
+ attributes["service"] = "net.buzzert.kordophonecd";
+ attributes["target"] = "default";
+ attributes["username"] = get_username();
+ return attributes;
+ }
+
+ public string get_password() throws Error {
+ var attributes = password_attributes();
+ var password = secret_service.lookup_sync(null, attributes, null);
+ if (password == null) {
+ warning("No password found for user %s", get_username());
+ return "";
+ }
+
+ return password.get_text();
+ }
+
+ public void set_password(string password) throws Error {
+ var attributes = password_attributes();
+ bool result = secret_service.store_sync(
+ null,
+ attributes,
+ Secret.COLLECTION_DEFAULT,
+ "Kordophone Keystore",
+ new Secret.Value(password, password.length, "text/plain"),
+ null
+ );
+
+ if (!result) {
+ warning("Failed to store password for user %s", get_username());
+ throw new Error(1337, 1, "Failed to store password");
+ }
+ }
+}
\ No newline at end of file