Browse Source

Initial commit

This is mostly sdtui code ported over from GLib to liberty,
with some MPD code from desktop-tools.

It tracks the current song and that's it.
Přemysl Janouch 2 years ago
commit
ec339eb0ff
Signed by: Přemysl Janouch <p.janouch@gmail.com> GPG Key ID: B715679E3A361BE6
11 changed files with 2299 additions and 0 deletions
  1. 9
    0
      .gitignore
  2. 6
    0
      .gitmodules
  3. 15
    0
      LICENSE
  4. 79
    0
      README.adoc
  5. 17
    0
      cmake/FindNcursesw.cmake
  6. 10
    0
      cmake/FindUnistring.cmake
  7. 10
    0
      config.h.in
  8. 1
    0
      liberty
  9. 645
    0
      mpd.c
  10. 1506
    0
      nncmpp.c
  11. 1
    0
      termo

+ 9
- 0
.gitignore View File

@@ -0,0 +1,9 @@
1
+# Build files
2
+/build
3
+
4
+# Qt Creator files
5
+/CMakeLists.txt.user*
6
+/nncmpp.config
7
+/nncmpp.files
8
+/nncmpp.creator*
9
+/nncmpp.includes

+ 6
- 0
.gitmodules View File

@@ -0,0 +1,6 @@
1
+[submodule "termo"]
2
+	path = termo
3
+	url = git://github.com/pjanouch/termo.git
4
+[submodule "liberty"]
5
+	path = liberty
6
+	url = git://github.com/pjanouch/liberty.git

+ 15
- 0
LICENSE View File

@@ -0,0 +1,15 @@
1
+ Copyright (c) 2016, Přemysl Janouch <p.janouch@gmail.com>
2
+ All rights reserved.
3
+ 
4
+ Permission to use, copy, modify, and/or distribute this software for any
5
+ purpose with or without fee is hereby granted, provided that the above
6
+ copyright notice and this permission notice appear in all copies.
7
+ 
8
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
11
+ SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
13
+ OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
14
+ CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
+

+ 79
- 0
README.adoc View File

@@ -0,0 +1,79 @@
1
+nncmpp
2
+======
3
+
4
+'nncmpp' is yet another MPD client.  It does exactly what I want it to, more
5
+specifically it's a simplified TUI version of Sonata so that I don't need to
6
+run an ugly undeveloped Python application.
7
+
8
+If it's not obvious enough, the name a pun on all those ridiculous client names,
9
+and should be pronounced as "nincompoop".
10
+
11
+Currently it's under development and doesn't work in any sense yet.
12
+
13
+Packages
14
+--------
15
+Regular releases are sporadic.  git master should be stable enough.  You can get
16
+a package with the latest development version from Archlinux's AUR, or from
17
+openSUSE Build Service for the rest of mainstream distributions.  Consult the
18
+list of repositories and their respective links at:
19
+
20
+https://build.opensuse.org/project/repositories/home:pjanouch:git
21
+
22
+Building and Running
23
+--------------------
24
+Build dependencies: CMake, pkg-config, liberty (included), termo (included) +
25
+Runtime dependencies: ncursesw, libunistring
26
+
27
+ $ git clone --recursive https://github.com/pjanouch/nncmpp.git
28
+ $ mkdir nncmpp/build
29
+ $ cd nncmpp/build
30
+ $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug
31
+ $ make
32
+
33
+To install the application, you can do either the usual:
34
+
35
+ # make install
36
+
37
+Or you can try telling CMake to make a package for you.  For Debian it is:
38
+
39
+ $ cpack -G DEB
40
+ # dpkg -i nncmpp-*.deb
41
+
42
+Note that for versions of CMake before 2.8.9, you need to prefix `cpack` with
43
+`fakeroot` or file ownership will end up wrong.
44
+
45
+Having the program installed, create a configuration file and run it.
46
+
47
+Configuration
48
+-------------
49
+Create _~/.config/nncmpp/nncmpp.conf_ with contents like the following:
50
+
51
+....
52
+settings = {
53
+	address = "localhost"
54
+	password = "<your password>"
55
+	root = "~/Music"
56
+}
57
+colors = {
58
+	header = "reverse"
59
+	header_active = "underline"
60
+	even = "16 231"
61
+	odd = "16 255"
62
+}
63
+....
64
+
65
+Contributing and Support
66
+------------------------
67
+Use this project's GitHub to report any bugs, request features, or submit pull
68
+requests.  If you want to discuss this project, or maybe just hang out with
69
+the developer, feel free to join me at irc://irc.janouch.name, channel #dev.
70
+
71
+License
72
+-------
73
+'nncmpp' is written by Přemysl Janouch <p.janouch@gmail.com>.
74
+
75
+You may use the software under the terms of the ISC license, the text of which
76
+is included within the package, or, at your option, you may relicense the work
77
+under the MIT or the Modified BSD License, as listed at the following site:
78
+
79
+http://www.gnu.org/licenses/license-list.html

