adds setting screen
This commit is contained in:
@@ -20,6 +20,10 @@ public class KordophoneApp : Adw.Application
|
|||||||
provider,
|
provider,
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Warm up dbus connections
|
||||||
|
Settings.get_instance();
|
||||||
|
Repository.get_instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void activate () {
|
protected override void activate () {
|
||||||
|
|||||||
@@ -24,6 +24,15 @@ public class MainWindow : Adw.ApplicationWindow
|
|||||||
|
|
||||||
var transcript_page = new NavigationPage (transcript_view, "Transcript");
|
var transcript_page = new NavigationPage (transcript_view, "Transcript");
|
||||||
split_view.content = transcript_page;
|
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) {
|
private void conversation_selected(string? conversation_guid) {
|
||||||
|
|||||||
73
src/application/preferences-window.vala
Normal file
73
src/application/preferences-window.vala
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,6 +48,16 @@ public class ConversationListView : Adw.Bin
|
|||||||
});
|
});
|
||||||
header_bar.pack_end (refresh_button);
|
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
|
// Set up model and bind to list
|
||||||
conversation_model = new ConversationListModel ();
|
conversation_model = new ConversationListModel ();
|
||||||
conversation_model.items_changed.connect (on_items_changed);
|
conversation_model.items_changed.connect (on_items_changed);
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ public class ConversationRow : Adw.ActionRow {
|
|||||||
|
|
||||||
public ConversationRow(Conversation conversation) {
|
public ConversationRow(Conversation conversation) {
|
||||||
this.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;
|
subtitle_lines = 1;
|
||||||
|
|
||||||
add_css_class("conversation-row");
|
add_css_class("conversation-row");
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ dependencies = [
|
|||||||
dependency('libadwaita-1', required : true),
|
dependency('libadwaita-1', required : true),
|
||||||
dependency('gio-2.0', required : true),
|
dependency('gio-2.0', required : true),
|
||||||
dependency('gee-0.8', 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')
|
gnome = import('gnome')
|
||||||
@@ -16,9 +17,12 @@ resources = gnome.compile_resources(
|
|||||||
sources = [
|
sources = [
|
||||||
'application/kordophone-application.vala',
|
'application/kordophone-application.vala',
|
||||||
'application/main-window.vala',
|
'application/main-window.vala',
|
||||||
|
'application/preferences-window.vala',
|
||||||
|
|
||||||
'service/interface/dbusservice.vala',
|
'service/interface/dbusservice.vala',
|
||||||
|
'service/dbus-service-base.vala',
|
||||||
'service/repository.vala',
|
'service/repository.vala',
|
||||||
|
'service/settings.vala',
|
||||||
|
|
||||||
'conversation-list/conversation-list-view.vala',
|
'conversation-list/conversation-list-view.vala',
|
||||||
'conversation-list/conversation-list-model.vala',
|
'conversation-list/conversation-list-model.vala',
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class Conversation : Object {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (participants.length > 1) {
|
if (participants.length > 1) {
|
||||||
return string.join(", ", participants);
|
return string.joinv(", ", participants);
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Untitled";
|
return "Untitled";
|
||||||
|
|||||||
@@ -28,4 +28,7 @@
|
|||||||
|
|
||||||
.message-input-entry {
|
.message-input-entry {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
60
src/service/dbus-service-base.vala
Normal file
60
src/service/dbus-service-base.vala
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,6 @@ namespace DBusService {
|
|||||||
[DBus (name = "Username")]
|
[DBus (name = "Username")]
|
||||||
public abstract string username { owned get; set; }
|
public abstract string username { owned get; set; }
|
||||||
|
|
||||||
[DBus (name = "CredentialItem")]
|
|
||||||
public abstract GLib.ObjectPath credential_item { owned get; set; }
|
|
||||||
|
|
||||||
[DBus (name = "SetServer")]
|
[DBus (name = "SetServer")]
|
||||||
public abstract void set_server(string url, string user) throws DBusError, IOError;
|
public abstract void set_server(string url, string user) throws DBusError, IOError;
|
||||||
|
|
||||||
|
|||||||
@@ -80,12 +80,6 @@
|
|||||||
<property name="ServerURL" type="s" access="readwrite"/>
|
<property name="ServerURL" type="s" access="readwrite"/>
|
||||||
<property name="Username" type="s" access="readwrite"/>
|
<property name="Username" type="s" access="readwrite"/>
|
||||||
|
|
||||||
<!-- Secret-Service handle (object path) -->
|
|
||||||
<property name="CredentialItem" type="o" access="readwrite">
|
|
||||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal"
|
|
||||||
value="true"/>
|
|
||||||
</property>
|
|
||||||
|
|
||||||
<!-- helpers for atomic updates -->
|
<!-- helpers for atomic updates -->
|
||||||
<method name="SetServer">
|
<method name="SetServer">
|
||||||
<arg name="url" type="s" direction="in"/>
|
<arg name="url" type="s" direction="in"/>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
using GLib;
|
using GLib;
|
||||||
using Gee;
|
using Gee;
|
||||||
|
|
||||||
public class Repository : Object
|
public class Repository : DBusServiceBase {
|
||||||
{
|
|
||||||
public signal void conversations_updated();
|
public signal void conversations_updated();
|
||||||
public signal void messages_updated(string conversation_guid);
|
public signal void messages_updated(string conversation_guid);
|
||||||
|
|
||||||
@@ -15,24 +14,38 @@ public class Repository : Object
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Repository instance = null;
|
private static Repository instance = null;
|
||||||
private DBusService.Repository dbus_repository;
|
private DBusService.Repository? dbus_repository;
|
||||||
private uint dbus_watch_id;
|
|
||||||
|
|
||||||
private Repository() {
|
private Repository() {
|
||||||
connect_to_dbus.begin((obj, res) => {
|
base();
|
||||||
connect_to_dbus.end(res);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~Repository() {
|
protected override string get_service_name() {
|
||||||
if (dbus_watch_id > 0) {
|
return "Repository";
|
||||||
Bus.unwatch_name(dbus_watch_id);
|
}
|
||||||
}
|
|
||||||
|
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 {
|
public Conversation[] get_conversations() throws Error {
|
||||||
if (dbus_repository == null) {
|
if (!is_connected || dbus_repository == null) {
|
||||||
throw new Error(1337, 1, "Repository not connected");
|
throw create_not_connected_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
var conversations = dbus_repository.get_conversations();
|
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 {
|
public Message[] get_messages(string conversation_guid, string last_message_id = "") throws Error {
|
||||||
if (dbus_repository == null) {
|
if (!is_connected || dbus_repository == null) {
|
||||||
throw new Error(1337, 1, "Repository not connected");
|
throw create_not_connected_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
var messages = dbus_repository.get_messages(conversation_guid, last_message_id);
|
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 {
|
public string send_message(string conversation_guid, string message) throws Error {
|
||||||
if (dbus_repository == null) {
|
if (!is_connected || dbus_repository == null) {
|
||||||
throw new Error(1337, 1, "Repository not connected");
|
throw create_not_connected_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbus_repository.send_message(conversation_guid, message);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/service/settings.vala
Normal file
115
src/service/settings.vala
Normal file
@@ -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<string, string> password_attributes() {
|
||||||
|
var attributes = new HashTable<string, string>(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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user