1
1
mirror of https://github.com/danbee/persephone synced 2025-03-04 08:39:11 +00:00

Add preference window with host and port

This commit is contained in:
Daniel Barber 2019-02-15 16:56:56 -05:00
parent 201c7effad
commit 84d209a967
Signed by: danbarber
GPG Key ID: 931D8112E0103DD8
11 changed files with 308 additions and 28 deletions

View File

@ -22,6 +22,10 @@
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; };
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; };
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */; };
E41EA46F221715910068EF46 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46E221715910068EF46 /* Preferences.swift */; };
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; };
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; };
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
E4A642DA22090CBE00067D21 /* Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* Status.swift */; };
@ -122,6 +126,10 @@
E41B22E921FB966C00D544F6 /* capabilities.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = capabilities.h; sourceTree = "<group>"; };
E41B22EA21FB966C00D544F6 /* queue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = queue.h; sourceTree = "<group>"; };
E41B22EB21FB966C00D544F6 /* playlist.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = playlist.h; sourceTree = "<group>"; };
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E41EA46E221715910068EF46 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
E42A8F3922176D6400A13ED9 /* LICENSE.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = "<group>"; };
E42A8F3A22176D6400A13ED9 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
E465049921E94DF500A70F4C /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = "<group>"; };
E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; };
E4A642D922090CBE00067D21 /* Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Status.swift; sourceTree = "<group>"; };
@ -167,6 +175,8 @@
E40786382110CE70006887B1 /* PersephoneUITests */,
E40786192110CE6E006887B1 /* Products */,
E41B22BE21FB6B3300D544F6 /* Frameworks */,
E42A8F3922176D6400A13ED9 /* LICENSE.md */,
E42A8F3A22176D6400A13ED9 /* README.md */,
);
sourceTree = "<group>";
};
@ -184,6 +194,7 @@
isa = PBXGroup;
children = (
E40FE717221B48CE00A4223F /* Layouts */,
E41EA46D221715820068EF46 /* Models */,
E407861F2110CE70006887B1 /* Assets.xcassets */,
E408D3B7220DE8CC0006D9BE /* Extensions */,
E4D1B598220BA3C90026F233 /* Resources */,
@ -309,6 +320,14 @@
path = mpd;
sourceTree = "<group>";
};
E41EA46D221715820068EF46 /* Models */ = {
isa = PBXGroup;
children = (
E41EA46E221715910068EF46 /* Preferences.swift */,
);
path = Models;
sourceTree = "<group>";
};
E4A642DB220912FA00067D21 /* MPDClient */ = {
isa = PBXGroup;
children = (
@ -343,6 +362,7 @@
isa = PBXGroup;
children = (
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
E41EA46B221636AF0068EF46 /* PreferencesViewController.swift */,
E4E8CC932206097F0024217A /* NotificationsController.swift */,
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
E465049921E94DF500A70F4C /* WindowController.swift */,
@ -472,7 +492,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */,
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */,
E40786232110CE70006887B1 /* Main.storyboard in Resources */,
);
@ -507,11 +529,13 @@
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */,
E41EA46F221715910068EF46 /* Preferences.swift in Sources */,
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
E4E8CC9A22075D370024217A /* Song.swift in Sources */,
E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */,
E41EA46C221636AF0068EF46 /* PreferencesViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,

View File

@ -10,15 +10,46 @@ import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var preferences = Preferences()
static let mpdClient = MPDClient(
withDelegate: NotificationsController()
)
func applicationDidFinishLaunching(_ aNotification: Notification) {
AppDelegate.mpdClient.connect()
connect()
preferences.addObserver(self, forKeyPath: "mpdHost")
preferences.addObserver(self, forKeyPath: "mpdPort")
}
func applicationWillTerminate(_ aNotification: Notification) {
disconnect()
}
override func observeValue(
forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?
) {
switch keyPath {
case "mpdHost", "mpdPort":
disconnect()
connect()
default:
break
}
}
func connect() {
AppDelegate.mpdClient.connect(
host: preferences.mpdHostOrDefault,
port: preferences.mpdPortOrDefault
)
}
func disconnect() {
AppDelegate.mpdClient.disconnect()
}
}

View File

@ -25,6 +25,13 @@ class AlbumViewController: NSViewController,
name: Notification.loadedAlbums,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(clearAlbums(_:)),
name: Notification.willDisconnect,
object: AppDelegate.mpdClient
)
}
override func viewWillLayout() {
@ -42,6 +49,12 @@ class AlbumViewController: NSViewController,
albumCollectionView.reloadData()
}
@objc func clearAlbums(_ notification: Notification) {
self.albums = []
albumCollectionView.reloadData()
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return albums.count
}

View File

@ -10,7 +10,15 @@ import Foundation
class NotificationsController: MPDClientDelegate {
let notificationQueue = DispatchQueue.main
func didConnect(mpdClient: MPDClient) {
sendNotification(name: Notification.didConnect)
}
func willDisconnect(mpdClient: MPDClient) {
sendNotification(name: Notification.willDisconnect)
}
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State) {
sendNotification(
name: Notification.stateChanged,
@ -39,7 +47,7 @@ class NotificationsController: MPDClientDelegate {
)
}
private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any]) {
private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any] = [:]) {
self.notificationQueue.async {
NotificationCenter.default.post(
name: name,

View File

@ -0,0 +1,36 @@
//
// PreferencesViewController.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/14.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Cocoa
class PreferencesViewController: NSViewController {
var preferences = Preferences()
override func viewDidLoad() {
super.viewDidLoad()
if let mpdHost = preferences.mpdHost {
mpdHostField.stringValue = mpdHost
}
if let mpdPort = preferences.mpdPort {
mpdPortField.stringValue = "\(mpdPort)"
}
}
@IBAction func updateMpdHost(_ sender: NSTextField) {
preferences.mpdHost = sender.stringValue
}
@IBAction func updateMpdPort(_ sender: NSTextField) {
preferences.mpdPort = sender.integerValue
}
@IBOutlet var mpdHostField: NSTextField!
@IBOutlet var mpdPortField: NSTextField!
}

View File

@ -50,6 +50,13 @@ class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineV
name: Notification.queuePosChanged,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(clearQueue(_:)),
name: Notification.willDisconnect,
object: AppDelegate.mpdClient
)
}
@IBAction func playTrack(_ sender: Any) {
@ -95,6 +102,12 @@ class QueueViewController: NSViewController, NSOutlineViewDataSource, NSOutlineV
)
}
@objc func clearQueue(_ notification: Notification) {
self.queue = []
queueView.reloadData()
}
func setQueueIcon(_ state: MPDClient.Status.State) {
switch state {
case .playing:

View File

@ -9,6 +9,9 @@
import Foundation
extension Notification {
static let didConnect = Notification.Name("MPDClientDidConnect")
static let willDisconnect = Notification.Name("MPDClientWillDisconnect")
static let stateChanged = Notification.Name("MPDClientStateChanged")
static let queueChanged = Notification.Name("MPDClientQueueChanged")
static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged")

View File

@ -12,10 +12,8 @@ import mpdclient
class MPDClient {
var delegate: MPDClientDelegate?
let HOST = "localhost"
let PORT: UInt32 = 6600
private var connection: OpaquePointer?
private var isConnected: Bool = false
private var status: Status?
private var queue: [Song] = []
@ -46,30 +44,40 @@ class MPDClient {
self.delegate = delegate
}
func connect() {
guard let connection = mpd_connection_new(HOST, PORT, 0)
else { return }
func connect(host: String, port: Int) {
commandQueue.async { [unowned self] in
guard let connection = mpd_connection_new(host, UInt32(port), 10000),
mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS
else { return }
guard let status = mpd_run_status(connection)
else { return }
self.isConnected = true
self.connection = connection
self.status = Status(status)
guard let status = mpd_run_status(connection)
else { return }
fetchQueue()
self.connection = connection
self.status = Status(status)
fetchAllAlbums()
self.fetchQueue()
self.fetchAllAlbums()
self.idle()
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
idle()
self.delegate?.didConnect(mpdClient: self)
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
}
}
func disconnect() {
guard isConnected else { return }
noIdle()
commandQueue.async { [unowned self] in
self.delegate?.willDisconnect(mpdClient: self)
mpd_connection_free(self.connection)
self.isConnected = false
}
}
@ -102,6 +110,8 @@ class MPDClient {
}
func playTrack(queuePos: Int) {
guard isConnected else { return }
noIdle()
commandQueue.async { [unowned self] in
mpd_run_play_pos(self.connection, UInt32(queuePos))
@ -110,6 +120,8 @@ class MPDClient {
}
func queueCommand(command: Command) {
guard isConnected else { return }
noIdle()
commandQueue.async { [unowned self] in
self.sendCommand(command: command)
@ -236,15 +248,15 @@ class MPDClient {
}
}
func getLastErrorMessage() -> String! {
func getLastErrorMessage() -> String? {
if mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS {
return "no error"
return nil
}
if let errorMessage = mpd_connection_get_error_message(connection) {
return String(cString: errorMessage)
}
return "no error"
return nil
}
}

View File

@ -9,6 +9,9 @@
import Foundation
protocol MPDClientDelegate {
func didConnect(mpdClient: MPDClient)
func willDisconnect(mpdClient: MPDClient)
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
func didUpdateQueuePos(mpdClient: MPDClient, song: Int)

View File

@ -0,0 +1,47 @@
//
// Preferences.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/15.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct Preferences {
let preferences = UserDefaults.standard
var mpdHost: String? {
get {
return preferences.string(forKey: "mpdHost")
}
set {
preferences.set(newValue, forKey: "mpdHost")
}
}
var mpdPort: Int? {
get {
return preferences.value(forKey: "mpdPort") as? Int
}
set {
if (newValue.map { $0 > 0 } ?? false) {
preferences.set(newValue, forKey: "mpdPort")
} else {
preferences.removeObject(forKey: "mpdPort")
}
}
}
var mpdHostOrDefault: String {
return mpdHost ?? "127.0.0.1"
}
var mpdPortOrDefault: Int {
return mpdPort ?? 6600
}
func addObserver(_ observer: NSObject, forKeyPath keyPath: String) {
preferences.addObserver(observer, forKeyPath: keyPath, options: .new, context: nil)
}
}

View File

@ -24,7 +24,12 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW">
<connections>
<action selector="preferencesItemSelected:" target="Voe-Tx-rLC" id="bxe-zW-cho"/>
<segue destination="xYu-7w-E5x" kind="show" id="OTW-56-v9E"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Services" id="NMo-om-nkz">
<modifierMask key="keyEquivalentModifierMask"/>
@ -680,7 +685,7 @@
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
<point key="canvasLocation" x="115" y="-366"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
@ -703,10 +708,10 @@
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="EBk-sD-nG7">
<font key="font" metaFont="system"/>
<segments>
<segment image="prevTrackButton" width="32"/>
<segment image="playButton" width="48" tag="1"/>
<segment image="stopButton" width="32"/>
<segment image="nextTrackButton" width="32"/>
<segment image="prevTrackButton" width="32" enabled="NO"/>
<segment image="playButton" width="48" enabled="NO" tag="1"/>
<segment image="stopButton" width="32" enabled="NO"/>
<segment image="nextTrackButton" width="32" enabled="NO"/>
</segments>
<connections>
<action selector="handleTransportControl:" target="B8D-0N-5wS" id="HTN-5E-y75"/>
@ -758,6 +763,91 @@
</objects>
<point key="canvasLocation" x="74" y="873"/>
</scene>
<!--Window Controller-->
<scene sceneID="Rpk-bo-5kf">
<objects>
<windowController id="xYu-7w-E5x" sceneMemberID="viewController">
<window key="window" title="Preferences" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="3FN-my-6kU">
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
<rect key="contentRect" x="245" y="301" width="416" height="100"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<connections>
<outlet property="delegate" destination="xYu-7w-E5x" id="gZw-NF-8zl"/>
</connections>
</window>
<connections>
<segue destination="nYi-sw-ZNp" kind="relationship" relationship="window.shadowedContentViewController" id="607-3F-gJf"/>
</connections>
</windowController>
<customObject id="0sd-8B-etN" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="915" y="-89"/>
</scene>
<!--Preferences View Controller-->
<scene sceneID="xTC-Y5-Agk">
<objects>
<viewController id="nYi-sw-ZNp" customClass="PreferencesViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="Uwt-Lw-ILP">
<rect key="frame" x="0.0" y="0.0" width="420" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="wPm-sJ-e9E">
<rect key="frame" x="162" y="58" width="184" height="22"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" alignment="left" placeholderString="127.0.0.1" drawsBackground="YES" id="MSX-mn-2ma">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="updateMpdHost:" target="nYi-sw-ZNp" id="Y7x-N9-6ag"/>
</connections>
</textField>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="IbX-oV-soD">
<rect key="frame" x="162" y="26" width="80" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="6600" drawsBackground="YES" id="i9j-nB-bqq">
<numberFormatter key="formatter" formatterBehavior="custom10_4" numberStyle="decimal" usesGroupingSeparator="NO" minimumIntegerDigits="1" maximumIntegerDigits="2000000000" maximumFractionDigits="3" id="UiQ-gi-Hbp">
<real key="minimum" value="0.0"/>
<real key="maximum" value="65535"/>
</numberFormatter>
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<action selector="updateMpdPort:" target="nYi-sw-ZNp" id="406-EC-aO2"/>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kvB-99-zwY">
<rect key="frame" x="76" y="62" width="80" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Host:" id="AVi-g9-Irz">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AU9-wN-kbU">
<rect key="frame" x="77" y="30" width="77" height="17"/>
<autoresizingMask key="autoresizingMask"/>
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Server Port:" id="DgA-xT-2ir">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
</view>
<connections>
<outlet property="mpdHostField" destination="wPm-sJ-e9E" id="PR7-oL-tVQ"/>
<outlet property="mpdPortField" destination="IbX-oV-soD" id="VLG-DK-5N6"/>
</connections>
</viewController>
<customObject id="lzf-yO-5pP" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="917" y="217"/>
</scene>
<!--Queue View Controller-->
<scene sceneID="QcX-dC-cTZ">
<objects>