+ 17
- 0
cmake/FindNcursesw.cmake View File

@@ -0,0 +1,17 @@
1
+# Public Domain
2
+
3
+find_package (PkgConfig REQUIRED)
4
+pkg_check_modules (NCURSESW QUIET ncursesw)
5
+
6
+# OpenBSD doesn't provide a pkg-config file
7
+set (required_vars NCURSESW_LIBRARIES)
8
+if (NOT NCURSESW_FOUND)
9
+	find_library (NCURSESW_LIBRARIES NAMES ncursesw)
10
+	find_path (NCURSESW_INCLUDE_DIRS ncurses.h)
11
+	list (APPEND required_vars NCURSESW_INCLUDE_DIRS)
12
+endif (NOT NCURSESW_FOUND)
13
+
14
+include (FindPackageHandleStandardArgs)
15
+FIND_PACKAGE_HANDLE_STANDARD_ARGS (NCURSESW DEFAULT_MSG ${required_vars})
16
+
17
+mark_as_advanced (NCURSESW_LIBRARIES NCURSESW_INCLUDE_DIRS)

+ 10
- 0
cmake/FindUnistring.cmake View File

@@ -0,0 +1,10 @@
1
+# Public Domain
2
+
3
+find_path (UNISTRING_INCLUDE_DIRS unistr.h)
4
+find_library (UNISTRING_LIBRARIES NAMES unistring libunistring)
5
+
6
+include (FindPackageHandleStandardArgs)
7
+FIND_PACKAGE_HANDLE_STANDARD_ARGS (UNISTRING DEFAULT_MSG
8
+	UNISTRING_INCLUDE_DIRS UNISTRING_LIBRARIES)
9
+
10
+mark_as_advanced (UNISTRING_LIBRARIES UNISTRING_INCLUDE_DIRS)

+ 10
- 0
config.h.in View File

@@ -0,0 +1,10 @@
1
+#ifndef CONFIG_H
2
+#define CONFIG_H
3
+
4
+#define PROGRAM_NAME "${CMAKE_PROJECT_NAME}"
5
+#define PROGRAM_VERSION "${project_VERSION}"
6
+
7
+#cmakedefine HAVE_RESIZETERM
8
+
9
+#endif  // ! CONFIG_H
10
+

+ 1
- 0
liberty

@@ -0,0 +1 @@
1
+Subproject commit 952cf985dca6a97ee662f3b189788089abd2ef57

+ 645
- 0
mpd.c View File

