Changes
4 changed files (+298/-187)
-
-
@@ -25,6 +25,7 @@ SPDX-License-Identifier: Apache-2.0<file preprocess="xml-stripblanks">ui/playback-toolbar.ui</file> <file preprocess="xml-stripblanks">ui/server-list.ui</file> <file preprocess="xml-stripblanks">ui/generic-error-dialog.ui</file> <file preprocess="xml-stripblanks">ui/preferences-dialog.ui</file> <file preprocess="xml-stripblanks">ui/server-connecting.ui</file> <file preprocess="xml-stripblanks">ui/server-list-network-error-dialog.ui</file> <file preprocess="xml-stripblanks">ui/zone-output-row.ui</file>
-
-
-
@@ -0,0 +1,122 @@<?xml version="1.0" encoding="utf-8"?> <!-- Copyright 2025 Shota FUJI Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. SPDX-License-Identifier: Apache-2.0 --> <interface> <template class="PlacGtkAdwaitaPreferencesDialog" parent="AdwPreferencesDialog"> <child> <object class="AdwPreferencesPage"> <child> <object class="AdwPreferencesGroup"> <property name="title">General</property> <child> <object class="AdwComboRow" id="startup_category"> <property name="title">Default category</property> <property name="subtitle">Category to be selected on startup.</property> </object> </child> </object> </child> <child> <object class="AdwPreferencesGroup"> <property name="title">Appearance</property> <child> <object class="AdwSwitchRow" id="disable_csd"> <property name="title">Hide window titlebar</property> <property name="subtitle">Hide window control buttons and title. Menu button and sidebar button remain.</property> </object> </child> <child> <object class="AdwSwitchRow" id="label_parsing_enabled"> <property name="title">Parse labels</property> <property name="subtitle">Enable parsing of "[[id|text]]" labels in browse section.</property> </object> </child> <child> <object class="AdwSwitchRow" id="show_browse_item_separators"> <property name="title">Show separators for browse items</property> <property name="subtitle">Show separator between browse items.</property> </object> </child> <child> <object class="AdwSwitchRow" id="show_seek_by_10secs"> <property name="title">Show seek by 10secs buttons</property> <property name="subtitle">Show buttons that seek forward/backwards by 10 seconds.</property> </object> </child> <child> <object class="AdwSwitchRow" id="show_stop_button"> <property name="title">Show stop button</property> <property name="subtitle">Show stop button next to play/pause button. Stopping playback releases audio device immediately.</property> </object> </child> </object> </child> <child> <object class="AdwPreferencesGroup"> <property name="title">Keyboard Navigation</property> <child> <object class="AdwSwitchRow" id="focus_browse_items_on_load"> <property name="title">Focus browse items on load</property> <property name="subtitle">Move focus to the first browse item or search entry right after the page is loaded.</property> </object> </child> <child> <object class="AdwEntryRow" id="seek_step_size"> <property name="title">Seekbar step size</property> <property name="input-purpose">GTK_INPUT_PURPOSE_NUMBER</property> <child type="suffix"> <object class="GtkLabel"> <property name="label">secs.</property> </object> </child> </object> </child> <child> <object class="AdwEntryRow" id="seek_page_size"> <property name="title">Seekbar jump size</property> <property name="input-purpose">GTK_INPUT_PURPOSE_NUMBER</property> <child type="suffix"> <object class="GtkLabel"> <property name="label">secs.</property> </object> </child> </object> </child> </object> </child> <child> <object class="AdwPreferencesGroup"> <property name="title">Server Discovery</property> <child> <object class="AdwSwitchRow" id="fixed_discovery_udp_port_enabled"> <property name="title">Use fixed UDP port</property> <property name="subtitle">Fix UDP port number to use for sending and receiving of Roon Server discovery. If this option is off, Plac will pick a random available UDP port. Requires restart to take effect.</property> </object> </child> <child> <object class="AdwEntryRow" id="discovery_udp_port"> <property name="title">Port number</property> <property name="input-purpose">GTK_INPUT_PURPOSE_NUMBER</property> </object> </child> </object> </child> </object> </child> </template> </interface>
-
-
-
@@ -55,177 +55,7 @@ namespace PlacGtkAdwaita {} private void on_preferences() { var startup_category_factory = new Gtk.SignalListItemFactory(); startup_category_factory.setup.connect(CategoryComboRowFactory.setup); startup_category_factory.bind.connect(CategoryComboRowFactory.bind); var startup_category_model = new Adw.EnumListModel(typeof (Roon.Browse.Hierarchy)); var startup_category = new Adw.ComboRow(); startup_category.model = startup_category_model; startup_category.factory = startup_category_factory; startup_category.title = "Default category"; startup_category.subtitle = "Category to be selected on startup."; settings.settings.bind_with_mapping(Settings.STARTUP_CATEGORY, startup_category, "selected", DEFAULT, (to, from, user_data) => { var model = (Adw.EnumListModel) user_data; var hierarchy = Roon.Browse.Hierarchy.from_int(from.get_int32()); to.set_uint(model.find_position(hierarchy)); return true; }, (from, variant_type, user_data) => { var model = (Adw.EnumListModel) user_data; var item = (Adw.EnumListItem) model.get_item(from.get_uint()); if (item == null) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } var hierarchy = Roon.Browse.Hierarchy.from_int(item.value); return new Variant("i", hierarchy); }, startup_category_model, null); var general = new Adw.PreferencesGroup(); general.add(startup_category); general.title = "General"; var disable_csd = new Adw.SwitchRow(); settings.settings.bind(Settings.DISABLE_CSD, disable_csd, "active", DEFAULT); disable_csd.title = "Hide window titlebar"; disable_csd.subtitle = "Hide window control buttons and title. Menu button and sidebar button remain."; var label_parsing_enabled = new Adw.SwitchRow(); settings.settings.bind(Settings.LABEL_PARSING_ENABLED, label_parsing_enabled, "active", DEFAULT); label_parsing_enabled.title = "Parse labels"; label_parsing_enabled.subtitle = "Enable parsing of \"[[id|text]]\" labels in browse section."; var show_browse_item_separators = new Adw.SwitchRow(); settings.settings.bind(Settings.SHOW_BROWSE_ITEM_SEPARATORS, show_browse_item_separators, "active", DEFAULT); show_browse_item_separators.title = "Show separators for browse items"; show_browse_item_separators.subtitle = "Show separator between browse items."; var show_seek_by_10secs = new Adw.SwitchRow(); settings.settings.bind(Settings.SHOW_SEEK_BY_10SECS, show_seek_by_10secs, "active", DEFAULT); show_seek_by_10secs.title = "Show seek by 10secs buttons"; show_seek_by_10secs.subtitle = "Show buttons that seek forward/backwards by 10 seconds."; var show_stop_button = new Adw.SwitchRow(); settings.settings.bind(Settings.SHOW_STOP_BUTTON, show_stop_button, "active", DEFAULT); show_stop_button.title = "Show stop button"; show_stop_button.subtitle = "Show stop button next to play/pause button. Stopping playback releases audio device immediately."; var appearance = new Adw.PreferencesGroup(); appearance.add(disable_csd); appearance.add(label_parsing_enabled); appearance.add(show_browse_item_separators); appearance.add(show_seek_by_10secs); appearance.add(show_stop_button); appearance.title = "Appearance"; var focus_browse_items_on_load = new Adw.SwitchRow(); settings.settings.bind(Settings.FOCUS_BROWSE_ITEMS_ON_LOAD, focus_browse_items_on_load, "active", DEFAULT); focus_browse_items_on_load.title = "Focus browse items on load"; focus_browse_items_on_load.subtitle = "Move focus to the first browse item or search entry right after the page is loaded."; var seek_step_size = new Adw.EntryRow(); seek_step_size.title = "Seekbar step size"; seek_step_size.input_purpose = NUMBER; settings.settings.bind_with_mapping(Settings.SEEK_STEP_SIZE, seek_step_size, "text", DEFAULT, (to, from) => { var size = from.get_uint32(); to.set_string(size.to_string()); return true; }, (from, variant) => { var text = from.get_string(); var raw_val = uint.parse(text, 10); if (raw_val == 0) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } return new Variant("u", raw_val); }, null, null); seek_step_size.add_suffix(new Gtk.Label("secs.")); var seek_page_size = new Adw.EntryRow(); seek_page_size.title = "Seekbar jump size"; seek_page_size.input_purpose = NUMBER; settings.settings.bind_with_mapping(Settings.SEEK_PAGE_SIZE, seek_page_size, "text", DEFAULT, (to, from) => { var size = from.get_uint32(); to.set_string(size.to_string()); return true; }, (from, variant) => { var text = from.get_string(); var raw_val = uint.parse(text, 10); if (raw_val == 0) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } return new Variant("u", raw_val); }, null, null); seek_page_size.add_suffix(new Gtk.Label("secs.")); var keyboard_navigation = new Adw.PreferencesGroup(); keyboard_navigation.add(focus_browse_items_on_load); keyboard_navigation.add(seek_step_size); keyboard_navigation.add(seek_page_size); keyboard_navigation.title = "Keyboard Navigation"; var fixed_discovery_udp_port_enabled = new Adw.SwitchRow(); fixed_discovery_udp_port_enabled.title = "Use fixed UDP port"; fixed_discovery_udp_port_enabled.subtitle = "Fix UDP port number to use for sending and receiving of Roon Server discovery. If this option is off, Plac will pick a random available UDP port. Requires restart to take effect."; settings.settings.bind_with_mapping(Settings.DISCOVERY_UDP_PORT, fixed_discovery_udp_port_enabled, "active", DEFAULT, (to, from) => { var port = from.get_uint32(); to.set_boolean(port != 0); return true; }, (from, variant) => { return new Variant("u", from.get_boolean() ? 9003 : 0); }, null, null); var discovery_udp_port = new Adw.EntryRow(); discovery_udp_port.title = "Port number"; discovery_udp_port.input_purpose = NUMBER; settings.settings.bind_with_mapping(Settings.DISCOVERY_UDP_PORT, discovery_udp_port, "text", DEFAULT, (to, from) => { var port = from.get_uint32(); to.set_string(port.to_string()); return true; }, (from, variant) => { var text = from.get_string(); var raw_val = uint.parse(text, 10); if (raw_val == 0 || raw_val > uint16.MAX) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } return new Variant("u", raw_val); }, null, null); fixed_discovery_udp_port_enabled.bind_property("active", discovery_udp_port, "visible", SYNC_CREATE); var discovery = new Adw.PreferencesGroup(); discovery.add(fixed_discovery_udp_port_enabled); discovery.add(discovery_udp_port); discovery.title = "Server Discovery"; var page = new Adw.PreferencesPage(); page.add(general); page.add(appearance); page.add(keyboard_navigation); page.add(discovery); var dialog = new Adw.PreferencesDialog(); dialog.add(page); var dialog = new PreferencesDialog(); dialog.present(this.active_window); }
-
@@ -249,22 +79,6 @@ namespace PlacGtkAdwaita {} } class CategoryComboRowFactory { public static void setup(Gtk.ListItemFactory factory, Object object) { var list_item = (Gtk.ListItem) object; list_item.child = new Gtk.Label(""); } public static void bind(Gtk.ListItemFactory factory, Object object) { var list_item = (Gtk.ListItem) object; var item = (Adw.EnumListItem) list_item.item; var hierarchy = Roon.Browse.Hierarchy.from_int(item.value); var label = (Gtk.Label) list_item.child; label.label = hierarchy.to_display_string(); } } [GtkTemplate(ui = "/jp/pocka/plac/gtk-adwaita/ui/server-list-network-error-dialog.ui")] class ServerListNetworkErrorDialog : GenericErrorDialog { public ServerListNetworkErrorDialog(string details) {
-
-
-
@@ -0,0 +1,174 @@// Copyright 2026 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 namespace PlacGtkAdwaita { [GtkTemplate(ui = "/jp/pocka/plac/gtk-adwaita/ui/preferences-dialog.ui")] private class PreferencesDialog : Adw.PreferencesDialog { private Settings settings = new Settings(); [GtkChild] private unowned Adw.ComboRow startup_category; [GtkChild] private unowned Adw.SwitchRow disable_csd; [GtkChild] private unowned Adw.SwitchRow label_parsing_enabled; [GtkChild] private unowned Adw.SwitchRow show_browse_item_separators; [GtkChild] private unowned Adw.SwitchRow show_seek_by_10secs; [GtkChild] private unowned Adw.SwitchRow show_stop_button; [GtkChild] private unowned Adw.SwitchRow focus_browse_items_on_load; [GtkChild] private unowned Adw.EntryRow seek_step_size; [GtkChild] private unowned Adw.EntryRow seek_page_size; [GtkChild] private unowned Adw.SwitchRow fixed_discovery_udp_port_enabled; [GtkChild] private unowned Adw.EntryRow discovery_udp_port; public PreferencesDialog() { Object(); } construct { var startup_category_factory = new Gtk.SignalListItemFactory(); startup_category_factory.setup.connect(setup_startup_category); startup_category_factory.bind.connect(bind_startup_category); var startup_category_model = new Adw.EnumListModel(typeof (Roon.Browse.Hierarchy)); startup_category.model = startup_category_model; startup_category.factory = startup_category_factory; settings.settings.bind_with_mapping(Settings.STARTUP_CATEGORY, startup_category, "selected", DEFAULT, (to, from, user_data) => { var model = (Adw.EnumListModel) user_data; var hierarchy = Roon.Browse.Hierarchy.from_int(from.get_int32()); to.set_uint(model.find_position(hierarchy)); return true; }, (from, variant_type, user_data) => { var model = (Adw.EnumListModel) user_data; var item = (Adw.EnumListItem) model.get_item(from.get_uint()); if (item == null) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } var hierarchy = Roon.Browse.Hierarchy.from_int(item.value); return new Variant("i", hierarchy); }, startup_category_model, null); settings.settings.bind(Settings.DISABLE_CSD, disable_csd, "active", DEFAULT); settings.settings.bind(Settings.LABEL_PARSING_ENABLED, label_parsing_enabled, "active", DEFAULT); settings.settings.bind(Settings.SHOW_BROWSE_ITEM_SEPARATORS, show_browse_item_separators, "active", DEFAULT); settings.settings.bind(Settings.SHOW_SEEK_BY_10SECS, show_seek_by_10secs, "active", DEFAULT); settings.settings.bind(Settings.SHOW_STOP_BUTTON, show_stop_button, "active", DEFAULT); settings.settings.bind(Settings.FOCUS_BROWSE_ITEMS_ON_LOAD, focus_browse_items_on_load, "active", DEFAULT); settings.settings.bind_with_mapping(Settings.SEEK_STEP_SIZE, seek_step_size, "text", DEFAULT, (to, from) => { var size = from.get_uint32(); to.set_string(size.to_string()); return true; }, (from, variant) => { var text = from.get_string(); var raw_val = uint.parse(text, 10); if (raw_val == 0) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } return new Variant("u", raw_val); }, null, null); settings.settings.bind_with_mapping(Settings.SEEK_PAGE_SIZE, seek_page_size, "text", DEFAULT, (to, from) => { var size = from.get_uint32(); to.set_string(size.to_string()); return true; }, (from, variant) => { var text = from.get_string(); var raw_val = uint.parse(text, 10); if (raw_val == 0) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } return new Variant("u", raw_val); }, null, null); settings.settings.bind_with_mapping(Settings.DISCOVERY_UDP_PORT, fixed_discovery_udp_port_enabled, "active", DEFAULT, (to, from) => { var port = from.get_uint32(); to.set_boolean(port != 0); return true; }, (from, variant) => { return new Variant("u", from.get_boolean() ? 9003 : 0); }, null, null); settings.settings.bind_with_mapping(Settings.DISCOVERY_UDP_PORT, discovery_udp_port, "text", DEFAULT, (to, from) => { var port = from.get_uint32(); to.set_string(port.to_string()); return true; }, (from, variant) => { var text = from.get_string(); var raw_val = uint.parse(text, 10); if (raw_val == 0 || raw_val > uint16.MAX) { // Vala's type definition wrongly defines return type signature of // "SettingsBindSetMappingShared" as non-null, despite it being nullable and // even documentation saying "return null in case of an error." // This cast suppress compiler warning. return (Variant) null; } return new Variant("u", raw_val); }, null, null); fixed_discovery_udp_port_enabled.bind_property("active", discovery_udp_port, "visible", SYNC_CREATE); } private static void setup_startup_category(Gtk.ListItemFactory factory, Object object) { var list_item = (Gtk.ListItem) object; list_item.child = new Gtk.Label(""); } private static void bind_startup_category(Gtk.ListItemFactory factory, Object object) { var list_item = (Gtk.ListItem) object; var item = (Adw.EnumListItem) list_item.item; var hierarchy = Roon.Browse.Hierarchy.from_int(item.value); var label = (Gtk.Label) list_item.child; label.label = hierarchy.to_display_string(); } } }
-