static DWORD service_start_process(struct service_entry *service_entry, HANDLE *process) { PROCESS_INFORMATION pi; STARTUPINFOW si; LPWSTR path = NULL; DWORD size; BOOL r; service_lock_exclusive(service_entry); if (service_entry->config.dwServiceType == SERVICE_KERNEL_DRIVER) { static const WCHAR winedeviceW[] = {'\\','w','i','n','e','d','e','v','i','c','e','.','e','x','e',' ',0}; DWORD len = GetSystemDirectoryW( NULL, 0 ) + sizeof(winedeviceW)/sizeof(WCHAR) + strlenW(service_entry->name); if (!(path = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) return ERROR_NOT_ENOUGH_SERVER_MEMORY; GetSystemDirectoryW( path, len ); lstrcatW( path, winedeviceW ); lstrcatW( path, service_entry->name ); } else { size = ExpandEnvironmentStringsW(service_entry->config.lpBinaryPathName,NULL,0); path = HeapAlloc(GetProcessHeap(),0,size*sizeof(WCHAR)); if (!path) return ERROR_NOT_ENOUGH_SERVER_MEMORY; ExpandEnvironmentStringsW(service_entry->config.lpBinaryPathName,path,size); } ZeroMemory(&si, sizeof(STARTUPINFOW)); si.cb = sizeof(STARTUPINFOW); if (!(service_entry->config.dwServiceType & SERVICE_INTERACTIVE_PROCESS)) { static WCHAR desktopW[] = {'_','_','w','i','n','e','s','e','r','v','i','c','e','_','w','i','n','s','t','a','t','i','o','n','\\','D','e','f','a','u','l','t',0}; si.lpDesktop = desktopW; } service_entry->status.dwCurrentState = SERVICE_START_PENDING; service_unlock(service_entry); r = CreateProcessW(NULL, path, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); HeapFree(GetProcessHeap(),0,path); if (!r) { service_lock_exclusive(service_entry); service_entry->status.dwCurrentState = SERVICE_STOPPED; service_unlock(service_entry); return GetLastError(); } service_entry->status.dwProcessId = pi.dwProcessId; *process = pi.hProcess; CloseHandle( pi.hThread ); return ERROR_SUCCESS; }
static DWORD service_wait_for_startup(struct service_entry *service_entry, HANDLE process_handle) { HANDLE handles[2] = { service_entry->status_changed_event, process_handle }; DWORD state, ret; WINE_TRACE("%p\n", service_entry); ret = WaitForMultipleObjects( 2, handles, FALSE, service_pipe_timeout ); if (ret != WAIT_OBJECT_0) return ERROR_SERVICE_REQUEST_TIMEOUT; service_lock_shared(service_entry); state = service_entry->status.dwCurrentState; service_unlock(service_entry); if (state == SERVICE_START_PENDING) { WINE_TRACE("Service state changed to SERVICE_START_PENDING\n"); return ERROR_SUCCESS; } else if (state == SERVICE_RUNNING) { WINE_TRACE("Service started successfully\n"); return ERROR_SUCCESS; } return ERROR_SERVICE_REQUEST_TIMEOUT; }
static int set_avtransport_uri(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } char *uri = upnp_get_string(event, "CurrentURI"); if (uri == NULL) { return -1; } service_lock(); char *meta = upnp_get_string(event, "CurrentURIMetaData"); // Transport URI/Meta set now, current URI/Meta when it starts playing. int requires_meta_update = replace_transport_uri_and_meta(uri, meta); if (transport_state_ == TRANSPORT_PLAYING) { // Uh, wrong state. // Usually, this should not be called while we are PLAYING, only // STOPPED or PAUSED. But if actually some controller sets this // while playing, probably the best is to update the current // current URI/Meta as well to reflect the state best. replace_current_uri_and_meta(uri, meta); } output_set_uri(uri, (requires_meta_update ? update_meta_from_stream : NULL)); service_unlock(); free(uri); free(meta); return 0; }
static int set_avtransport_uri(struct action_event *event) { int rc = 0; ENTER(); if (obtain_instanceid(event, NULL)) { LEAVE(); return -1; } char *uri = upnp_get_string(event, "CurrentURI"); if (uri == NULL) { LEAVE(); return -1; } service_lock(); char *meta = upnp_get_string(event, "CurrentURIMetaData"); int requires_meta_update = replace_transport_uri_and_meta(uri, meta); output_set_uri(uri, (requires_meta_update ? update_meta_from_stream : NULL)); notify_changed_uris(); service_unlock(); free(uri); free(meta); LEAVE(); return rc; }
static int set_next_avtransport_uri(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } char *next_uri = upnp_get_string(event, "NextURI"); if (next_uri == NULL) { return -1; } int rc = 0; service_lock(); output_set_next_uri(next_uri); replace_var(TRANSPORT_VAR_NEXT_AV_URI, next_uri); char *next_uri_meta = upnp_get_string(event, "NextURIMetaData"); if (next_uri_meta == NULL) { rc = -1; } else { replace_var(TRANSPORT_VAR_NEXT_AV_URI_META, next_uri_meta); } service_unlock(); free(next_uri); free(next_uri_meta); return rc; }
static int stop(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } service_lock(); switch (transport_state_) { case TRANSPORT_STOPPED: // nothing to change. break; case TRANSPORT_PLAYING: case TRANSPORT_TRANSITIONING: case TRANSPORT_PAUSED_RECORDING: case TRANSPORT_RECORDING: case TRANSPORT_PAUSED_PLAYBACK: output_stop(); change_transport_state(TRANSPORT_STOPPED); break; case TRANSPORT_NO_MEDIA_PRESENT: /* action not allowed in these states - error 701 */ upnp_set_error(event, UPNP_TRANSPORT_E_TRANSITION_NA, "Transition not allowed; allowed=%s", get_var(TRANSPORT_VAR_CUR_TRANSPORT_ACTIONS)); break; } service_unlock(); return 0; }
static int pause_stream(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } int rc = 0; service_lock(); switch (transport_state_) { case TRANSPORT_PAUSED_PLAYBACK: // Nothing to change. break; case TRANSPORT_PLAYING: if (output_pause()) { upnp_set_error(event, 704, "Pause failed"); rc = -1; } else { change_transport_state(TRANSPORT_PAUSED_PLAYBACK); } break; default: /* action not allowed in these states - error 701 */ upnp_set_error(event, UPNP_TRANSPORT_E_TRANSITION_NA, "Transition not allowed; allowed=%s", get_var(TRANSPORT_VAR_CUR_TRANSPORT_ACTIONS)); rc = -1; } service_unlock(); return rc; }
// We constantly update the track time to event about it to our clients. static void *thread_update_track_time(void *userdata) { const gint64 one_sec_unit = 1000000000LL; char tbuf[32]; gint64 last_duration = -1, last_position = -1; for (;;) { usleep(500000); // 500ms service_lock(); gint64 duration, position; const int pos_result = output_get_position(&duration, &position); if (pos_result == 0) { if (duration != last_duration) { print_upnp_time(tbuf, sizeof(tbuf), duration); replace_var(TRANSPORT_VAR_CUR_TRACK_DUR, tbuf); last_duration = duration; } if (position / one_sec_unit != last_position) { print_upnp_time(tbuf, sizeof(tbuf), position); replace_var(TRANSPORT_VAR_REL_TIME_POS, tbuf); last_position = position / one_sec_unit; } } service_unlock(); } return NULL; // not reached. }
static int seek(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } char *unit = upnp_get_string(event, "Unit"); if (strcmp(unit, "REL_TIME") == 0) { // This is the only thing we support right now. char *target = upnp_get_string(event, "Target"); gint64 nanos = parse_upnp_time(target); service_lock(); if (output_seek(nanos) == 0) { // TODO(hzeller): Seeking might take some time, // pretend to already be there. Should we go into // TRANSITION mode ? // (gstreamer will go into PAUSE, then PLAYING) replace_var(TRANSPORT_VAR_REL_TIME_POS, target); } service_unlock(); free(target); } free(unit); return 0; }
static int set_mute(struct action_event *event) { const char *value = upnp_get_string(event, "DesiredMute"); service_lock(); const int do_mute = atoi(value); set_mute_toggle(do_mute); change_var_and_notify(event, CONTROL_VAR_MUTE, do_mute ? "1" : "0"); service_unlock(); return 0; }
static int set_streaming_playlist(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } char *playlistdata = upnp_get_string(event, "PlaylistData"); if (playlistdata == NULL) { return -1; } int playlistdatalength = atoi(upnp_get_string(event, "PlaylistDataLength")); /*char *playlistmimetype = upnp_get_string(event, "PlaylistMIMEType"); if (playlistmimetype == NULL) { free(playlistdata); return -1; } */ char *playliststep = upnp_get_string(event, "PlaylistStep"); //Initial Continue Stop Reset if (playliststep == NULL) { free(playlistdata); // free(playlistmimetype); return -1; } int rc = 0; service_lock(); if(strcmp(playliststep, "Initial") == 0){ //TODO: rc = write_playlist(event, playlistdata, playlistdatalength, "w"); } if(strcmp(playliststep, "Continue") == 0){ rc = write_playlist(event, playlistdata, playlistdatalength, "a"); } if(strcmp(playliststep, "Stop") == 0){ //TODO: Indicates that the current streaming playlist operation will end when all pending playlist data at then device is consumed. if(playlistdata) rc = write_playlist(event, playlistdata, playlistdatalength, "a"); output_set_playlist(M3U_STREAMINGPLAYLIST_PATH); } if(strcmp(playliststep, "Reset") == 0){ //TODO: Indicates that processing of the current streaming playlist ends immediately. any pending playlist data for the streaming playlist is discarded. output_set_playlist(M3U_STREAMINGPLAYLIST_PATH); } if(!rc) replace_var(TRANSPORT_VAR_AAT_PLAYLIST_STEP, playliststep); service_unlock(); if(playlistdata) free(playlistdata); free(playliststep); return rc; }
static void shared_meta_time_change(uint32_t total, uint32_t current) { char tbuf[32]; service_lock(); print_upnp_time(tbuf, sizeof(tbuf), total); replace_var(TRANSPORT_VAR_CUR_TRACK_DUR, tbuf); print_upnp_time(tbuf, sizeof(tbuf), current); replace_var(TRANSPORT_VAR_REL_TIME_POS, tbuf); service_unlock(); }
static int set_volume_db(struct action_event *event) { const char *str_decibel_in = upnp_get_string(event, "DesiredVolume"); service_lock(); float raw_decibel_in = atof(str_decibel_in); float decibel = change_volume_decibel(event, raw_decibel_in); output_set_volume(exp(decibel / 20 * log(10))); service_unlock(); return 0; }
void release_service(struct service_entry *service) { if (InterlockedDecrement(&service->ref_count) == 0 && is_marked_for_delete(service)) { scmdatabase_lock_exclusive(service->db); service_lock_exclusive(service); scmdatabase_remove_service(service->db, service); service_unlock(service); scmdatabase_unlock(service->db); free_service_entry(service); } }
// Callback from our output if the song meta data changed. static void update_meta_from_stream(const struct SongMetaData *meta) { if (meta->title == NULL || strlen(meta->title) == 0) { return; } const char *original_xml = get_var(TRANSPORT_VAR_AV_URI_META); char *didl = SongMetaData_to_DIDL(meta, original_xml); service_lock(); replace_var(TRANSPORT_VAR_AV_URI_META, didl); replace_var(TRANSPORT_VAR_CUR_TRACK_META, didl); service_unlock(); free(didl); }
// Callback from our output if the song meta data changed. static void update_meta_from_stream(const struct SongMetaData *meta) { if (meta->title == NULL || strlen(meta->title) == 0) { return; } const char *original_xml = transport_values[TRANSPORT_VAR_AV_URI_META]; char *didl = SongMetaData_to_DIDL(original_xml, meta); service_lock(); replace_var(TRANSPORT_VAR_AV_URI_META, didl); replace_var(TRANSPORT_VAR_CUR_TRACK_META, didl); notify_changed_uris(); service_unlock(); free(didl); }
static int play(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } int rc = 0; service_lock(); switch (transport_state_) { // even if already playing, we must restart playback when Play // action is executed. Kinsky, for example, would not send Stop // when changing tracks and playback is already in progress - // it will just set new URI, and send Play command case TRANSPORT_PLAYING: case TRANSPORT_STOPPED: // If we were stopped before, we start a new song now. So just // set the time to zero now; otherwise we will see the old // value of the previous song until it updates some fractions // of a second later. replace_var(TRANSPORT_VAR_REL_TIME_POS, kZeroTime); /* >>> fall through */ case TRANSPORT_PAUSED_PLAYBACK: if (output_play(&inform_play_transition_from_output)) { upnp_set_error(event, 704, "Playing failed"); rc = -1; } else { change_transport_state(TRANSPORT_PLAYING); const char *av_uri = get_var(TRANSPORT_VAR_AV_URI); const char *av_meta = get_var(TRANSPORT_VAR_AV_URI_META); replace_current_uri_and_meta(av_uri, av_meta); } break; case TRANSPORT_NO_MEDIA_PRESENT: case TRANSPORT_TRANSITIONING: case TRANSPORT_PAUSED_RECORDING: case TRANSPORT_RECORDING: /* action not allowed in these states - error 701 */ upnp_set_error(event, UPNP_TRANSPORT_E_TRANSITION_NA, "Transition not allowed; allowed=%s", get_var(TRANSPORT_VAR_CUR_TRANSPORT_ACTIONS)); rc = -1; break; } service_unlock(); return rc; }
void service_terminate(struct service_entry *service) { service_lock_exclusive(service); TerminateProcess(service->process, 0); CloseHandle(service->process); service->process = NULL; CloseHandle(service->status_changed_event); service->status_changed_event = NULL; CloseHandle(service->control_mutex); service->control_mutex = NULL; CloseHandle(service->control_pipe); service->control_pipe = INVALID_HANDLE_VALUE; service->status.dwProcessId = 0; service->status.dwCurrentState = SERVICE_STOPPED; service_unlock(service); }
static int play(struct action_event *event) { int rc = 0; ENTER(); if (obtain_instanceid(event, NULL)) { LEAVE(); return -1; } service_lock(); switch (transport_state_) { case TRANSPORT_PLAYING: // For clients that didn't get it. change_and_notify_transport(TRANSPORT_PLAYING); // Set TransportPlaySpeed to '1' break; case TRANSPORT_STOPPED: case TRANSPORT_PAUSED_PLAYBACK: if (output_play(&inform_done_playing)) { upnp_set_error(event, 704, "Playing failed"); rc = -1; } else { change_and_notify_transport(TRANSPORT_PLAYING); } // Set TransportPlaySpeed to '1' break; case TRANSPORT_NO_MEDIA_PRESENT: case TRANSPORT_TRANSITIONING: case TRANSPORT_PAUSED_RECORDING: case TRANSPORT_RECORDING: /* action not allowed in these states - error 701 */ upnp_set_error(event, UPNP_TRANSPORT_E_TRANSITION_NA, "Transition not allowed"); rc = -1; break; } service_unlock(); LEAVE(); return rc; }
//TODO:FIXME static int set_playmode(struct action_event *event) { if (obtain_instanceid(event, NULL) < 0) { return -1; } char *playmode = upnp_get_string(event, "NewPlayMode"); if (playmode == NULL) { return -1; } //TODO: support this playmode? error 712 // This is the only thing we support right now. service_lock(); replace_var(TRANSPORT_VAR_CUR_PLAY_MODE, playmode); service_unlock(); free(playmode); return 0; }
void timerPlay(char *uri) { service_lock(); output_stop(); change_transport_state(TRANSPORT_STOPPED); output_set_uri(uri, NULL); replace_var(TRANSPORT_VAR_REL_TIME_POS, kZeroTime); if (output_play(&inform_play_transition_from_output)) { } else { change_transport_state(TRANSPORT_PLAYING); const char *av_uri = get_var(TRANSPORT_VAR_AV_URI); const char *av_meta = get_var(TRANSPORT_VAR_AV_URI_META); replace_current_uri_and_meta(av_uri, av_meta); } service_unlock(); }
static void inform_done_playing(enum PlayFeedback fb) { printf("---------------------------------- Done playing....%d\n", fb); service_lock(); switch (fb) { case PLAY_STOPPED: replace_transport_uri_and_meta("", ""); change_and_notify_transport(TRANSPORT_STOPPED); notify_changed_uris(); break; case PLAY_STARTED_NEXT_STREAM: replace_transport_uri_and_meta( transport_values[TRANSPORT_VAR_NEXT_AV_URI], transport_values[TRANSPORT_VAR_NEXT_AV_URI_META]); replace_var(TRANSPORT_VAR_NEXT_AV_URI, ""); replace_var(TRANSPORT_VAR_NEXT_AV_URI_META, ""); notify_changed_uris(); break; } service_unlock(); }
static void inform_play_transition_from_output(enum PlayFeedback fb) { service_lock(); switch (fb) { case PLAY_STOPPED: replace_transport_uri_and_meta("", ""); replace_current_uri_and_meta("", ""); change_transport_state(TRANSPORT_STOPPED); break; case PLAY_STARTED_NEXT_STREAM: { const char *av_uri = get_var(TRANSPORT_VAR_NEXT_AV_URI); const char *av_meta = get_var(TRANSPORT_VAR_NEXT_AV_URI_META); replace_transport_uri_and_meta(av_uri, av_meta); replace_current_uri_and_meta(av_uri, av_meta); replace_var(TRANSPORT_VAR_NEXT_AV_URI, ""); replace_var(TRANSPORT_VAR_NEXT_AV_URI_META, ""); break; } } service_unlock(); }
static int set_volume(struct action_event *event) { const char *volume = upnp_get_string(event, "DesiredVolume"); service_lock(); int volume_level = atoi(volume); // range 0..100 if (volume_level < volume_range.min) volume_level = volume_range.min; if (volume_level > volume_range.max) volume_level = volume_range.max; const float decibel = volume_level_to_decibel(volume_level); char db_volume[10]; snprintf(db_volume, sizeof(db_volume), "%d", (int) (256 * decibel)); Log_info("control", "Setting %d volume-db to %d" , volume_level,(int)decibel); const double fraction = exp(decibel / 20 * log(10)); change_volume(volume, db_volume); output_set_volume(fraction, volume_level); set_mute_toggle(volume_level == 0); service_unlock(); return 0; }
static int set_volume(struct action_event *event) { const char *volume = upnp_get_string(event, "DesiredVolume"); service_lock(); int volume_level = atoi(volume); // range 0..100 if (volume_level < volume_range.min) volume_level = volume_range.min; if (volume_level > volume_range.max) volume_level = volume_range.max; const float decibel = volume_level_to_decibel(volume_level); char db_volume[10]; snprintf(db_volume, sizeof(db_volume), "%d", (int) (256 * decibel)); const double fraction = exp(decibel / 20 * log(10)); fprintf(stderr, "Setting volume to #%d = %.2fdb (%.4f)\n", volume_level, decibel, fraction); change_volume_and_notify(event, volume, db_volume); output_set_volume(fraction); set_mute_toggle(volume_level == 0); service_unlock(); return 0; }
static int pause_stream(struct action_event *event) { int rc = 0; ENTER(); if (obtain_instanceid(event, NULL)) { LEAVE(); return -1; } service_lock(); switch (transport_state_) { case TRANSPORT_PAUSED_PLAYBACK: // For clients that didn't get it. change_and_notify_transport(TRANSPORT_PAUSED_PLAYBACK); break; case TRANSPORT_PLAYING: if (output_pause()) { upnp_set_error(event, 704, "Pause failed"); rc = -1; } else { change_and_notify_transport(TRANSPORT_PAUSED_PLAYBACK); } // Set TransportPlaySpeed to '1' break; default: /* action not allowed in these states - error 701 */ upnp_set_error(event, UPNP_TRANSPORT_E_TRANSITION_NA, "Transition not allowed"); rc = -1; } service_unlock(); LEAVE(); return rc; }
static int set_next_avtransport_uri(struct action_event *event) { int rc = 0; char *value; ENTER(); if (obtain_instanceid(event, NULL)) { LEAVE(); return -1; } value = upnp_get_string(event, "NextURI"); if (value == NULL) { LEAVE(); return -1; } service_lock(); output_set_next_uri(value); change_var_and_notify(TRANSPORT_VAR_NEXT_AV_URI, value); printf("%s: NextURI='%s'\n", __FUNCTION__, value); free(value); value = upnp_get_string(event, "NextURIMetaData"); if (value == NULL) { rc = -1; } else { change_var_and_notify(TRANSPORT_VAR_NEXT_AV_URI_META, value); free(value); } service_unlock(); LEAVE(); return rc; }
static int stop(struct action_event *event) { ENTER(); if (obtain_instanceid(event, NULL)) { return -1; } service_lock(); switch (transport_state_) { case TRANSPORT_STOPPED: // For clients that didn't get it. change_and_notify_transport(TRANSPORT_STOPPED); break; case TRANSPORT_PLAYING: case TRANSPORT_TRANSITIONING: case TRANSPORT_PAUSED_RECORDING: case TRANSPORT_RECORDING: case TRANSPORT_PAUSED_PLAYBACK: output_stop(); change_and_notify_transport(TRANSPORT_STOPPED); // Set TransportPlaySpeed to '1' break; case TRANSPORT_NO_MEDIA_PRESENT: /* action not allowed in these states - error 701 */ upnp_set_error(event, UPNP_TRANSPORT_E_TRANSITION_NA, "Transition not allowed"); break; } service_unlock(); LEAVE(); return 0; }
static DWORD service_wait_for_startup(struct service_entry *service_entry, HANDLE process_handle) { WINE_TRACE("%p\n", service_entry); for (;;) { DWORD dwCurrentStatus; HANDLE handles[2] = { service_entry->status_changed_event, process_handle }; DWORD ret; ret = WaitForMultipleObjects(sizeof(handles)/sizeof(handles[0]), handles, FALSE, 20000); if (ret != WAIT_OBJECT_0) return ERROR_SERVICE_REQUEST_TIMEOUT; service_lock_shared(service_entry); dwCurrentStatus = service_entry->status.dwCurrentState; service_unlock(service_entry); if (dwCurrentStatus == SERVICE_RUNNING) { WINE_TRACE("Service started successfully\n"); return ERROR_SUCCESS; } if (dwCurrentStatus != SERVICE_START_PENDING) return ERROR_SERVICE_REQUEST_TIMEOUT; } }
static DWORD service_start_process(struct service_entry *service_entry, HANDLE *process) { PROCESS_INFORMATION pi; STARTUPINFOW si; LPWSTR path = NULL; DWORD size; BOOL r; service_lock_exclusive(service_entry); if (!env) { HANDLE htok; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY|TOKEN_DUPLICATE, &htok)) CreateEnvironmentBlock(&env, htok, FALSE); if (!env) WINE_ERR("failed to create services environment\n"); } size = ExpandEnvironmentStringsW(service_entry->config.lpBinaryPathName,NULL,0); path = HeapAlloc(GetProcessHeap(),0,size*sizeof(WCHAR)); if (!path) { service_unlock(service_entry); return ERROR_NOT_ENOUGH_SERVER_MEMORY; } ExpandEnvironmentStringsW(service_entry->config.lpBinaryPathName,path,size); if (service_entry->config.dwServiceType == SERVICE_KERNEL_DRIVER) { static const WCHAR winedeviceW[] = {'\\','w','i','n','e','d','e','v','i','c','e','.','e','x','e',' ',0}; WCHAR system_dir[MAX_PATH]; DWORD type, len; GetSystemDirectoryW( system_dir, MAX_PATH ); if (is_win64) { if (!GetBinaryTypeW( path, &type )) { HeapFree( GetProcessHeap(), 0, path ); service_unlock(service_entry); return GetLastError(); } if (type == SCS_32BIT_BINARY) GetSystemWow64DirectoryW( system_dir, MAX_PATH ); } len = strlenW( system_dir ) + sizeof(winedeviceW)/sizeof(WCHAR) + strlenW(service_entry->name); HeapFree( GetProcessHeap(), 0, path ); if (!(path = HeapAlloc( GetProcessHeap(), 0, len * sizeof(WCHAR) ))) { service_unlock(service_entry); return ERROR_NOT_ENOUGH_SERVER_MEMORY; } lstrcpyW( path, system_dir ); lstrcatW( path, winedeviceW ); lstrcatW( path, service_entry->name ); } ZeroMemory(&si, sizeof(STARTUPINFOW)); si.cb = sizeof(STARTUPINFOW); if (!(service_entry->config.dwServiceType & SERVICE_INTERACTIVE_PROCESS)) { static WCHAR desktopW[] = {'_','_','w','i','n','e','s','e','r','v','i','c','e','_','w','i','n','s','t','a','t','i','o','n','\\','D','e','f','a','u','l','t',0}; si.lpDesktop = desktopW; } service_entry->status.dwCurrentState = SERVICE_START_PENDING; service_unlock(service_entry); r = CreateProcessW(NULL, path, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, env, NULL, &si, &pi); HeapFree(GetProcessHeap(),0,path); if (!r) { service_lock_exclusive(service_entry); service_entry->status.dwCurrentState = SERVICE_STOPPED; service_unlock(service_entry); return GetLastError(); } service_entry->status.dwProcessId = pi.dwProcessId; service_entry->process = pi.hProcess; *process = pi.hProcess; CloseHandle( pi.hThread ); return ERROR_SUCCESS; }