/* * This file is part of Moonlight Embedded. * * Copyright (C) 2015-2017 Iwan Timmer * * Moonlight 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 3 of the License, or * (at your option) any later version. * * Moonlight 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 Moonlight; if not, see . */ #include "loop.h" #include "connection.h" #include "configuration.h" #include "config.h" #include "platform.h" #include "sdl.h" #include "audio/audio.h" #include "video/video.h" #include "input/mapping.h" #include "input/evdev.h" #include "input/udev.h" #ifdef HAVE_LIBCEC #include "input/cec.h" #endif #ifdef HAVE_SDL #include "input/sdl.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void applist(PSERVER_DATA server) { PAPP_LIST list = NULL; if (gs_applist(server, &list) != GS_OK) { fprintf(stderr, "Can't get app list\n"); return; } for (int i = 1;list != NULL;i++) { printf("%d. %s\n", i, list->name); list = list->next; } } static int get_app_id(PSERVER_DATA server, const char *name) { PAPP_LIST list = NULL; if (gs_applist(server, &list) != GS_OK) { fprintf(stderr, "Can't get app list\n"); return -1; } while (list != NULL) { if (strcmp(list->name, name) == 0) return list->id; list = list->next; } return -1; } static void stream(PSERVER_DATA server, PCONFIGURATION config, enum platform system) { int appId = get_app_id(server, config->app); if (appId<0) { fprintf(stderr, "Can't find app %s\n", config->app); exit(-1); } int gamepads = 0; gamepads += evdev_gamepads; #ifdef HAVE_SDL gamepads += sdl_gamepads; #endif int gamepad_mask; for (int i = 0; i < gamepads && i < 4; i++) gamepad_mask = (gamepad_mask << 1) + 1; int ret = gs_start_app(server, &config->stream, appId, config->sops, config->localaudio, gamepad_mask); if (ret < 0) { if (ret == GS_NOT_SUPPORTED_4K) fprintf(stderr, "Server doesn't support 4K\n"); else if (ret == GS_NOT_SUPPORTED_MODE) fprintf(stderr, "Server doesn't support %dx%d (%d fps) or try --unsupported option\n", config->stream.width, config->stream.height, config->stream.fps); else if (ret == GS_ERROR) fprintf(stderr, "Gamestream error: %s\n", gs_error); else fprintf(stderr, "Errorcode starting app: %d\n", ret); exit(-1); } int drFlags = 0; if (config->fullscreen) drFlags |= DISPLAY_FULLSCREEN; if (config->debug_level > 0) { printf("Stream %d x %d, %d fps, %d kbps\n", config->stream.width, config->stream.height, config->stream.fps, config->stream.bitrate); connection_debug = true; } platform_start(system); LiStartConnection(&server->serverInfo, &config->stream, &connection_callbacks, platform_get_video(system), platform_get_audio(system, config->audio_device), NULL, drFlags, config->audio_device, 0); if (IS_EMBEDDED(system)) { evdev_start(); loop_main(); evdev_stop(); } #ifdef HAVE_SDL else if (system == SDL) sdl_loop(); #endif LiStopConnection(); if (config->quitappafter) { if (config->debug_level > 0) printf("Sending app quit request ...\n"); gs_quit_app(server); } platform_stop(system); } static void help() { printf("Moonlight Embedded %d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); printf("Usage: moonlight [action] (options) [host]\n"); printf(" moonlight [configfile]\n"); printf("\n Actions\n\n"); printf("\tpair\t\t\tPair device with computer\n"); printf("\tunpair\t\t\tUnpair device with computer\n"); printf("\tstream\t\t\tStream computer to device\n"); printf("\tlist\t\t\tList available games and applications\n"); printf("\tquit\t\t\tQuit the application or game being streamed\n"); printf("\tmap\t\t\tCreate mapping for gamepad\n"); printf("\thelp\t\t\tShow this help\n"); printf("\n Global Options\n\n"); printf("\t-config \tLoad configuration file\n"); printf("\t-save \t\tSave configuration file\n"); printf("\t-verbose\t\tEnable verbose output\n"); printf("\t-debug\t\t\tEnable verbose and debug output\n"); printf("\n Streaming options\n\n"); printf("\t-720\t\t\tUse 1280x720 resolution [default]\n"); printf("\t-1080\t\t\tUse 1920x1080 resolution\n"); printf("\t-4k\t\t\tUse 3840x2160 resolution\n"); printf("\t-width \t\tHorizontal resolution (default 1280)\n"); printf("\t-height \tVertical resolution (default 720)\n"); printf("\t-fps \t\tSpecify the fps to use (default -1)\n"); printf("\t-bitrate \tSpecify the bitrate in Kbps\n"); printf("\t-packetsize \tSpecify the maximum packetsize in bytes\n"); printf("\t-codec \t\tSelect used codec: auto/h264/h265 (default auto)\n"); printf("\t-remote\t\t\tEnable remote optimizations\n"); printf("\t-app \t\tName of app to stream\n"); printf("\t-nosops\t\t\tDon't allow GFE to modify game settings\n"); printf("\t-localaudio\t\tPlay audio locally\n"); printf("\t-surround\t\tStream 5.1 surround sound (requires GFE 2.7)\n"); printf("\t-keydir \tLoad encryption keys from directory\n"); printf("\t-mapping \t\tUse as gamepad mappings configuration file\n"); printf("\t-platform \tSpecify system used for audio, video and input: pi/imx/aml/rk/x11/x11_vdpau/sdl/fake (default auto)\n"); printf("\t-unsupported\t\tTry streaming if GFE version or options are unsupported\n"); printf("\t-quitappafter\t\tSend quit app request to remote after quitting session\n"); #if defined(HAVE_SDL) || defined(HAVE_X11) printf("\n WM options (SDL and X11 only)\n\n"); printf("\t-windowed\t\tDisplay screen in a window\n"); #endif #ifdef HAVE_EMBEDDED printf("\n I/O options (Not for SDL)\n\n"); printf("\t-input \t\tUse as input. Can be used multiple times\n"); printf("\t-audio \t\tUse as audio output device\n"); #endif printf("\nUse Ctrl+Alt+Shift+Q or Play+Back+LeftShoulder+RightShoulder to exit streaming session\n\n"); exit(0); } static void pair_check(PSERVER_DATA server) { if (!server->paired) { fprintf(stderr, "You must pair with the PC first\n"); exit(-1); } } int main(int argc, char* argv[]) { CONFIGURATION config; config_parse(argc, argv, &config); if (config.action == NULL || strcmp("help", config.action) == 0) help(); if (config.debug_level > 0) printf("Moonlight Embedded %d.%d.%d (%s)\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, COMPILE_OPTIONS); if (strcmp("map", config.action) == 0) { if (config.inputsCount != 1) { printf("You need to specify one input device using -input.\n"); exit(-1); } evdev_create(config.inputs[0], NULL, config.debug_level > 0); evdev_map(config.inputs[0]); exit(0); } if (config.address == NULL) { config.address = malloc(MAX_ADDRESS_SIZE); if (config.address == NULL) { perror("Not enough memory"); exit(-1); } config.address[0] = 0; printf("Searching for server...\n"); gs_discover_server(config.address); if (config.address[0] == 0) { fprintf(stderr, "Autodiscovery failed. Specify an IP address next time.\n"); exit(-1); } } char host_config_file[128]; sprintf(host_config_file, "hosts/%s.conf", config.address); if (access(host_config_file, R_OK) != -1) config_file_parse(host_config_file, &config); SERVER_DATA server; printf("Connect to %s...\n", config.address); int ret; if ((ret = gs_init(&server, config.address, config.key_dir, config.debug_level, config.unsupported)) == GS_OUT_OF_MEMORY) { fprintf(stderr, "Not enough memory\n"); exit(-1); } else if (ret == GS_ERROR) { fprintf(stderr, "Gamestream error: %s\n", gs_error); exit(-1); } else if (ret == GS_INVALID) { fprintf(stderr, "Invalid data received from server: %s\n", gs_error); exit(-1); } else if (ret == GS_UNSUPPORTED_VERSION) { fprintf(stderr, "Unsupported version: %s\n", gs_error); exit(-1); } else if (ret != GS_OK) { fprintf(stderr, "Can't connect to server %s\n", config.address); exit(-1); } if (config.debug_level > 0) printf("NVIDIA %s, GFE %s (%s, %s)\n", server.gpuType, server.serverInfo.serverInfoGfeVersion, server.gsVersion, server.serverInfo.serverInfoAppVersion); if (strcmp("list", config.action) == 0) { pair_check(&server); applist(&server); } else if (strcmp("stream", config.action) == 0) { pair_check(&server); enum platform system = platform_check(config.platform); if (config.debug_level > 0) printf("Platform %s\n", platform_name(system)); if (system == 0) { fprintf(stderr, "Platform '%s' not found\n", config.platform); exit(-1); } else if (system == SDL && config.audio_device != NULL) { fprintf(stderr, "You can't select a audio device for SDL\n"); exit(-1); } config.stream.supportsHevc = config.codec != CODEC_H264 && (config.codec == CODEC_HEVC || platform_supports_hevc(system)); if (IS_EMBEDDED(system)) { char* mapping_env = getenv("SDL_GAMECONTROLLERCONFIG"); if (config.mapping == NULL && mapping_env == NULL) { fprintf(stderr, "Please specify mapping file as default mapping could not be found.\n"); exit(-1); } struct mapping* mappings = NULL; if (config.mapping != NULL) mappings = mapping_load(config.mapping, config.debug_level > 0); if (mapping_env != NULL) { struct mapping* map = mapping_parse(mapping_env); map->next = mappings; mappings = map; } for (int i=0;i 0) printf("Add input %s...\n", config.inputs[i]); evdev_create(config.inputs[i], mappings, config.debug_level > 0); } udev_init(!inputAdded, mappings, config.debug_level > 0); evdev_init(); #ifdef HAVE_LIBCEC cec_init(); #endif /* HAVE_LIBCEC */ } #ifdef HAVE_SDL else if (system == SDL) { if (config.inputsCount > 0) { fprintf(stderr, "You can't select input devices as SDL will automatically use all available controllers\n"); exit(-1); } sdl_init(config.stream.width, config.stream.height, config.fullscreen); sdlinput_init(config.mapping); } #endif stream(&server, &config, system); } else if (strcmp("pair", config.action) == 0) { char pin[5]; sprintf(pin, "%d%d%d%d", (int)random() % 10, (int)random() % 10, (int)random() % 10, (int)random() % 10); printf("Please enter the following PIN on the target PC: %s\n", pin); if (gs_pair(&server, &pin[0]) != GS_OK) { fprintf(stderr, "Failed to pair to server: %s\n", gs_error); } else { printf("Succesfully paired\n"); } } else if (strcmp("unpair", config.action) == 0) { if (gs_unpair(&server) != GS_OK) { fprintf(stderr, "Failed to unpair to server: %s\n", gs_error); } else { printf("Succesfully unpaired\n"); } } else if (strcmp("quit", config.action) == 0) { pair_check(&server); gs_quit_app(&server); } else fprintf(stderr, "%s is not a valid action\n", config.action); }