@@ -0,0 +1,645 @@
1
+// Copied from desktop-tools, should go to liberty if it proves useful
2
+
3
+// --- MPD client interface ----------------------------------------------------
4
+
5
+// This is a rather thin MPD client interface intended for basic tasks
6
+
7
+#define MPD_SUBSYSTEM_TABLE(XX)                 \
8
+	XX (DATABASE,         0, "database")        \
9
+	XX (UPDATE,           1, "update")          \
10
+	XX (STORED_PLAYLIST,  2, "stored_playlist") \
11
+	XX (PLAYLIST,         3, "playlist")        \
12
+	XX (PLAYER,           4, "player")          \
13
+	XX (MIXER,            5, "mixer")           \
14
+	XX (OUTPUT,           6, "output")          \
15
+	XX (OPTIONS,          7, "options")         \
16
+	XX (STICKER,          8, "sticker")         \
17
+	XX (SUBSCRIPTION,     9, "subscription")    \
18
+	XX (MESSAGE,         10, "message")
19
+
20
+enum mpd_subsystem
21
+{
22
+#define XX(a, b, c) MPD_SUBSYSTEM_ ## a = (1 << b),
23
+	MPD_SUBSYSTEM_TABLE (XX)
24
+#undef XX
25
+	MPD_SUBSYSTEM_MAX
26
+};
27
+
28
+static const char *mpd_subsystem_names[] =
29
+{
30
+#define XX(a, b, c) [b] = c,
31
+	MPD_SUBSYSTEM_TABLE (XX)
32
+#undef XX
33
+};
34
+
35
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
36
+
37
+enum mpd_client_state
38
+{
39
+	MPD_DISCONNECTED,                   ///< Not connected
40
+	MPD_CONNECTING,                     ///< Currently connecting
41
+	MPD_CONNECTED                       ///< Connected
42
+};
43
+
44
+struct mpd_response
45
+{
46
+	bool success;                       ///< OK or ACK
47
+
48
+	// ACK-only fields:
49
+
50
+	int error;                          ///< Numeric error value (ack.h)
51
+	int list_offset;                    ///< Offset of command in list
52
+	char *current_command;              ///< Name of the erroring command
53
+	char *message_text;                 ///< Error message
54
+};
55
+
56
+/// Task completion callback
57
+typedef void (*mpd_client_task_cb) (const struct mpd_response *response,
58
+	const struct str_vector *data, void *user_data);
59
+
60
+struct mpd_client_task
61
+{
62
+	LIST_HEADER (struct mpd_client_task)
63
+
64
+	mpd_client_task_cb callback;        ///< Callback on completion
65
+	void *user_data;                    ///< User data
66
+};
67
+
68
+struct mpd_client
69
+{
70
+	struct poller *poller;              ///< Poller
71
+
72
+	// Connection:
73
+
74
+	enum mpd_client_state state;        ///< Connection state
75
+	struct connector *connector;        ///< Connection establisher
76
+
77
+	int socket;                         ///< MPD socket
78
+	struct str read_buffer;             ///< Input yet to be processed
79
+	struct str write_buffer;            ///< Outut yet to be be sent out
80
+	struct poller_fd socket_event;      ///< We can read from the socket
81
+
82
+	struct poller_timer timeout_timer;  ///< Connection seems to be dead
83
+
84
+	// Protocol:
85
+
86
+	bool got_hello;                     ///< Got the OK MPD hello message
87
+
88
+	bool idling;                        ///< Sent idle as the last command
89
+	unsigned idling_subsystems;         ///< Subsystems we're idling for
90
+	bool in_list;                       ///< We're inside a command list
91
+
92
+	struct mpd_client_task *tasks;      ///< Task queue
93
+	struct mpd_client_task *tasks_tail; ///< Tail of task queue
94
+	struct str_vector data;             ///< Data from last command
95
+
96
+	// User configuration:
97
+
98
+	void *user_data;                    ///< User data for callbacks
99
+
100
+	/// Callback after connection has been successfully established
101
+	void (*on_connected) (void *user_data);
102
+
103
+	/// Callback for general failures or even normal disconnection;
104
+	/// the interface is reinitialized
105
+	void (*on_failure) (void *user_data);
106
+
107
+	/// Callback to receive "idle" updates.
108
+	/// Remember to restart the idle if needed.
109
+	void (*on_event) (unsigned subsystems, void *user_data);
110
+};
111
+
112
+static void mpd_client_reset (struct mpd_client *self);
113
+static void mpd_client_destroy_connector (struct mpd_client *self);
114
+
115
+static void
116
+mpd_client_init (struct mpd_client *self, struct poller *poller)
117
+{
118
+	memset (self, 0, sizeof *self);
119
+
120
+	self->poller = poller;
121
+	self->socket = -1;
122
+
123
+	str_init (&self->read_buffer);
124
+	str_init (&self->write_buffer);
125
+
126
+	str_vector_init (&self->data);
127
+
128
+	poller_fd_init (&self->socket_event, poller, -1);
129
+	poller_timer_init (&self->timeout_timer, poller);
130
+}
131
+
132
+static void
133
+mpd_client_free (struct mpd_client *self)
134
+{
135
+	// So that we don't have to repeat most of the stuff
136
+	mpd_client_reset (self);
137
+
138
+	str_free (&self->read_buffer);
139
+	str_free (&self->write_buffer);
140
+
141
+	str_vector_free (&self->data);
142
+}
143
+
144
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
145
+
146
+/// Reinitialize the interface so that you can reconnect anew
147
+static void
148
+mpd_client_reset (struct mpd_client *self)
149
+{
150
+	if (self->state == MPD_CONNECTING)
151
+		mpd_client_destroy_connector (self);
152
+
153
+	if (self->socket != -1)
154
+		xclose (self->socket);
155
+	self->socket = -1;
156
+
157
+	self->socket_event.closed = true;
158
+	poller_fd_reset (&self->socket_event);
159
+	poller_timer_reset (&self->timeout_timer);
160
+
161
+	str_reset (&self->read_buffer);
162
+	str_reset (&self->write_buffer);
163
+
164
+	str_vector_reset (&self->data);
165
+
166
+	self->got_hello = false;
167
+	self->idling = false;
168
+	self->idling_subsystems = 0;
169
+	self->in_list = false;
170
+
171
+	LIST_FOR_EACH (struct mpd_client_task, iter, self->tasks)
172
+		free (iter);
173
+	self->tasks = self->tasks_tail = NULL;
174
+
175
+	self->state = MPD_DISCONNECTED;
176
+}
177
+
178
+static void
179
+mpd_client_fail (struct mpd_client *self)
180
+{
181
+	mpd_client_reset (self);
182
+	if (self->on_failure)
183
+		self->on_failure (self->user_data);
184
+}
185
+
186
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
187
+
188
+static bool
189
+mpd_client_parse_response (const char *p, struct mpd_response *response)
190
+{
191
+	if (!strcmp (p, "OK"))
192
+		return response->success = true;
193
+	if (!strcmp (p, "list_OK"))
194
+		// TODO: either implement this or fail the connection properly
195
+		hard_assert (!"command_list_ok_begin not implemented");
196
+
197
+	char *end = NULL;
198
+	if (*p++ != 'A' || *p++ != 'C' || *p++ != 'K' || *p++ != ' ' || *p++ != '[')
199
+		return false;
200
+
201
+	errno = 0;
202
+	response->error = strtoul (p, &end, 10);
203
+	if (errno != 0 || end == p)
204
+		return false;
205
+	p = end;
206
+	if (*p++ != '@')
207
+		return false;
208
+
209
+	errno = 0;
210
+	response->list_offset = strtoul (p, &end, 10);
211
+	if (errno != 0 || end == p)
212
+		return false;
213
+	p = end;
214
+	if (*p++ != ']' || *p++ != ' ' || *p++ != '{' || !(end = strchr (p, '}')))
215
+		return false;
216
+
217
+	response->current_command = xstrndup (p, end - p);
218
+	p = end + 1;
219
+
220
+	if (*p++ != ' ')
221
+		return false;
222
+
223
+	response->message_text = xstrdup (p);
224
+	response->success = false;
225
+	return true;
226
+}
227
+
228
+static void
229
+mpd_client_dispatch (struct mpd_client *self, struct mpd_response *response)
230
+{
231
+	struct mpd_client_task *task;
232
+	if (!(task = self->tasks))
233
+		return;
234
+
235
+	if (task->callback)
236
+		task->callback (response, &self->data, task->user_data);
237
+	str_vector_reset (&self->data);
238
+
239
+	LIST_UNLINK_WITH_TAIL (self->tasks, self->tasks_tail, task);
240
+	free (task);
241
+}
242
+
243
+static bool
244
+mpd_client_parse_hello (struct mpd_client *self, const char *line)
245
+{
246
+	const char hello[] = "OK MPD ";
247
+	if (strncmp (line, hello, sizeof hello - 1))
248
+	{
249
+		print_debug ("invalid MPD hello message");
250
+		return false;
251
+	}
252
+
253
+	// TODO: call "on_connected" now.  We should however also set up a timer
254
+	//   so that we don't wait on this message forever.
255
+	return self->got_hello = true;
256
+}
257
+
258
+static bool
259
+mpd_client_parse_line (struct mpd_client *self, const char *line)
260
+{
261
+	print_debug ("MPD >> %s", line);
262
+
263
+	if (!self->got_hello)
264
+		return mpd_client_parse_hello (self, line);
265
+
266
+	struct mpd_response response;
267
+	memset (&response, 0, sizeof response);
268
+	if (mpd_client_parse_response (line, &response))
269
+	{
270
+		mpd_client_dispatch (self, &response);
271
+		free (response.current_command);
272
+		free (response.message_text);
273
+	}
274
+	else
275
+		str_vector_add (&self->data, line);
276
+	return true;
277
+}
278
+
279
+/// All output from MPD commands seems to be in a trivial "key: value" format
280
+static char *
281
+mpd_client_parse_kv (char *line, char **value)
282
+{
283
+	char *sep;
284
+	if (!(sep = strstr (line, ": ")))
285
+		return NULL;
286
+
287
+	*sep = 0;
288
+	*value = sep + 2;
289
+	return line;
290
+}
291
+
292
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
293
+
294
+static void
295
+mpd_client_update_poller (struct mpd_client *self)
296
+{
297
+	poller_fd_set (&self->socket_event,
298
+		self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN);
299
+}
300
+
301
+static bool
302
+mpd_client_process_input (struct mpd_client *self)
303
+{
304
+	// Split socket input at newlines and process them separately
305
+	struct str *rb = &self->read_buffer;
306
+	char *start = rb->str, *end = start + rb->len;
307
+	for (char *p = start; p < end; p++)
308
+	{
309
+		if (*p != '\n')
310
+			continue;
311
+
312
+		*p = 0;
313
+		if (!mpd_client_parse_line (self, start))
314
+			return false;
315
+		start = p + 1;
316
+	}
317
+
318
+	str_remove_slice (rb, 0, start - rb->str);
319
+	return true;
320
+}
321
+
322
+static void
323
+mpd_client_on_ready (const struct pollfd *pfd, void *user_data)
324
+{
325
+	(void) pfd;
326
+
327
+	struct mpd_client *self = user_data;
328
+	if (socket_io_try_read  (self->socket, &self->read_buffer)  != SOCKET_IO_OK
329
+	 || !mpd_client_process_input (self)
330
+	 || socket_io_try_write (self->socket, &self->write_buffer) != SOCKET_IO_OK)
331
+		mpd_client_fail (self);
332
+	else
333
+		mpd_client_update_poller (self);
334
+}
335
+
336
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
337
+
338
+static bool
339
+mpd_client_must_quote_char (char c)
340
+{
341
+	return (unsigned char) c <= ' ' || c == '"' || c == '\'';
342
+}
343
+
344
+static bool
345
+mpd_client_must_quote (const char *s)
346
+{
347
+	if (!*s)
348
+		return true;
349
+	for (; *s; s++)
350
+		if (mpd_client_must_quote_char (*s))
351
+			return true;
352
+	return false;
353
+}
354
+
355
+static void
356
+mpd_client_quote (const char *s, struct str *output)
357
+{
358
+	str_append_c (output, '"');
359
+	for (; *s; s++)
360
+	{
361
+		if (mpd_client_must_quote_char (*s))
362
+			str_append_c (output, '\\');
363
+		str_append_c (output, *s);
364
+	}
365
+	str_append_c (output, '"');
366
+}
367
+
368
+/// Beware that delivery of the event isn't deferred and you musn't make
369
+/// changes to the interface while processing the event!
370
+static void
371
+mpd_client_add_task
372
+	(struct mpd_client *self, mpd_client_task_cb cb, void *user_data)
373
+{
374
+	// This only has meaning with command_list_ok_begin, and then it requires
375
+	// special handling (all in-list tasks need to be specially marked and
376
+	// later flushed if an early ACK or OK arrives).
377
+	hard_assert (!self->in_list);
378
+
379
+	struct mpd_client_task *task = xcalloc (1, sizeof *self);
380
+	task->callback = cb;
381
+	task->user_data = user_data;
382
+	LIST_APPEND_WITH_TAIL (self->tasks, self->tasks_tail, task);
383
+}
384
+
385
+/// Send a command.  Remember to call mpd_client_add_task() to handle responses,
386
+/// unless the command is being sent in a list.
387
+static void mpd_client_send_command
388
+	(struct mpd_client *self, const char *command, ...) ATTRIBUTE_SENTINEL;
389
+
390
+static void
391
+mpd_client_send_commandv (struct mpd_client *self, char **commands)
392
+{
393
+	// Automatically interrupt idle mode
394
+	if (self->idling)
395
+	{
396
+		poller_timer_reset (&self->timeout_timer);
397
+
398
+		self->idling = false;
399
+		self->idling_subsystems = 0;
400
+		mpd_client_send_command (self, "noidle", NULL);
401
+	}
402
+
403
+	struct str line;
404
+	str_init (&line);
405
+
406
+	for (; *commands; commands++)
407
+	{
408
+		if (line.len)
409
+			str_append_c (&line, ' ');
410
+
411
+		if (mpd_client_must_quote (*commands))
412
+			mpd_client_quote (*commands, &line);
413
+		else
414
+			str_append (&line, *commands);
415
+	}
416
+
417
+	print_debug ("MPD << %s", line.str);
418
+	str_append_c (&line, '\n');
419
+	str_append_str (&self->write_buffer, &line);
420
+	str_free (&line);
421
+
422
+	mpd_client_update_poller (self);
423
+}
424
+
425
+static void
426
+mpd_client_send_command (struct mpd_client *self, const char *command, ...)
427
+{
428
+	struct str_vector v;
429
+	str_vector_init (&v);
430
+
431
+	va_list ap;
432
+	va_start (ap, command);
433
+	for (; command; command = va_arg (ap, const char *))
434
+		str_vector_add (&v, command);
435
+	va_end (ap);
436
+
437
+	mpd_client_send_commandv (self, v.vector);
438
+	str_vector_free (&v);
439
+}
440
+
441
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
442
+
443
+static void
444
+mpd_client_list_begin (struct mpd_client *self)
445
+{
446
+	hard_assert (!self->in_list);
447
+	mpd_client_send_command (self, "command_list_begin", NULL);
448
+	self->in_list = true;
449
+}
450
+
451
+/// End a list of commands.  Remember to call mpd_client_add_task()
452
+/// to handle the summary response.
453
+static void
454
+mpd_client_list_end (struct mpd_client *self)
455
+{
456
+	hard_assert (self->in_list);
457
+	mpd_client_send_command (self, "command_list_end", NULL);
458
+	self->in_list = false;
459
+}
460
+
461
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
462
+
463
+static bool
464
+mpd_resolve_subsystem (const char *name, unsigned *output)
465
+{
466
+	for (size_t i = 0; i < N_ELEMENTS (mpd_subsystem_names); i++)
467
+		if (!strcasecmp_ascii (name, mpd_subsystem_names[i]))
468
+		{
469
+			*output |= 1 << i;
470
+			return true;
471
+		}
472
+	return false;
473
+}
474
+
475
+static void
476
+mpd_client_on_idle_return (const struct mpd_response *response,
477
+	const struct str_vector *data, void *user_data)
478
+{
479
+	(void) response;
480
+
481
+	struct mpd_client *self = user_data;
482
+	unsigned subsystems = 0;
483
+	for (size_t i = 0; i < data->len; i++)
484
+	{
485
+		char *value, *key;
486
+		if (!(key = mpd_client_parse_kv (data->vector[i], &value)))
487
+			print_debug ("%s: %s", "erroneous MPD output", data->vector[i]);
488
+		else if (strcasecmp_ascii (key, "changed"))
489
+			print_debug ("%s: %s", "unexpected idle key", key);
490
+		else if (!mpd_resolve_subsystem (value, &subsystems))
491
+			print_debug ("%s: %s", "unknown subsystem", value);
492
+	}
493
+
494
+	// Not resetting "idling" here, we may send an extra "noidle" no problem
495
+	if (self->on_event && subsystems)
496
+		self->on_event (subsystems, self->user_data);
497
+}
498
+
499
+static void mpd_client_idle (struct mpd_client *self, unsigned subsystems);
500
+
501
+static void
502
+mpd_client_on_timeout (void *user_data)
503
+{
504
+	struct mpd_client *self = user_data;
505
+	unsigned subsystems = self->idling_subsystems;
506
+
507
+	// Just sending this out should bring a dead connection down over TCP
508
+	// TODO: set another timer to make sure the ping reply arrives
509
+	mpd_client_send_command (self, "ping", NULL);
510
+	mpd_client_add_task (self, NULL, NULL);
511
+
512
+	// Restore the incriminating idle immediately
513
+	mpd_client_idle (self, subsystems);
514
+}
515
+
516
+/// When not expecting to send any further commands, you should call this
517
+/// in order to keep the connection alive.  Or to receive updates.
518
+static void
519
+mpd_client_idle (struct mpd_client *self, unsigned subsystems)
520
+{
521
+	hard_assert (!self->in_list);
522
+
523
+	struct str_vector v;
524
+	str_vector_init (&v);
525
+
526
+	str_vector_add (&v, "idle");
527
+	for (size_t i = 0; i < N_ELEMENTS (mpd_subsystem_names); i++)
528
+		if (subsystems & (1 << i))
529
+			str_vector_add (&v, mpd_subsystem_names[i]);
530
+
531
+	mpd_client_send_commandv (self, v.vector);
532
+	str_vector_free (&v);
533
+
534
+	self->timeout_timer.dispatcher = mpd_client_on_timeout;
535
+	self->timeout_timer.user_data = self;
536
+	poller_timer_set (&self->timeout_timer, 5 * 60 * 1000);
537
+
538
+	mpd_client_add_task (self, mpd_client_on_idle_return, self);
539
+	self->idling = true;
540
+	self->idling_subsystems = subsystems;
541
+}
542
+
543
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
544
+
545
+static void
546
+mpd_client_finish_connection (struct mpd_client *self, int socket)
547
+{
548
+	set_blocking (socket, false);
549
+	self->socket = socket;
550
+	self->state = MPD_CONNECTED;
551
+
552
+	poller_fd_init (&self->socket_event, self->poller, self->socket);
553
+	self->socket_event.dispatcher = mpd_client_on_ready;
554
+	self->socket_event.user_data = self;
555
+
556
+	mpd_client_update_poller (self);
557
+
558
+	if (self->on_connected)
559
+		self->on_connected (self->user_data);
560
+}
561
+
562
+static void
563
+mpd_client_destroy_connector (struct mpd_client *self)
564
+{
565
+	if (self->connector)
566
+		connector_free (self->connector);
567
+	free (self->connector);
568
+	self->connector = NULL;
569
+
570
+	// Not connecting anymore
571
+	self->state = MPD_DISCONNECTED;
572
+}
573
+
574
+static void
575
+mpd_client_on_connector_failure (void *user_data)
576
+{
577
+	struct mpd_client *self = user_data;
578
+	mpd_client_destroy_connector (self);
579
+	mpd_client_fail (self);
580
+}
581
+
582
+static void
583
+mpd_client_on_connector_connected
584
+	(void *user_data, int socket, const char *host)
585
+{
586
+	(void) host;
587
+
588
+	struct mpd_client *self = user_data;
589
+	mpd_client_destroy_connector (self);
590
+	mpd_client_finish_connection (self, socket);
591
+}
592
+
593
+static bool
594
+mpd_client_connect_unix (struct mpd_client *self, const char *address,
595
+	struct error **e)
596
+{
597
+	int fd = socket (AF_UNIX, SOCK_STREAM, 0);
598
+	if (fd == -1)
599
+	{
600
+		error_set (e, "%s: %s", "socket", strerror (errno));
601
+		return false;
602
+	}
603
+
604
+	// Expand tilde if needed
605
+	char *expanded = resolve_filename (address, xstrdup);
606
+
607
+	struct sockaddr_un sun;
608
+	sun.sun_family = AF_UNIX;
609
+	strncpy (sun.sun_path, expanded, sizeof sun.sun_path);
610
+	sun.sun_path[sizeof sun.sun_path - 1] = 0;
611
+
612
+	free (expanded);
613
+
614
+	if (connect (fd, (struct sockaddr *) &sun, sizeof sun))
615
+	{
616
+		error_set (e, "%s: %s", "connect", strerror (errno));
617
+		return false;
618
+	}
619
+
620
+	mpd_client_finish_connection (self, fd);
621
+	return true;
622
+}
623
+
624
+static bool
625
+mpd_client_connect (struct mpd_client *self, const char *address,
626
+	const char *service, struct error **e)
627
+{
628
+	hard_assert (self->state == MPD_DISCONNECTED);
629
+
630
+	// If it looks like a path, assume it's a UNIX socket
631
+	if (strchr (address, '/'))
632
+		return mpd_client_connect_unix (self, address, e);
633
+
634
+	struct connector *connector = xmalloc (sizeof *connector);
635
+	connector_init (connector, self->poller);
636
+	self->connector = connector;
637
+
638
+	connector->user_data    = self;
639
+	connector->on_connected = mpd_client_on_connector_connected;
640
+	connector->on_failure   = mpd_client_on_connector_failure;
641
+
642
+	connector_add_target (connector, address, service);
643
+	self->state = MPD_CONNECTING;
644
+	return true;
645
+}

+ 1506
- 0
nncmpp.c
File diff suppressed because it is too large
View File


+ 1
- 0
termo

@@ -0,0 +1 @@
1
+Subproject commit 4282f3715c7d4307f57c27edf66874762bdee858

Loading…
Cancel
Save