From e8ebb1af91435674784dcbbc3207331fcdac7458 Mon Sep 17 00:00:00 2001 From: Denis Krjuchkov Date: Mon, 20 Sep 2010 18:28:08 -0700 Subject: main: Add Windows Service support I've added PIPE_EVENT_SHUTDOWN because calling g_main_loop_quit() do not work when called from another thread. Main thread was sleeping in g_poll() so I needed some way to wake it up. By some strange reason call close(event_pipe[0]) in event_pipe_deinit() hangs. In current implementation that code never reached so that was not a problem :-) I've added a conditional to leave event_pipe[0] open on Win32. --- Makefile.am | 1 + src/event_pipe.c | 3 ++ src/event_pipe.h | 3 ++ src/main.c | 28 +++++++++- src/main.h | 41 +++++++++++++++ src/main_win32.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 src/main_win32.c diff --git a/Makefile.am b/Makefile.am index 01abe578f..eeeffc90e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -278,6 +278,7 @@ src_mpd_SOURCES = \ src/log.c \ src/ls.c \ src/main.c \ + src/main_win32.c \ src/event_pipe.c \ src/daemon.c \ src/AudioCompress/compress.c \ diff --git a/src/event_pipe.c b/src/event_pipe.c index af6517cd4..286256f96 100644 --- a/src/event_pipe.c +++ b/src/event_pipe.c @@ -116,7 +116,10 @@ void event_pipe_deinit(void) g_source_remove(event_pipe_source_id); g_io_channel_unref(event_channel); +#ifndef WIN32 + /* By some strange reason this call hangs on Win32 */ close(event_pipe[0]); +#endif close(event_pipe[1]); } diff --git a/src/event_pipe.h b/src/event_pipe.h index 6c3d8c169..923544bf4 100644 --- a/src/event_pipe.h +++ b/src/event_pipe.h @@ -44,6 +44,9 @@ enum pipe_event { /** a hardware mixer plugin has detected a change */ PIPE_EVENT_MIXER, + /** shutdown requested */ + PIPE_EVENT_SHUTDOWN, + PIPE_EVENT_MAX }; diff --git a/src/main.c b/src/main.c index c93a3f615..34310927d 100644 --- a/src/main.c +++ b/src/main.c @@ -269,7 +269,25 @@ idle_event_emitted(void) client_manager_idle_add(flags); } +/** + * event_pipe callback function for PIPE_EVENT_SHUTDOWN + */ +static void +shutdown_event_emitted(void) +{ + g_main_loop_quit(main_loop); +} + int main(int argc, char *argv[]) +{ +#ifdef WIN32 + return win32_main(argc, argv); +#else + return mpd_main(argc, argv); +#endif +} + +int mpd_main(int argc, char *argv[]) { struct options options; clock_t start; @@ -324,6 +342,7 @@ int main(int argc, char *argv[]) event_pipe_init(); event_pipe_register(PIPE_EVENT_IDLE, idle_event_emitted); + event_pipe_register(PIPE_EVENT_SHUTDOWN, shutdown_event_emitted); path_global_init(); glue_mapper_init(); @@ -392,10 +411,17 @@ int main(int argc, char *argv[]) playlist_state_restore() */ pc_update_audio(); - /* run the main loop */ +#ifdef WIN32 + win32_app_started(); +#endif + /* run the main loop */ g_main_loop_run(main_loop); +#ifdef WIN32 + win32_app_stopping(); +#endif + /* cleanup */ g_main_loop_unref(main_loop); diff --git a/src/main.h b/src/main.h index c1d3f3621..9b9cba018 100644 --- a/src/main.h +++ b/src/main.h @@ -28,4 +28,45 @@ extern GMainLoop *main_loop; extern GCond *main_cond; +/** + * A entry point for application. + * On non-Windows platforms this is called directly from main() + * On Windows platform this is called from win32_main() + * after doing some initialization. + */ +int mpd_main(int argc, char *argv[]); + +#ifdef WIN32 + +/** + * If program is run as windows service performs nessesary initialization + * and then calls mpd_main() with specified arguments. + * If program is run as a regular application calls mpd_main() immediately. + */ +int +win32_main(int argc, char *argv[]); + +/** + * When running as a service reports to service control manager + * that our service is started. + * When running as a console application enables console handler that will + * trigger PIPE_EVENT_SHUTDOWN when user closes console window + * or presses Ctrl+C. + * This function should be called just before entering main loop. + */ +void +win32_app_started(void); + +/** + * When running as a service reports to service control manager + * that our service is about to stop. + * When running as a console application enables console handler that will + * catch all shutdown requests and ignore them. + * This function should be called just after leaving main loop. + */ +void +win32_app_stopping(void); + +#endif + #endif diff --git a/src/main_win32.c b/src/main_win32.c new file mode 100644 index 000000000..b2d6a0d12 --- /dev/null +++ b/src/main_win32.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2003-2010 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "main.h" + +#ifdef WIN32 + +#include "event_pipe.h" + +#include + +#define WINVER 0x0501 +#include + +static int service_argc; +static char **service_argv; +static char service_name[] = ""; +static BOOL ignore_console_events; +static SERVICE_STATUS_HANDLE service_handle; + +static void WINAPI +service_main(DWORD argc, CHAR *argv[]); + +static SERVICE_TABLE_ENTRY service_registry[] = { + {service_name, service_main}, + {NULL, NULL} +}; + +static void +service_notify_status(DWORD status_code) +{ + SERVICE_STATUS current_status; + + current_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + current_status.dwControlsAccepted = status_code == SERVICE_START_PENDING + ? 0 + : SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + + current_status.dwCurrentState = status_code; + current_status.dwWin32ExitCode = NO_ERROR; + current_status.dwCheckPoint = 0; + current_status.dwWaitHint = 1000; + + SetServiceStatus(service_handle, ¤t_status); +} + +static DWORD WINAPI +service_dispatcher(G_GNUC_UNUSED DWORD control, G_GNUC_UNUSED DWORD event_type, + G_GNUC_UNUSED void *event_data, G_GNUC_UNUSED void *context) +{ + switch (control) { + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + event_pipe_emit(PIPE_EVENT_SHUTDOWN); + return NO_ERROR; + default: + return NO_ERROR; + } +} + +static void WINAPI +service_main(G_GNUC_UNUSED DWORD argc, G_GNUC_UNUSED CHAR *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_handle = + RegisterServiceCtrlHandlerEx(service_name, + service_dispatcher, NULL); + + if (service_handle == 0) { + error_code = GetLastError(); + error_message = g_win32_error_message(error_code); + g_error("RegisterServiceCtrlHandlerEx() failed: %s", + error_message); + } + + service_notify_status(SERVICE_START_PENDING); + mpd_main(service_argc, service_argv); + service_notify_status(SERVICE_STOPPED); +} + +static BOOL WINAPI +console_handler(DWORD event) +{ + switch (event) { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + if (!ignore_console_events) + event_pipe_emit(PIPE_EVENT_SHUTDOWN); + return TRUE; + default: + return FALSE; + } +} + +int win32_main(int argc, char *argv[]) +{ + DWORD error_code; + gchar* error_message; + + service_argc = argc; + service_argv = argv; + + if (StartServiceCtrlDispatcher(service_registry)) + return 0; /* run as service successefully */ + + error_code = GetLastError(); + if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { + /* running as console app */ + SetConsoleTitle("Music Player Daemon"); + ignore_console_events = TRUE; + SetConsoleCtrlHandler(console_handler, TRUE); + return mpd_main(argc, argv); + } + + error_message = g_win32_error_message(error_code); + g_error("StartServiceCtrlDispatcher() failed: %s", error_message); +} + +void win32_app_started() +{ + if (service_handle != 0) + service_notify_status(SERVICE_RUNNING); + else + ignore_console_events = FALSE; +} + +void win32_app_stopping() +{ + if (service_handle != 0) + service_notify_status(SERVICE_STOP_PENDING); + else + ignore_console_events = TRUE; +} + +#endif -- cgit v1.2.3