'use strict'; const {Gdk, Gio, GLib, Gtk, GObject} = imports.gi; const Util = imports.util; const Widgets = imports.widgets; const LibSnapcast = imports.snapcast; const Avahi = imports.avahi; var SnapControlWindow = GObject.registerClass({ Template: Util.getBuilderFile('appwindow.ui') , InternalChildren: [ 'storeServerProps' , 'storeServers' , 'titlebar' , 'cbtServers' , 'spinner' , 'btnInfo' , 'btnAppMenu' , 'vBox' , 'labelState' , 'popoverConfig' ] }, class SnapControlWindow extends Gtk.ApplicationWindow { _init(params) { super._init(Object.assign({ title: _("Snapcast Control") , role: _("Snapcast Control") , 'show-menubar': false , 'icon-name': pkg.id , name: 'snap-control-window' }, params)); this._settings = Util.getSettings(pkg.id); this._btnAppMenu.set_menu_model(this.application.get_app_menu()); this.AS = new Avahi.AvahiSnapcast(); this.AS.connect('added', (AS, server) => { // XXX: Check if not exists this._storeServers.set( this._storeServers.append() , [0, 1, 2, 3, 4, 5] , [server.name, server.hostname.toLowerCase(), server.domain, server.ip, server.port, false] ); this._cbtServers.set_sensitive(true); if (this.snapcast._state == LibSnapcast.ClientStates.connected) { let [tie, iter] = this._storeServers.get_iter_first(); let model = this._cbtServers.get_model(); while (tie) { let host = [this._storeServers.get_value(iter, 3), this._storeServers.get_value(iter, 4)].join(':'); if (host === this.snapcast.host) { this._cbtServers.set_active_iter(iter); break; } tie = this._storeServers.iter_next(iter); } } else if (this._cbtServers.get_active() === -1) { this._cbtServers.set_active(0); } }); this.AS.connect('removed', (AS, name) => { let [tie, iter] = this._storeServers.get_iter_first(); let model = this._cbtServers.get_model(); while (tie) { if (this._storeServers.get_value(iter, 0) === name) { if (this._storeServers.get_value(iter, 5) === false) { this._storeServers.remove(iter); } break; } tie = this._storeServers.iter_next(iter); } if (this._cbtServers.get_active() === -1) { this._cbtServers.set_active(0); } }); this._spinner.start(); this._popoverConfig.connect('closed', () => { this._btnInfo.set_active(false); }); this._btnInfo.connect('toggled', (btn) => { if (btn.get_active()) { this._popoverConfig.set_relative_to(this._btnInfo); //this._popoverConfig.show_all(); this._popoverConfig.popup(); } else { this._popoverConfig.popdown(); } }); /* this._cbtServers.set_has_tooltip(true); this._cbtServers.connect('query-tooltip', (widget, x, y, keyboar_mode, tooltip) => { this._popoverConfig.set_relative_to(widget); this._popoverConfig.show_all(); this._popoverConfig.popup(); log(this._popoverConfig); return false; }); */ this._btnAppMenu = new Gtk.MenuButton(); this._btnAppMenu.set_menu_model(this.application.get_app_menu()); this._btnAppMenu.set_image(new Gtk.Image({ 'icon-name': 'open-menu-symbolic' })); this.show_all(); let lastUsed = this._settings.get_string('last-used'); this.snapcast = new LibSnapcast.Snapcast(lastUsed); this.snapcast.connect("state::ready", this._on_snapcast_ready.bind(this)); this.snapcast.connect("state::disconnected", this._on_snapcast_disconnected.bind(this)); this.snapcast.connect("state::connecting", this._on_snapcast_connecting.bind(this)); this.snapcast.connect("updated", this._on_snapcast_updated.bind(this)); this.snapcast.enable(); this._cbtServers.connect('changed', (combo) => { let [tie, tree_iter] = combo.get_active_iter(); let activeItem = combo.get_active(); let model = combo.get_model(); if (tie) { let hostname = this._storeServers.get_value(tree_iter, 1) , domain = this._storeServers.get_value(tree_iter, 2) , ip = this._storeServers.get_value(tree_iter, 3) , port = this._storeServers.get_value(tree_iter, 4) , host; if (ip !== "") { host = ip; } else { host = hostname; if (domain !== "") { host += '.' + domain; } } if (port === "") { port = 1705; } this.snapcast.host = [host, port].join(':'); } }); this.AS.enable(); } _on_snapcast_updated() { log("SNAPCAST_UPDATED"); this.updateInfoPopover(); //this._popoverConfig.add(this.buildInfoPopover()); } _on_snapcast_disconnected() { log("SNAPCAST_DISCONNECTED"); this._titlebar.set_subtitle(_("Disconnected")); this._vBox.foreach((child) => { if (child instanceof Widgets.GroupWidget) { this._vBox.remove(child); } }); if (this._popoverConfig.is_visible()) { this._popoverConfig.popdown(); } this._btnInfo.set_sensitive(false); this._btnInfo.set_visible(false); this._labelState.set_visible(true); } _on_snapcast_connecting() { this._spinner.start(); this._spinner.set_visible(true); } _on_snapcast_ready() { log("SNAPCAST_READY"); this._titlebar.set_subtitle(_("Connected")); this._settings.set_string('last-used', this.snapcast.host); this._labelState.set_visible(false); this._spinner.stop(); this._spinner.set_visible(false); this._btnInfo.set_sensitive(true); this._btnInfo.set_visible(true); this.snapcast.sServer.connect("streams::added", this._on_snapcast_stream_added.bind(this)); this.snapcast.sServer.connect("streams::deleted", this._on_snapcast_stream_deleted.bind(this)); this.snapcast.sServer.connect("groups::added", this._on_snapcast_group_added.bind(this)); this.snapcast.sServer.connect("groups::deleted", this._on_snapcast_group_deleted.bind(this)); this.snapcast.sServer.connect("clients::added", this._on_snapcast_client_added.bind(this)); this.snapcast.sServer.connect("clients::deleted", this._on_snapcast_client_deleted.bind(this)); } _on_snapcast_stream_added() { // XXX } _on_snapcast_stream_deleted() { // XXX } _on_snapcast_group_added(event, group) { this._vBox.add(new Widgets.GroupWidget(this, this.snapcast, group)); this._refresh_window_size(); } _on_snapcast_group_deleted(event, group_id) { this._refresh_window_size(); } _on_snapcast_client_added(event, client) { this._refresh_window_size(); } _on_snapcast_client_deleted(event, client) { this._refresh_window_size(); } _refresh_window_size() { let [wm, wn] = this.get_preferred_width(); let [hm, hn] = this._vBox.get_preferred_height(); let [thm, thn] = this._titlebar.get_preferred_height(); let ch = 0; this._vBox.get_children().forEach((child) => { let [chm, chn] = child.get_preferred_height(); ch = ch + chn + (this._vBox.get_spacing() * 2); }); ch += this._vBox.get_parent().get_margin_top() + this._vBox.get_parent().get_margin_bottom(); let nh = thn + this._vBox.get_margin_top() + this._vBox.get_margin_bottom() + ch; let display = this.get_display(); let monitor = display.get_monitor_at_window(this.get_window()); let wa = monitor.get_workarea(); if (nh > wa.height - thn) { nh = wa.height - thn; } this.resize(wn, nh); } updateInfoPopover() { this._storeServerProps.clear(); let props = this.snapcast.sServer.server; this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Name: "), props.snapserver.name]); this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Hostname: "), props.host.name]); this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Operating System: "), props.host.os]); if (props.host.arch !== "") { this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Architecture: "), props.host.arch]); } this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Snapserver Version: "), props.snapserver.version]); this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Protocol Version: "), props.snapserver.protocolVersion]); this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Control Version: "), props.snapserver.controlProtocolVersion]); this._storeServerProps.set(this._storeServerProps.append(), [0,1], [_("Streams: "), Object.keys(this.snapcast.sServer.streams).length]); } showServersWindow() { log('SHOW SERVER WINDOWS'); let modal = new Gtk.Dialog({ default_width: 250 , modal: true , transient_for: this , title: _("Servers") , use_header_bar: false }); let contentArea = modal.get_content_area() , actionArea = modal.get_action_area(); let treeview = new Gtk.TreeView({ model: this._storeServers , 'headers-visible': false }); let sel = treeview.get_selection(); sel.set_mode(Gtk.SelectionMode.NONE); let renderer0 = new Gtk.CellRendererText(); let renderer1 = new Gtk.CellRendererText(); let renderer2 = new Gtk.CellRendererText(); let renderer3 = new Gtk.CellRendererText(); let renderer4 = new Gtk.CellRendererText(); let renderer5 = new Gtk.CellRendererText(); let column0 = new Gtk.TreeViewColumn({ title: "ID" }); let column1 = new Gtk.TreeViewColumn({ title: "Hostname" }); let column2 = new Gtk.TreeViewColumn({ title: "Domain" }); let column3 = new Gtk.TreeViewColumn({ title: "IP" }); let column4 = new Gtk.TreeViewColumn({ title: "Port" }); let column5 = new Gtk.TreeViewColumn({ title: "Manual" }); column0.pack_start(renderer0, false); column0.add_attribute(renderer0, "text", 0); column1.pack_start(renderer1, false); column1.add_attribute(renderer1, "text", 1); column2.pack_start(renderer2, false); column2.add_attribute(renderer2, "text", 2); column2.pack_start(renderer3, false); column2.add_attribute(renderer3, "text", 3); column2.pack_start(renderer4, false); column2.add_attribute(renderer4, "text", 4); column2.pack_start(renderer5, false); column2.add_attribute(renderer5, "text", 5); treeview.append_column(column0); treeview.append_column(column1); treeview.append_column(column2); treeview.append_column(column3); treeview.append_column(column4); treeview.append_column(column5); treeview.set_margin_top(10); treeview.set_margin_bottom(10); contentArea.add(treeview); let boxBtn = new Gtk.ButtonBox({ orientation: Gtk.Orientation.HORIZONTAL }); boxBtn.set_layout(Gtk.ButtonBoxStyle.EDGE); let btnOk = Gtk.Button.new_from_stock(Gtk.STOCK_OK); btnOk.connect("clicked", () => { modal.close(); }); boxBtn.add(btnOk); actionArea.add(boxBtn); modal.show_all(); } });