diff --git a/Makefile b/Makefile index 072258db1c..15354e7e93 100644 --- a/Makefile +++ b/Makefile @@ -838,6 +838,9 @@ ${PROG}: $(OBJS) $(ALLDEPS) ${BUILDDIR}/support/dataroot/wd.o ${PROG}.bundle: $(OBJS) $(BUNDLE_OBJS) $(ALLDEPS) ${BUILDDIR}/support/dataroot/bundle.o $(LINKER) -o $@ $(OBJS) ${BUILDDIR}/support/dataroot/bundle.o $(BUNDLE_OBJS) $(LDFLAGS) ${LDFLAGS_cfg} +${PROG}.sbundle: ${PROG}.bundle $(ALLDEPS) + $(STRIP) -o $@ $< + ${PROG}.datadir: $(OBJS) $(ALLDEPS) ${BUILDDIR}/support/dataroot/datadir.o $(LINKER) -o $@ $(OBJS) ${BUILDDIR}/support/dataroot/datadir.o $(LDFLAGS) ${LDFLAGS_cfg} diff --git a/configure.linux b/configure.linux index 8351ed8147..09a9c74e83 100755 --- a/configure.linux +++ b/configure.linux @@ -91,10 +91,8 @@ enable libfontconfig enable libpulse enable lirc enable stdin -enable gu enable avahi enable libxss -enable libxv enable openssl enable locatedb enable vdpau @@ -385,57 +383,6 @@ if enabled libpulse; then fi -# -# GTK2 UI -# -if enabled gu; then - if pkg-config gtk+-2.0 && pkg-config gthread-2.0; then - - if disabled libx11; then - echo "GU depends on libx11" - die - fi - - if disabled libxext; then - echo "GU depends on libxext" - die - fi - - - echo >>${CONFIG_MAK} "LDFLAGS_cfg += " `pkg-config --libs gtk+-2.0` - echo >>${CONFIG_MAK} "CFLAGS_GTK += " `pkg-config --cflags gtk+-2.0` - echo "Using GTK+: `pkg-config --modversion gtk+-2.0`" - - echo >>${CONFIG_MAK} "LDFLAGS_cfg += " `pkg-config --libs gthread-2.0` - echo >>${CONFIG_MAK} "CFLAGS_GTK += " `pkg-config --cflags gthread-2.0` - echo "Using GTK Threading: `pkg-config --modversion gthread-2.0`" - - else - echo "GTK2 not found. Unable to build GU (GTK user interface)." - echo "To compile without it, configure with: --disable-gu" - die - fi -else - disable libxv -fi - -# -# webkit -# -if enabled webkit; then - if pkg-config webkit-1.0; then - echo >>${CONFIG_MAK} "LDFLAGS_cfg += " `pkg-config --libs webkit-1.0` - echo >>${CONFIG_MAK} "CFLAGS_GTK += " `pkg-config --cflags webkit-1.0` - echo "Using WebKit: `pkg-config --modversion webkit-1.0`" - enable webpopup - else - echo "libwebkitgtk development files not found." - echo "To compile without it, configure with: --disable-webkit" - die - fi -fi - - # # libXss (Screen saver control library) # @@ -481,21 +428,6 @@ if enabled libxrandr; then fi -# -# libxv (Xvideo) -# -if enabled libxv; then - if pkg-config xv; then - echo >>${CONFIG_MAK} "CFLAGS_cfg += " `pkg-config --cflags xv` - echo >>${CONFIG_MAK} "LDFLAGS_cfg += " `pkg-config --libs xv` - echo "Using libXv: `pkg-config --modversion xv`" - else - echo "libXv not found. Unable to build with Xv support." - die - fi -fi - - # # AVAHI # diff --git a/src/arch/linux/linux.h b/src/arch/linux/linux.h index 0a9a092a16..f503920dbc 100644 --- a/src/arch/linux/linux.h +++ b/src/arch/linux/linux.h @@ -28,9 +28,4 @@ void linux_webpopup_check(void); void linux_init_monitors(void); -struct prop; - -typedef struct linux_ui { - void *(*start)(struct prop *nav); - struct prop *(*stop)(void *ui); -} linux_ui_t; +void glw_x11_main(int *running); diff --git a/src/arch/linux/linux.mk b/src/arch/linux/linux.mk index 6e67a5bd36..513ff74d58 100644 --- a/src/arch/linux/linux.mk +++ b/src/arch/linux/linux.mk @@ -14,7 +14,6 @@ SRCS += src/arch/linux/linux_main.c \ src/networking/net_ifaddr.c \ src/fileaccess/fa_opencookie.c \ src/fileaccess/fa_fs.c \ - src/prop/prop_glib_courier.c \ src/arch/linux/linux_process_monitor.c \ SRCS += src/htsmsg/persistent_file.c @@ -67,3 +66,49 @@ uninstall: # gtk-update-icon-cache $(prefix)/share/icons/hicolor/ +# +# +# + +SNAPROOT=$(BUILDDIR)/snaproot + +SNAPDEPS = \ + $(SNAPROOT)/bin/movian \ + $(SNAPROOT)/meta/snap.yaml \ + $(SNAPROOT)/meta/gui/movian.desktop \ + $(SNAPROOT)/usr/share/movian/icons/movian-128.png \ + $(SNAPROOT)/command-movian.wrapper \ + $(SNAPROOT)/lib/libXss.so.1 \ + +$(SNAPROOT)/meta/snap.yaml: support/snap.yaml + @mkdir -p $(dir $@) + sed >$@ -e s/@@VERSION@@/${VERSION}/g -e s/@@APPNAME@@/${APPNAMEUSER}/g -e s/@@VERCODE@@/${NUMVER}/g $< + + +$(SNAPROOT)/lib/%: /usr/lib/x86_64-linux-gnu/% + @mkdir -p $(dir $@) + cp $< $@ + +$(SNAPROOT)/bin/movian: $(BUILDDIR)/movian.sbundle + @mkdir -p $(dir $@) + cp $< $@ + +$(SNAPROOT)/command-movian.wrapper: support/command-movian.wrapper + @mkdir -p $(dir $@) + cp $< $@ + +$(SNAPROOT)/meta/gui/movian.desktop: support/movian.desktop + @mkdir -p $(dir $@) + cp $< $@ + +$(SNAPROOT)/usr/share/movian/icons/movian-128.png: support/artwork/movian-128.png + @mkdir -p $(dir $@) + cp $< $@ + + +$(BUILDDIR)/movian.snap: $(SNAPDEPS) + rm -f $@ + mksquashfs $(SNAPROOT) $@ -comp xz + +snap: $(BUILDDIR)/movian.snap + diff --git a/src/arch/linux/linux_main.c b/src/arch/linux/linux_main.c index 10606e3d71..985c0590f7 100644 --- a/src/arch/linux/linux_main.c +++ b/src/arch/linux/linux_main.c @@ -18,8 +18,6 @@ * For more information, contact andreas@lonelycoder.com */ #include -#include -#include #include "main.h" #include "arch/arch.h" @@ -28,113 +26,12 @@ #include "prop/prop.h" #include "navigator.h" #include "service.h" -#include "prop/prop_glib_courier.h" -hts_mutex_t gdk_mutex; -prop_courier_t *glibcourier; +// https://www.uninformativ.de/blog/postings/2017-04-02/0/POSTING-en.html static void add_xdg_paths(void); -/** - * - */ -static void -gdk_obtain(void) -{ - hts_mutex_lock(&gdk_mutex); -} - - -/** - * - */ -static void -gdk_release(void) -{ - hts_mutex_unlock(&gdk_mutex); -} - -static int running; -extern const linux_ui_t ui_glw, ui_gu; -static const linux_ui_t *ui_wanted = &ui_glw, *ui_current; - - -/** - * - */ -int -arch_stop_req(void) -{ - running = 0; - g_main_context_wakeup(g_main_context_default()); - return 0; -} - - -/** - * - */ -static void -switch_ui(void) -{ - if(ui_current == &ui_glw) - ui_wanted = &ui_gu; - else - ui_wanted = &ui_glw; -} - - -/** - * - */ -static void -mainloop(void) -{ - void *aux = NULL; - - running = 1; - - while(running) { - - if(ui_current != ui_wanted) { - - prop_t *nav; - - if(ui_current != NULL) { - nav = ui_current->stop(aux); - } else { - nav = NULL; - } - - ui_current = ui_wanted; - - aux= ui_current->start(nav); - } - -#if ENABLE_WEBPOPUP - linux_webpopup_check(); -#endif - gtk_main_iteration(); - } - - if(ui_current != NULL) { - prop_t *nav = ui_current->stop(aux); - if(nav != NULL) - prop_destroy(nav); - } -} - - -/** - * - */ -static void -linux_global_eventsink(void *opaque, event_t *e) -{ - if(event_is_action(e, ACTION_SWITCH_UI)) - switch_ui(); -} - +static int running = 1; /** * Linux main @@ -147,14 +44,8 @@ main(int argc, char **argv) posix_init(); XInitThreads(); - hts_mutex_init(&gdk_mutex); - g_thread_init(NULL); - gdk_threads_set_lock_functions(gdk_obtain, gdk_release); - - gdk_threads_init(); - gdk_threads_enter(); - gtk_init(&argc, &argv); + // g_thread_init(NULL); parse_opts(argc, argv); @@ -162,20 +53,9 @@ main(int argc, char **argv) main_init(); - if(gconf.ui && !strcmp(gconf.ui, "gu")) - ui_wanted = &ui_gu; - - glibcourier = glib_courier_create(g_main_context_default()); - - prop_subscribe(0, - PROP_TAG_NAME("global", "eventSink"), - PROP_TAG_CALLBACK_EVENT, linux_global_eventsink, NULL, - PROP_TAG_COURIER, glibcourier, - NULL); - add_xdg_paths(); - mainloop(); + glw_x11_main(&running); main_fini(); @@ -193,6 +73,15 @@ arch_exit(void) } +int +arch_stop_req(void) +{ + running = 0; + return 0; +} + + + /** * */ diff --git a/src/arch/posix/posix.c b/src/arch/posix/posix.c index c146d1480f..7e9ff42266 100644 --- a/src/arch/posix/posix.c +++ b/src/arch/posix/posix.c @@ -154,6 +154,17 @@ posix_init(void) } } +#ifdef linux + const char *snap_user_common = getenv("SNAP_USER_COMMON"); + if(snap_user_common != NULL) { + char buf[PATH_MAX]; + snprintf(buf, sizeof(buf), "%s/cache", snap_user_common); + gconf.cache_path = strdup(buf); + + snprintf(buf, sizeof(buf), "%s/persistent", snap_user_common); + gconf.persistent_path = strdup(buf); + } +#endif setlocale(LC_ALL, ""); #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR @@ -161,7 +172,7 @@ posix_init(void) #else decorate_trace = isatty(2); #endif - + signal(SIGPIPE, SIG_IGN); #ifdef RLIMIT_AS diff --git a/src/blobcache_file.c b/src/blobcache_file.c index ccaa35477d..68845ae93e 100644 --- a/src/blobcache_file.c +++ b/src/blobcache_file.c @@ -986,7 +986,7 @@ blobcache_init(void) prop_t *dir = setting_get_dir("general:resets"); - settings_create_action(dir, _p("Clear cached files"), + settings_create_action(dir, _p("Clear cached files"), NULL, cache_clear, NULL, 0, NULL); hts_thread_create_joinable("blobcache", &bcthread, flushthread, NULL, diff --git a/src/event.c b/src/event.c index 7064d6a6ba..8d9a66a27f 100644 --- a/src/event.c +++ b/src/event.c @@ -196,8 +196,6 @@ static struct strtab actionnames[] = { { "Playqueue", ACTION_PLAYQUEUE }, { "Sysinfo", ACTION_SYSINFO }, - { "SwitchUI", ACTION_SWITCH_UI }, - }; @@ -637,7 +635,7 @@ const static int action_from_fkey[13][2] = { { ACTION_SWITCH_VIEW, ACTION_SEEK_FORWARD }, { 0, ACTION_VOLUME_MUTE_TOGGLE }, { ACTION_FULLSCREEN_TOGGLE, ACTION_VOLUME_DOWN }, - { ACTION_SWITCH_UI, ACTION_VOLUME_UP }, + { 0, ACTION_VOLUME_UP }, }; diff --git a/src/event.h b/src/event.h index 9f240ab428..236857d72a 100644 --- a/src/event.h +++ b/src/event.h @@ -128,8 +128,6 @@ typedef enum { ACTION_SYSINFO, - ACTION_SWITCH_UI, - ACTION_RECORD_UI, ACTION_mappable_end, diff --git a/src/fileaccess/fa_zip.c b/src/fileaccess/fa_zip.c index cb65b8eabc..a6b0c94acb 100644 --- a/src/fileaccess/fa_zip.c +++ b/src/fileaccess/fa_zip.c @@ -164,6 +164,7 @@ zip_archive_find_file(zip_archive_t *za, zip_file_t *parent, const char *s, *n = name; char *b; int l; + int must_be_dir = 0; if(parent == NULL) return NULL; @@ -174,8 +175,7 @@ zip_archive_find_file(zip_archive_t *za, zip_file_t *parent, if(s != NULL) { l = s - name; s++; - if(*s == 0) - return NULL; + must_be_dir = *s == 0; n = b = alloca(l + 1); memcpy(b, name, l); b[l] = 0; @@ -195,7 +195,13 @@ zip_archive_find_file(zip_archive_t *za, zip_file_t *parent, zf->zf_name = strdup(n); zf->zf_type = s ? CONTENT_DIR : CONTENT_FILE; LIST_INSERT_HEAD(&parent->zf_files, zf, zf_link); - } + } + + if(must_be_dir) { + if(zf->zf_type != CONTENT_DIR) + return NULL; + return zf; + } return s != NULL ? zip_archive_find_file(za, zf, s, create) : zf; } diff --git a/src/ipc/libcec.c b/src/ipc/libcec.c index abd6ca2fe6..0dfe3314cb 100644 --- a/src/ipc/libcec.c +++ b/src/ipc/libcec.c @@ -31,6 +31,7 @@ static int longpress_select; static libcec_configuration cec_config; static libcec_connection_t conn; +#if CEC_LIB_VERSION_MAJOR < 4 static int log_message(void *lib, const cec_log_message message) { @@ -54,6 +55,31 @@ log_message(void *lib, const cec_log_message message) TRACE(level, "CEC", "%s", message.message); return 1; } +#else +static void +log_message(void *lib, const cec_log_message *message) +{ + int level; + switch (message->level) { + case CEC_LOG_ERROR: + level = TRACE_ERROR; + break; + + case CEC_LOG_WARNING: + case CEC_LOG_NOTICE: + level = TRACE_INFO; + break; + + default: + if(!gconf.enable_cec_debug) + return; + level = TRACE_DEBUG; + break; + } + TRACE(level, "CEC", "%s", message->message); + return; +} +#endif #define AVEC(x...) (const action_type_t []){x, ACTION_NONE} @@ -80,13 +106,13 @@ const static action_type_t *btn_to_action[256] = { [CEC_USER_CONTROL_CODE_CHANNEL_DOWN]= AVEC(ACTION_PREV_CHANNEL), [CEC_USER_CONTROL_CODE_F1_BLUE] = AVEC(ACTION_SYSINFO), - [CEC_USER_CONTROL_CODE_F2_RED] = AVEC(ACTION_SWITCH_VIEW), [CEC_USER_CONTROL_CODE_F4_YELLOW] = AVEC(ACTION_SHOW_MEDIA_STATS), [CEC_USER_CONTROL_CODE_SUB_PICTURE] = AVEC(ACTION_CYCLE_SUBTITLE), }; +#if CEC_LIB_VERSION_MAJOR < 4 static int keypress(void *aux, const cec_keypress kp) { @@ -127,7 +153,48 @@ keypress(void *aux, const cec_keypress kp) } return 1; } +#else +static void +keypress(void *aux, const cec_keypress *kp) +{ + event_t *e = NULL; + if(gconf.enable_cec_debug) + TRACE(TRACE_DEBUG, "CEC", "Got keypress code=0x%x duration=0x%x", + kp->keycode, kp->duration); + + if(longpress_select) { + if(kp->keycode == CEC_USER_CONTROL_CODE_SELECT) { + if(kp->duration == 0) + return; + + if(kp->duration < 500) + e = event_create_action(ACTION_ACTIVATE); + else + e = event_create_action(ACTION_ITEMMENU); + } + } + if(e == NULL) { + const action_type_t *avec = NULL; + if(kp->duration == 0 || kp->keycode == cec_config.comboKey) { + avec = btn_to_action[kp->keycode]; + } + + if(avec != NULL) { + int i = 0; + while(avec[i] != 0) + i++; + e = event_create_action_multi(avec, i); + } + } + + if(e != NULL) { + e->e_flags |= EVENT_KEYPRESS; + event_to_ui(e); + } + return; +} +#endif static void source_activated(void *aux, const cec_logical_address la, const uint8_t on) @@ -136,7 +203,7 @@ source_activated(void *aux, const cec_logical_address la, const uint8_t on) la, on ? "active" : "inactive"); } - +#if CEC_LIB_VERSION_MAJOR < 4 static int handle_cec_command(void *aux, const cec_command cmd) { @@ -151,13 +218,35 @@ handle_cec_command(void *aux, const cec_command cmd) } return 1; } - +#else +static void +handle_cec_command(void *aux, const cec_command *cmd) +{ + switch(cmd->opcode) { + case CEC_OPCODE_STANDBY: + if(cmd->initiator == CECDEVICE_TV) { + TRACE(TRACE_INFO, "CEC", "TV STANDBY"); + } + break; + default: + break; + } + return; +} +#endif static ICECCallbacks g_callbacks = { +#if CEC_LIB_VERSION_MAJOR < 4 .CBCecLogMessage = log_message, .CBCecKeyPress = keypress, .CBCecSourceActivated = source_activated, .CBCecCommand = handle_cec_command, +#else + .logMessage = log_message, + .keyPress = keypress, + .sourceActivated = source_activated, + .commandReceived = handle_cec_command, +#endif }; diff --git a/src/keyring.c b/src/keyring.c index d93bc3bea0..45a7e67ed9 100644 --- a/src/keyring.c +++ b/src/keyring.c @@ -63,7 +63,7 @@ keyring_init(void) prop_t *dir = setting_get_dir("general:resets"); settings_create_action(dir, - _p("Forget remembered passwords"), + _p("Forget remembered passwords"), NULL, keyring_clear, NULL, 0, NULL); } diff --git a/src/main.c b/src/main.c index 3c15ebdc2a..13289fd41c 100644 --- a/src/main.c +++ b/src/main.c @@ -50,6 +50,7 @@ #include "subtitles/subtitles.h" #include "db/db_support.h" #include "htsmsg/htsmsg_store.h" +#include "htsmsg/htsmsg_json.h" #include "db/kvstore.h" #include "upgrade.h" #include "usage.h" @@ -214,6 +215,39 @@ navigator_can_start(void) } +/** + * + */ +static void * +geothread(void *aux) +{ + for(int i = 0; i < 10; i++) { + + buf_t *b = fa_load("http://ifconfig.co/json", NULL); + if(b == NULL) { + sleep(i * 2); + continue; + } + htsmsg_t *msg = htsmsg_json_deserialize(buf_cstr(b)); + buf_release(b); + if(msg != NULL) { + const char *cc = htsmsg_get_str(msg, "country_iso"); + if(cc != NULL) { + TRACE(TRACE_DEBUG, "GEO", "Current country: %s", cc); + prop_setv(prop_get_global(), "location", "cc", NULL, + PROP_SET_STRING, cc); + } + + htsmsg_release(msg); + } + break; + } + return NULL; +} + + + + /** * */ @@ -221,7 +255,7 @@ static void * swthread(void *aux) { #if ENABLE_PLUGINS - plugins_init2(); + plugins_load_all(); #endif upgrade_init(); @@ -253,8 +287,6 @@ swthread(void *aux) navigator_can_start(); } - load_site_news(); - hts_mutex_lock(&gconf.state_mutex); gconf.swrefresh = 0; @@ -276,7 +308,6 @@ swthread(void *aux) plugins_upgrade_check(); #endif upgrade_refresh(); - load_site_news(); hts_mutex_lock(&gconf.state_mutex); } hts_mutex_unlock(&gconf.state_mutex); @@ -458,6 +489,7 @@ main_init(void) TRACE(TRACE_DEBUG, "SYSTEM", "Device type: %s", gconf.device_type); /* Start software installer thread (plugins, upgrade, etc) */ + hts_thread_create_detached("geothread", geothread, NULL, THREAD_PRIO_BGTASK); hts_thread_create_detached("swinst", swthread, NULL, THREAD_PRIO_BGTASK); /* Internationalization */ diff --git a/src/metadata/metadb.c b/src/metadata/metadb.c index 96b464b950..975e5011c8 100644 --- a/src/metadata/metadb.c +++ b/src/metadata/metadb.c @@ -128,7 +128,7 @@ metadb_init(void) metadb_pool = NULL; // Disable } else { prop_t *dir = setting_get_dir("general:resets"); - settings_create_action(dir, _p("Clear all metadata"), + settings_create_action(dir, _p("Clear all metadata"), NULL, items_clear, NULL, 0, NULL); } } diff --git a/src/misc/str.c b/src/misc/str.c index 9cfe67476c..403c539228 100644 --- a/src/misc/str.c +++ b/src/misc/str.c @@ -17,6 +17,7 @@ * This program is also available under a commercial proprietary license. * For more information, contact andreas@lonelycoder.com */ +#define _GNU_SOURCE #include #include #include @@ -1806,3 +1807,30 @@ pattern_match(const char *str, const char *pat) } while (*str++); return 0; } + +void +freecharp(char **ptr) +{ + free(*ptr); + *ptr = NULL; +} + +char * +fmtv(const char *fmt, va_list ap) +{ + char *ret; + if(vasprintf(&ret, fmt, ap) == -1) + abort(); + return ret; +} + +char * +fmt(const char *fmt, ...) +{ + va_list ap; + char *ret; + va_start(ap, fmt); + ret = fmtv(fmt, ap); + va_end(ap); + return ret; +} diff --git a/src/misc/str.h b/src/misc/str.h index 5a41136cfb..60366edaa6 100644 --- a/src/misc/str.h +++ b/src/misc/str.h @@ -145,4 +145,12 @@ void rgbstr_to_floatvec(const char *s, float *out); int pattern_match(const char *str, const char *pat); +void freecharp(char **ptr); + +#define scoped_char char __attribute__((cleanup(freecharp))) + +char *fmtv(const char *fmt, va_list ap); + +char *fmt(const char *fmt, ...) __attribute__ ((format (printf, 1, 2))); + #endif diff --git a/src/notifications.c b/src/notifications.c index 96f62df1cc..61e95a6f2c 100644 --- a/src/notifications.c +++ b/src/notifications.c @@ -32,18 +32,12 @@ #include "misc/time.h" #include "settings.h" -#if ENABLE_WEBPOPUP -#include "ui/webpopup.h" -#endif - static prop_t *notify_prop_entries; static hts_mutex_t news_mutex; static htsmsg_t *dismissed_news_in; static htsmsg_t *dismissed_news_out; -static int shownews; - /** * */ @@ -58,18 +52,6 @@ notifications_init(void) dismissed_news_out = htsmsg_create_map(); notify_prop_entries = prop_create(root, "nodes"); - -#if ENABLE_WEBPOPUP - - prop_t *dir = setting_get_dir("general:misc"); - - setting_create(SETTING_BOOL, dir, SETTINGS_INITIAL_UPDATE, - SETTING_TITLE(_p("Show news on home screen")), - SETTING_VALUE(1), - SETTING_WRITE_BOOL(&shownews), - SETTING_STORE("notifications", "shownews"), - NULL); -#endif } @@ -440,177 +422,3 @@ add_news(const char *id, const char *message, hts_mutex_unlock(&news_mutex); return p; } - -#if ENABLE_WEBPOPUP - -/** - * - */ -static void -open_news(void *opaque, prop_event_t event, ...) -{ - prop_t *p = opaque; - event_t *e; - va_list ap; - - va_start(ap, event); - - switch(event) { - case PROP_DESTROYED: - prop_unsubscribe(va_arg(ap, prop_sub_t *)); - prop_ref_dec(p); - break; - - case PROP_EXT_EVENT: - e = va_arg(ap, event_t *); - if(event_is_type(e, EVENT_DYNAMIC_ACTION)) { - const event_payload_t *ep = (const event_payload_t *)e; - const char *id = mystrbegins(ep->payload, "sitenews:"); - if(id != NULL) { - - dismis_news(ep->payload); - char url[512]; -#ifdef PS3 - // PS3 browser is really bad when it comes to HTTPS - snprintf(url, sizeof(url), "http://www2.movian.tv/news/%s", id); -#else - snprintf(url, sizeof(url), "https://movian.tv/news/%s", id); -#endif - TRACE(TRACE_DEBUG, "NEWS", "Opening %s", url); - webbrowser_open(url, APPNAMEUSER); - } - } - break; - - default: - break; - } - va_end(ap); -} - -/** - * - */ -static int -parse_created_on_time(time_t *tp, const char *d) -{ - int year; - int month; - int day; - int hour; - int min; - int sec; - - if(sscanf(d, "%d-%d-%dT%d:%d:%dZ", - &year, &month, &day, &hour, &min, &sec) != 6) - return -1; - - return mktime_utc(tp, year, month-1, day, hour, min, sec); -} - -#endif - - -/** - * - */ -void -load_site_news(void) -{ - if(!shownews) - return; - -#if ENABLE_WEBPOPUP - struct http_header_list response_headers; - buf_t *b; - char errbuf[512]; - b = fa_load("https://movian.tv/projects/movian/news.json", - FA_LOAD_FLAGS(FA_DISABLE_AUTH | FA_COMPRESSION), - FA_LOAD_RESPONSE_HEADERS(&response_headers), - FA_LOAD_ERRBUF(errbuf, sizeof(errbuf)), - NULL); - if(b == NULL) { - TRACE(TRACE_DEBUG, "News", "Unable to load news -- %s", errbuf); - return; - } - - const char *dateheader = http_header_get(&response_headers, "date"); - if(dateheader == NULL) { - buf_release(b); - http_headers_free(&response_headers); - return; - } - dateheader = mystrdupa(dateheader); - http_headers_free(&response_headers); - - - htsmsg_t *newsinfo = htsmsg_store_load("sitenews"); - time_t servertime; - - if(http_ctime(&servertime, dateheader)) { - buf_release(b); - htsmsg_release(newsinfo); - return; - } - - if(newsinfo == NULL) - newsinfo = htsmsg_create_map(); - - time_t no_news_before = - htsmsg_get_u32_or_default(newsinfo, "nothingbefore", 0); - - if(no_news_before == 0) { - no_news_before = servertime; - htsmsg_add_u32(newsinfo, "nothingbefore", no_news_before); - htsmsg_store_save(newsinfo, "sitenews"); - } - htsmsg_release(newsinfo); - - htsmsg_t *doc = htsmsg_json_deserialize(buf_cstr(b)); - buf_release(b); - if(doc == NULL) { - return; - } - - hts_mutex_lock(&news_mutex); - - htsmsg_t *news = htsmsg_get_list(doc, "news"); - if(news != NULL) { - htsmsg_field_t *f; - HTSMSG_FOREACH(f, news) { - htsmsg_t *entry; - if((entry = htsmsg_get_map_by_field(f)) == NULL) - continue; - - const char *title = htsmsg_get_str(entry, "title"); - const char *created_on = htsmsg_get_str(entry, "created_on"); - int id = htsmsg_get_u32_or_default(entry, "id", 0); - if(created_on == NULL || title == NULL || id == 0) - continue; - - time_t t; - - if(parse_created_on_time(&t, created_on)) - continue; - - if(t < no_news_before || t < servertime - 86400 * 30) - continue; - - char idstr[64]; - snprintf(idstr, sizeof(idstr), "sitenews:%d", id); - prop_t *p = add_news_locked(idstr, title, NULL, "Read more", idstr); - if(p != NULL) { - prop_subscribe(PROP_SUB_TRACK_DESTROY, - PROP_TAG_CALLBACK, open_news, p, - PROP_TAG_ROOT, prop_create(p, "eventSink"), - PROP_TAG_MUTEX, &news_mutex, - NULL); - } - } - } - - hts_mutex_unlock(&news_mutex); - htsmsg_release(doc); - TRACE(TRACE_DEBUG, "News", "News loaded and updated"); -#endif -} diff --git a/src/notifications.h b/src/notifications.h index 43fd059a2d..01bad53f45 100644 --- a/src/notifications.h +++ b/src/notifications.h @@ -53,6 +53,4 @@ int text_dialog(const char *message, char** string, int flags); struct prop *add_news(const char *id, const char *message, const char *location, const char *caption); -void load_site_news(void); - #endif // NOTIFICATIONS_H__ diff --git a/src/plugins.c b/src/plugins.c index 12fe7f5a32..07051c4850 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -24,6 +24,7 @@ #include "htsmsg/htsmsg_json.h" #include "htsmsg/htsmsg_store.h" #include "backend/backend.h" +#include "backend/backend_prop.h" #include "misc/str.h" #include "misc/minmax.h" #include "prop/prop_nodefilter.h" @@ -33,6 +34,8 @@ #include "arch/arch.h" #include "usage.h" #include "backend/search.h" +#include "misc/md5.h" +#include "service.h" #include "ecmascript/ecmascript.h" @@ -56,7 +59,7 @@ typedef enum { } plugin_type_t; static struct strtab catnames[] = { - { "tv", PLUGIN_CAT_TV }, + { "tv", PLUGIN_CAT_TV }, { "video", PLUGIN_CAT_VIDEO }, { "music", PLUGIN_CAT_MUSIC }, { "cloud", PLUGIN_CAT_CLOUD }, @@ -67,10 +70,8 @@ static struct strtab catnames[] = { { "subtitles", PLUGIN_CAT_SUBTITLES }, }; +const char *storage_prefix = "mrp"; // multi-repo plugins -static const char *plugin_repo_url = PLUGINREPO; -static char *plugin_alt_repo_url; -static char *plugin_beta_passwords; static HTS_MUTEX_DECL(plugin_mutex); static HTS_MUTEX_DECL(autoplugin_mutex); @@ -79,8 +80,12 @@ static char **devplugins; static prop_t *plugin_root_list; static prop_t *plugin_start_model; static prop_t *plugin_repo_model; +static prop_t *plugin_repos_settings; + +static service_t *plugin_service; LIST_HEAD(plugin_list, plugin); +LIST_HEAD(plugin_repo_list, plugin_repo); LIST_HEAD(plugin_view_list, plugin_view); LIST_HEAD(plugin_view_entry_list, plugin_view_entry); @@ -88,7 +93,8 @@ static struct plugin_list plugins; typedef struct plugin { LIST_ENTRY(plugin) pl_link; - char *pl_id; + char *pl_fqid; + char *pl_origin; char *pl_package; char *pl_title; @@ -105,6 +111,7 @@ typedef struct plugin { char pl_loaded; char pl_installed; char pl_can_upgrade; + char pl_auto_upgrade; char pl_new_version_avail; char pl_mark; @@ -112,6 +119,18 @@ typedef struct plugin { } plugin_t; +static struct plugin_repo_list plugin_repos; + +typedef struct plugin_repo { + LIST_ENTRY(plugin_repo) pr_link; + char *pr_url; + prop_t *pr_root; + prop_t *pr_title; + int pr_autoupgrade; + int pr_initialized; +} plugin_repo_t; + + static int plugin_install(plugin_t *pl, const char *package); static void plugin_remove(plugin_t *pl); static void plugin_autoupgrade(void); @@ -127,8 +146,7 @@ static void autoplugin_create_from_control(const char *id, htsmsg_t *ctrl, static void autoplugin_set_installed(const char *id, int is_installed); -static int autoupgrade; -static int autoinstall; +static void plugin_repo_create(const char *url, const char *title, int load); #define VERSION_ENCODE(a,b,c) ((a) * 10000000 + (b) * 100000 + (c)) @@ -140,6 +158,29 @@ static const struct { { "xperience", VERSION_ENCODE(1,0,0) } }; + +static void +plugin_update_service(void) +{ + const int should_show = LIST_FIRST(&plugin_repos) || LIST_FIRST(&plugins); + + if(should_show && plugin_service == NULL) { + plugin_service = service_createp("showtime:plugin", + _p("Plugins"), "plugin:start", + "plugin", NULL, 0, 1, SVC_ORIGIN_SYSTEM); + return; + } + + if(!should_show && plugin_service != NULL) { + service_destroy(plugin_service); + plugin_service = NULL; + return; + } +} + + + + /** * */ @@ -174,51 +215,45 @@ is_plugin_blacklisted(const char *id, const char *version, rstr_t **reason) return 0; } - - - - - - - -/** - * - */ -static const char * -repo_url(void) +static char * +origin_hash(const char *url) { - return plugin_alt_repo_url && *plugin_alt_repo_url ? - plugin_alt_repo_url : plugin_repo_url; + md5_decl(md5); + md5_init(md5); + md5_update(md5, (const void *)url, strlen(url)); + uint8_t hash[16]; + md5_final(md5, hash); + + return fmt("%02x%02x%02x%02x%02x%02x%02x%02x", + hash[0], hash[1], hash[2], hash[3], + hash[4], hash[5], hash[6], hash[7]); } + /** * */ -static void -set_alt_repo_url(void *opaque, const char *value) +static plugin_t * +plugin_make(const char *id, const char *origin) { - mystrset(&plugin_alt_repo_url, value); -} + plugin_t *pl; + scoped_char *fqid = fmt("%s@%s", id, origin); + LIST_FOREACH(pl, &plugins, pl_link) + if(!strcmp(pl->pl_fqid, fqid)) + return pl; -/** - * - */ -static void -set_beta_passwords(void *opaque, const char *value) -{ - mystrset(&plugin_beta_passwords, value); -} + pl = calloc(1, sizeof(plugin_t)); + pl->pl_fqid = strdup(fqid); + pl->pl_origin = strdup(origin); + pl->pl_status = prop_create_root(NULL); -/** - * - */ -static void -set_autoupgrade(void *opaque, int value) -{ - autoupgrade = value; - plugin_autoupgrade(); + LIST_INSERT_HEAD(&plugins, pl, pl_link); + + plugin_update_service(); + + return pl; } @@ -226,22 +261,13 @@ set_autoupgrade(void *opaque, int value) * */ static plugin_t * -plugin_find(const char *id, int create) +plugin_find(const char *fqid) { plugin_t *pl; LIST_FOREACH(pl, &plugins, pl_link) - if(!strcmp(pl->pl_id, id)) + if(!strcmp(pl->pl_fqid, fqid)) return pl; - if(!create) - return NULL; - - pl = calloc(1, sizeof(plugin_t)); - pl->pl_id = strdup(id); - - pl->pl_status = prop_create_root(NULL); - - LIST_INSERT_HEAD(&plugins, pl, pl_link); - return pl; + return NULL; } @@ -383,9 +409,9 @@ plugin_event(void *opaque, prop_event_t event, ...) */ static void plugin_fill_prop(struct htsmsg *pm, struct prop *p, - const char *basepath, plugin_t *pl) + const char *baseurl, plugin_t *pl) { - const char *title = htsmsg_get_str(pm, "title") ?: pl->pl_id; + const char *title = htsmsg_get_str(pm, "title") ?: pl->pl_fqid; const char *icon = htsmsg_get_str(pm, "icon"); const char *cat = htsmsg_get_str(pm, "category"); @@ -421,20 +447,16 @@ plugin_fill_prop(struct htsmsg *pm, struct prop *p, prop_set(metadata, "version", PROP_SET_STRING, htsmsg_get_str(pm, "version")); - unsigned int popularity; - - if(!htsmsg_get_u32(pm, "popularity", &popularity)) - prop_set(metadata, "popularity", PROP_SET_INT, popularity); - if(icon != NULL) { - if(basepath != NULL) { - char url[512]; - snprintf(url, sizeof(url), "%s/%s", basepath, icon); - prop_set(metadata, "icon", PROP_SET_STRING,url); + if(mystrbegins(icon, "http://") || mystrbegins(icon, "https://")) { + prop_set(metadata, "icon", PROP_SET_STRING, icon); + } else if(mystrbegins(baseurl, "http://") || + mystrbegins(baseurl, "https://")) { + scoped_char *iconurl = url_resolve_relative_from_base(baseurl, icon); + prop_set(metadata, "icon", PROP_SET_STRING, iconurl); } else { - char *iconurl = url_resolve_relative_from_base(repo_url(), icon); + scoped_char *iconurl = fmt("%s/%s", baseurl, icon); prop_set(metadata, "icon", PROP_SET_STRING, iconurl); - free(iconurl); } } prop_ref_dec(metadata); @@ -442,22 +464,62 @@ plugin_fill_prop(struct htsmsg *pm, struct prop *p, +static char * +plugin_resolve_zip_path(const char *zipfile) +{ + scoped_char *zp = fmt("zip://%s", zipfile); + fa_dir_t *fd = fa_scandir(zp, NULL, 0); + if(fd == NULL) { + return NULL; + } + fa_dir_entry_t *fde; + RB_FOREACH(fde, &fd->fd_entries, fde_link) { + if(!strcmp(rstr_get(fde->fde_filename), "plugin.json")) { + fa_dir_free(fd); + return strdup(zp); + } + } + + fde = RB_FIRST(&fd->fd_entries); + if(fde != NULL && fde->fde_type == CONTENT_DIR) { + scoped_char *zp2 = fmt("zip://%s/%s/plugin.json", zipfile, + rstr_get(fde->fde_filename)); + + struct fa_stat buf; + if(!fa_stat(zp2, &buf, NULL, 0)) { + char *r = fmt("zip://%s/%s", zipfile, rstr_get(fde->fde_filename)); + fa_dir_free(fd); + return r; + } + } + + fa_dir_free(fd); + return NULL; +} + + /** * */ void plugin_props_from_file(prop_t *prop, const char *zipfile) { - char path[200]; char errbuf[200]; buf_t *b; - snprintf(path, sizeof(path), "zip://%s/plugin.json", zipfile); - b = fa_load(path, + scoped_char *zippath = plugin_resolve_zip_path(zipfile); + if(zippath == NULL) { + TRACE(TRACE_ERROR, "plugins", + "Unable to open %s -- Not a valid plugin archive", zipfile); + return; + } + scoped_char *plugin_json = fmt("%s/plugin.json", zippath); + b = fa_load(plugin_json, FA_LOAD_ERRBUF(errbuf, sizeof(errbuf)), NULL); if(b == NULL) { - TRACE(TRACE_ERROR, "plugins", "Unable to open %s -- %s", path, errbuf); + TRACE(TRACE_ERROR, "plugins", "Unable to open %s -- %s", + plugin_json, errbuf); return; } htsmsg_t *pm = htsmsg_json_deserialize(buf_cstr(b)); @@ -470,11 +532,8 @@ plugin_props_from_file(prop_t *prop, const char *zipfile) if(id != NULL) { hts_mutex_lock(&plugin_mutex); - plugin_t *pl = plugin_find(id, 1); - - - snprintf(path, sizeof(path), "zip://%s", zipfile); - plugin_fill_prop(pm, prop, path, pl); + plugin_t *pl = plugin_make(id, "local"); + plugin_fill_prop(pm, prop, zippath, pl); prop_set(prop, "package", PROP_SET_STRING, zipfile); update_state(pl); hts_mutex_unlock(&plugin_mutex); @@ -492,8 +551,8 @@ plugin_prop_setup(htsmsg_t *pm, plugin_t *pl, const char *basepath) { prop_t *p; hts_mutex_assert(&plugin_mutex); - p = prop_create(plugin_root_list, pl->pl_id); - mystrset(&pl->pl_title, htsmsg_get_str(pm, "title") ?: pl->pl_id); + p = prop_create(plugin_root_list, pl->pl_fqid); + mystrset(&pl->pl_title, htsmsg_get_str(pm, "title") ?: pl->pl_fqid); prop_set(p, "type", PROP_SET_STRING, "plugin"); plugin_fill_prop(pm, p, basepath, pl); if(basepath == NULL) { @@ -509,7 +568,7 @@ plugin_prop_setup(htsmsg_t *pm, plugin_t *pl, const char *basepath) static void plugin_unload_ecmascript(plugin_t *pl) { - ecmascript_plugin_unload(pl->pl_id); + ecmascript_plugin_unload(pl->pl_fqid); } @@ -520,7 +579,7 @@ plugin_unload_ecmascript(plugin_t *pl) static void plugin_unload_vmir(plugin_t *pl) { - np_plugin_unload(pl->pl_id); + np_plugin_unload(pl->pl_fqid); } #endif @@ -549,7 +608,8 @@ plugin_unload(plugin_t *pl) * */ static int -plugin_load(const char *url, char *errbuf, size_t errlen, int flags) +plugin_load(const char *url, const char *origin, + char *errbuf, size_t errlen, int flags) { char ctrlfile[URL_MAX]; char errbuf2[1024]; @@ -570,7 +630,7 @@ plugin_load(const char *url, char *errbuf, size_t errlen, int flags) goto bad; const char *type = htsmsg_get_str(ctrl, "type"); - const char *id = htsmsg_get_str(ctrl, "id"); + const char *ext_id = htsmsg_get_str(ctrl, "id"); const char *version = htsmsg_get_str(ctrl, "version"); if(type == NULL) { snprintf(errbuf, errlen, "Missing \"type\" element in control file %s", @@ -578,19 +638,18 @@ plugin_load(const char *url, char *errbuf, size_t errlen, int flags) goto bad; } - if(id == NULL) { + if(ext_id == NULL) { snprintf(errbuf, errlen, "Missing \"id\" element in control file %s", ctrlfile); goto bad; } - plugin_t *pl = plugin_find(id, 1); - + plugin_t *pl = plugin_make(ext_id, origin); if(version != NULL) { rstr_t *notifymsg; - if(is_plugin_blacklisted(id, version, ¬ifymsg)) { - const char *title = htsmsg_get_str(ctrl, "title") ?: id; + if(is_plugin_blacklisted(ext_id, version, ¬ifymsg)) { + const char *title = htsmsg_get_str(ctrl, "title") ?: ext_id; char tmp[512]; rstr_t *fmt = _("Plugin %s has been uninstalled - %s"); snprintf(tmp, sizeof(tmp), rstr_get(fmt), title, rstr_get(notifymsg)); @@ -602,7 +661,7 @@ plugin_load(const char *url, char *errbuf, size_t errlen, int flags) } if(!(flags & PLUGIN_LOAD_FORCE) && pl->pl_loaded) { - snprintf(errbuf, errlen, "Plugin \"%s\" already loaded", id); + snprintf(errbuf, errlen, "Plugin \"%s\" already loaded", pl->pl_fqid); goto bad; } @@ -632,7 +691,7 @@ plugin_load(const char *url, char *errbuf, size_t errlen, int flags) int stack_size = htsmsg_get_u32_or_default(ctrl, "stack-size", 64); hts_mutex_unlock(&plugin_mutex); - r = np_plugin_load(id, fullpath, errbuf, errlen, version, 0, + r = np_plugin_load(pl->pl_fqid, fullpath, errbuf, errlen, version, 0, memory_size * 1024, stack_size * 1024); hts_mutex_lock(&plugin_mutex); if(!r) @@ -664,7 +723,7 @@ plugin_load(const char *url, char *errbuf, size_t errlen, int flags) pflags |= ECMASCRIPT_FILE_BYPASS_ACL_WRITE; } hts_mutex_unlock(&plugin_mutex); - r = ecmascript_plugin_load(id, fullpath, errbuf, errlen, version, + r = ecmascript_plugin_load(pl->pl_fqid, fullpath, errbuf, errlen, version, buf_cstr(b), pflags); hts_mutex_lock(&plugin_mutex); if(!r) @@ -722,10 +781,10 @@ plugin_load(const char *url, char *errbuf, size_t errlen, int flags) pl->pl_installed = 1; mystrset(&pl->pl_inst_ver, htsmsg_get_str(ctrl, "version")); - autoplugin_set_installed(pl->pl_id, 1); + autoplugin_set_installed(pl->pl_fqid, 1); } - mystrset(&pl->pl_title, htsmsg_get_str(ctrl, "title") ?: id); + mystrset(&pl->pl_title, htsmsg_get_str(ctrl, "title") ?: pl->pl_fqid); pl->pl_loaded = 1; } @@ -754,14 +813,29 @@ plugin_load_installed(void) char errbuf[200]; fa_dir_entry_t *fde; - snprintf(path, sizeof(path), "%s/installedplugins", gconf.persistent_path); + snprintf(path, sizeof(path), "%s/%s/installed", + gconf.persistent_path, storage_prefix); fa_dir_t *fd = fa_scandir(path, NULL, 0); if(fd != NULL) { RB_FOREACH(fde, &fd->fd_entries, fde_link) { - snprintf(path, sizeof(path), "zip://%s", rstr_get(fde->fde_url)); - if(plugin_load(path, errbuf, sizeof(errbuf), + scoped_char *d0 = strdup(rstr_get(fde->fde_filename)); + char *origin = strchr(d0, '@'); + if(origin != NULL) { + origin++; + char *dot = strchr(origin, '.'); + if(dot != NULL) + *dot = 0; + } + scoped_char *zippath = plugin_resolve_zip_path(rstr_get(fde->fde_url)); + if(zippath == NULL) { + TRACE(TRACE_ERROR, "plugins", + "Unable to load %s -- Not a valid plugin archive", path); + continue; + } + + if(plugin_load(zippath, origin, errbuf, sizeof(errbuf), PLUGIN_LOAD_AS_INSTALLED)) { TRACE(TRACE_ERROR, "plugins", "Unable to load %s\n%s", path, errbuf); } @@ -781,29 +855,12 @@ repo_get(const char *repo, char *errbuf, size_t errlen) { buf_t *b; htsmsg_t *json; - const char *qargs[32]; - int qp = 0; TRACE(TRACE_DEBUG, "plugins", "Loading repo from %s", repo); - if(plugin_beta_passwords != NULL) { - char *pws = mystrdupa(plugin_beta_passwords); - char *tmp = NULL; - - while(qp < 30) { - const char *p = strtok_r(pws, " ", &tmp); - if(p == NULL) - break; - qargs[qp++] = "betapassword"; - qargs[qp++] = p; - pws = NULL; - } - } - qargs[qp] = 0; hts_mutex_unlock(&plugin_mutex); b = fa_load(repo, FA_LOAD_ERRBUF(errbuf, errlen), - FA_LOAD_QUERY_ARGVEC(qargs), FA_LOAD_FLAGS(FA_COMPRESSION | FA_DISABLE_AUTH), NULL); @@ -819,7 +876,6 @@ repo_get(const char *repo, char *errbuf, size_t errlen) snprintf(errbuf, errlen, "Malformed JSON in repository"); fa_load(repo, - FA_LOAD_QUERY_ARGVEC(qargs), FA_LOAD_CACHE_EVICT(), NULL); return REPO_ERROR_NETWORK; @@ -839,38 +895,69 @@ repo_get(const char *repo, char *errbuf, size_t errlen) htsmsg_release(json); return NULL; } - + return json; } +static void +plugin_mark(void) +{ + plugin_t *pl; + + LIST_FOREACH(pl, &plugins, pl_link) { + pl->pl_mark = 1; + } +} + + +static void +plugin_sweep(void) +{ + plugin_t *pl, *next; + + for(pl = LIST_FIRST(&plugins); pl != NULL; pl = next) { + next = LIST_NEXT(pl, pl_link); + prop_set(pl->pl_status, "inRepo", PROP_SET_INT, !pl->pl_mark); + if(pl->pl_mark) { + prop_ref_dec(pl->pl_repo_model); + pl->pl_repo_model = NULL; + pl->pl_mark = 0; + } + } +} + + /** * */ static int -plugin_load_repo(void) +plugin_load_repo(plugin_repo_t *pr) { - plugin_t *pl, *next; + plugin_t *pl; char errbuf[512]; - htsmsg_t *msg = repo_get(repo_url(), errbuf, sizeof(errbuf)); + const char *url = pr->pr_url; + + htsmsg_t *msg = repo_get(url, errbuf, sizeof(errbuf)); if(msg == REPO_ERROR_NETWORK || msg == NULL) { TRACE(TRACE_ERROR, "plugins", "Unable to load repo %s -- %s", - repo_url(), errbuf); + url, errbuf); return msg == REPO_ERROR_NETWORK ? -1 : 0; } hts_mutex_lock(&autoplugin_mutex); autoplugin_clear(); + const char *title = htsmsg_get_str(msg, "title"); + if(title != NULL) + prop_set_string(pr->pr_title, title); + htsmsg_t *r = htsmsg_get_list(msg, "plugins"); if(r != NULL) { htsmsg_field_t *f; - LIST_FOREACH(pl, &plugins, pl_link) - pl->pl_mark = 1; - HTSMSG_FOREACH(f, r) { htsmsg_t *pm; if((pm = htsmsg_get_map_by_field(f)) == NULL) @@ -895,74 +982,32 @@ plugin_load_repo(void) if(is_plugin_blacklisted(id, version, NULL)) continue; - pl = plugin_find(id, 1); + const char *relurl = htsmsg_get_str(pm, "downloadURL"); + if(relurl == NULL) + continue; + + char *package_url = url_resolve_relative_from_base(url, relurl); + + scoped_char *origin = origin_hash(package_url); + pl = plugin_make(id, origin); + free(pl->pl_package); + pl->pl_package = package_url; pl->pl_mark = 0; - plugin_prop_setup(pm, pl, NULL); + plugin_prop_setup(pm, pl, url); mystrset(&pl->pl_repo_ver, version); mystrset(&pl->pl_app_min_version, htsmsg_get_str(pm, "showtimeVersion")); update_state(pl); - const char *dlurl = htsmsg_get_str(pm, "downloadURL"); - if(dlurl != NULL) { - char *package = url_resolve_relative_from_base(repo_url(), dlurl); - free(pl->pl_package); - pl->pl_package = package; - } - htsmsg_t *ctrl = htsmsg_get_map(pm, "control"); if(ctrl != NULL) { autoplugin_create_from_control(id, ctrl, pl->pl_installed); } } - - for(pl = LIST_FIRST(&plugins); pl != NULL; pl = next) { - next = LIST_NEXT(pl, pl_link); - prop_set(pl->pl_status, "inRepo", PROP_SET_INT, !pl->pl_mark); - if(pl->pl_mark) { - prop_ref_dec(pl->pl_repo_model); - pl->pl_repo_model = NULL; - pl->pl_mark = 0; - } - } } hts_mutex_unlock(&autoplugin_mutex); - r = htsmsg_get_list(msg, "blacklist"); - if(r != NULL) { - htsmsg_field_t *f; - HTSMSG_FOREACH(f, r) { - htsmsg_t *pm; - if((pm = htsmsg_get_map_by_field(f)) == NULL) - continue; - - const char *id = htsmsg_get_str(pm, "id"); - const char *version = htsmsg_get_str(pm, "version"); - - if(id == NULL || version == NULL) - continue; - - LIST_FOREACH(pl, &plugins, pl_link) - if(!strcmp(id, pl->pl_id) && pl->pl_installed && pl->pl_inst_ver && - !strcmp(version, pl->pl_inst_ver)) - break; - - if(pl != NULL) { - notify_add(NULL, NOTIFY_ERROR, NULL, 10, - _("Plugin %s %s has been uninstalled because it may cause problems.\nYou may try reinstalling a different version manually."), pl->pl_title, pl->pl_inst_ver); - plugin_remove(pl); - } - } - } - - - const char *cc = htsmsg_get_str(msg, "cc"); - if(cc != NULL) { - TRACE(TRACE_DEBUG, "GEO", "Current country: %s", cc); - prop_setv(prop_get_global(), "location", "cc", NULL, PROP_SET_STRING, cc); - } - htsmsg_release(msg); return 0; } @@ -976,15 +1021,12 @@ plugin_autoupgrade(void) { plugin_t *pl; - if(!autoupgrade) - return; - LIST_FOREACH(pl, &plugins, pl_link) { - if(!pl->pl_can_upgrade) + if(!pl->pl_can_upgrade || !pl->pl_auto_upgrade) continue; if(plugin_install(pl, NULL)) continue; - notify_add(NULL, NOTIFY_INFO, NULL, 5, + notify_add(NULL, NOTIFY_INFO, NULL, 5, _("Upgraded plugin %s to version %s"), pl->pl_title, pl->pl_inst_ver); } @@ -1015,7 +1057,7 @@ plugin_setup_start_model(void) // Top items prop_t *sta = prop_create_root(NULL); - + p = prop_create(sta, NULL); prop_set_string(prop_create(p, "type"), "store"); prop_link(_p("Browse available plugins"), @@ -1125,8 +1167,7 @@ plugin_setup_repo_model(void) prop_nf_pred_int_add(pnf, "node.status.inRepo", PROP_NF_CMP_NEQ, 1, NULL, PROP_NF_MODE_EXCLUDE); - // prop_nf_sort(pnf, "node.metadata.title", 0, 0, NULL, 1); - prop_nf_sort(pnf, "node.metadata.popularity", 1, 0, NULL, 1); + prop_nf_sort(pnf, "node.metadata.title", 0, 0, NULL, 1); prop_nf_release(pnf); prop_t *header = prop_create_root(NULL); @@ -1135,7 +1176,55 @@ plugin_setup_repo_model(void) prop_concat_add_source(pc, cat, header); } +} + + +static void +plguin_repo_save(void) +{ + htsmsg_t *m = htsmsg_create_list(); + plugin_repo_t *pr; + LIST_FOREACH(pr, &plugin_repos, pr_link) { + htsmsg_t *e = htsmsg_create_map(); + htsmsg_add_str(e, "url", pr->pr_url); + htsmsg_add_msg(m, NULL, e); + } + htsmsg_store_save(m, "pluginrepos"); + htsmsg_release(m); +} + +static void +plguin_repo_load(void) +{ + htsmsg_t *m; + if((m = htsmsg_store_load("pluginrepos")) != NULL) { + htsmsg_field_t *f; + HTSMSG_FOREACH(f, m) { + htsmsg_t *e; + if((e = htsmsg_get_map_by_field(f)) == NULL) + continue; + const char *url = htsmsg_get_str(e, "url"); + plugin_repo_create(url, NULL, 0); + } + } +} + + + +static void +plugins_add_repo_popup(void *opaque, prop_event_t event, ...) +{ + rstr_t *msg = _("Enter URL of plugin repository"); + scoped_char *url = NULL; + int x = text_dialog(rstr_get(msg), &url, + MESSAGE_POPUP_OK | MESSAGE_POPUP_CANCEL); + rstr_release(msg); + if(x) + return; + + plugin_repo_create(url, NULL, 1); + plguin_repo_save(); } @@ -1155,38 +1244,17 @@ plugins_setup_root_props(void) // Settings prop_t *dir = setting_get_dir("general:plugins"); + prop_concat_t *pc = prop_concat_create(prop_create(dir, "nodes")); - setting_create(SETTING_STRING, dir, - SETTINGS_INITIAL_UPDATE, - SETTING_STORE("pluginconf", "alt_repo"), - SETTING_TITLE(_p("Alternate plugin Repository URL")), - SETTING_CALLBACK(set_alt_repo_url, NULL), - SETTING_MUTEX(&plugin_mutex), - NULL); + plugin_repos_settings = prop_create_root(NULL); + prop_concat_add_source(pc, plugin_repos_settings, NULL); - setting_create(SETTING_STRING, dir, - SETTINGS_INITIAL_UPDATE, - SETTING_STORE("pluginconf", "betapasswords"), - SETTING_TITLE(_p("Beta testing passwords")), - SETTING_CALLBACK(set_beta_passwords, NULL), - SETTING_MUTEX(&plugin_mutex), - NULL); + prop_t *add = prop_create_root(NULL); + prop_concat_add_source(pc, add, NULL); - setting_create(SETTING_BOOL, dir, SETTINGS_INITIAL_UPDATE, - SETTING_STORE("pluginconf", "autoupgrade"), - SETTING_TITLE(_p("Automatically upgrade plugins")), - SETTING_VALUE(1), - SETTING_CALLBACK(set_autoupgrade, NULL), - SETTING_MUTEX(&plugin_mutex), - NULL); - - setting_create(SETTING_BOOL, dir, SETTINGS_INITIAL_UPDATE, - SETTING_STORE("pluginconf", "autoupgrade"), - SETTING_TITLE(_p("Auto install plugins on demand")), - SETTING_VALUE(1), - SETTING_WRITE_INT(&autoinstall), - SETTING_MUTEX(&plugin_mutex), - NULL); + settings_create_action(add, _p("Subscribe to plugin repository feed"), "add", + plugins_add_repo_popup, NULL, SETTINGS_RAW_NODES, + NULL); } @@ -1194,7 +1262,7 @@ plugins_setup_root_props(void) * */ void -plugins_init2(void) +plugins_load_all(void) { hts_mutex_lock(&plugin_mutex); plugin_load_installed(); @@ -1202,23 +1270,139 @@ plugins_init2(void) } + /** * */ int plugins_upgrade_check(void) { + int r = 0; + plugin_repo_t *pr; hts_mutex_lock(&plugin_mutex); - int r = plugin_load_repo(); + + plugin_mark(); + + LIST_FOREACH(pr, &plugin_repos, pr_link) { + r |= plugin_load_repo(pr); + } + + plugin_sweep(); + if(!r) { update_global_state(); plugin_autoupgrade(); } + hts_mutex_unlock(&plugin_mutex); return r; } + + +static void +set_autoupgrade(void *opaque, int value) +{ + plugin_repo_t *pr = opaque; + pr->pr_autoupgrade = value; + if(value && pr->pr_initialized) { + if(!plugin_load_repo(pr)) { + update_global_state(); + } + } +} + + +static void +plugin_repo_delete(void *opaque, event_t *e) +{ + plugin_repo_t *pr = opaque; + + free(pr->pr_url); + LIST_REMOVE(pr, pr_link); + prop_destroy(pr->pr_root); + prop_ref_dec(pr->pr_title); + prop_ref_dec(pr->pr_root); + free(pr); + if(e->e_nav != NULL) { + event_t *be = event_create_action(ACTION_NAV_BACK); + prop_t *eventsink = prop_create_r(e->e_nav, "eventSink"); + prop_send_ext_event(eventsink, be); + prop_ref_dec(eventsink); + event_release(be); + } + + plugin_update_service(); + + plguin_repo_save(); +} + + +static void +plugin_repo_create(const char *url, const char *title, int load) +{ + hts_mutex_lock(&plugin_mutex); + + plugin_update_service(); + + plugin_repo_t *pr = calloc(1, sizeof(plugin_repo_t)); + LIST_INSERT_HEAD(&plugin_repos, pr, pr_link); + + pr->pr_url = strdup(url); + pr->pr_root = prop_create_r(plugin_repos_settings, NULL); + + pr->pr_title = prop_ref_inc(prop_create_multi(pr->pr_root, "metadata", + "title", NULL)); + prop_set_string(pr->pr_title, title ?: url); + prop_set(pr->pr_root, "type", PROP_SET_STRING, "directory"); + prop_set(pr->pr_root, "subtype", PROP_SET_STRING, "plugins"); + prop_t *m = prop_create(pr->pr_root, "model"); + prop_set(pr->pr_root, "url", PROP_ADOPT_RSTRING, backend_prop_make(m, NULL)); + + prop_set(m, "type", PROP_SET_STRING, "settings"); + prop_t *md = prop_create(m, "metadata"); + prop_set(md, "title", PROP_SET_LINK, pr->pr_title); + + prop_t *nodes = prop_create(m, "nodes"); + + prop_t *info = prop_create(nodes, NULL); + prop_setv(info, "type", NULL, PROP_SET_STRING, "info"); + scoped_char *infostr = fmt("URL: %s", pr->pr_url); + prop_setv(info, "description", NULL, PROP_SET_STRING, infostr); + + setting_create(SETTING_SEPARATOR, m, 0, + NULL); + + setting_create(SETTING_BOOL, m, SETTINGS_INITIAL_UPDATE, + SETTING_STORE("pluginconf", "autoupgrade"), + SETTING_TITLE(_p("Automatically upgrade plugins")), + SETTING_VALUE(1), + SETTING_KVSTORE(url, "autoupgrade"), + SETTING_CALLBACK(set_autoupgrade, pr), + SETTING_MUTEX(&plugin_mutex), + NULL); + + setting_create(SETTING_SEPARATOR, m, 0, + NULL); + + setting_create(SETTING_ACTION, m, 0, + SETTING_TITLE(_p("Stop subscribing to repository feed")), + SETTING_CALLBACK(plugin_repo_delete, pr), + SETTING_MUTEX(&plugin_mutex), + NULL); + + if(load) + plugin_load_repo(pr); + pr->pr_initialized = 1; + + plugin_update_service(); + + hts_mutex_unlock(&plugin_mutex); +} + + + /** * */ @@ -1231,6 +1415,10 @@ plugins_init(char **devplugs) plugins_setup_root_props(); + if(gconf.plugin_repo) + plugin_repo_create(gconf.plugin_repo, NULL, 0); + + hts_mutex_lock(&plugin_mutex); if(devplugs != NULL) { @@ -1244,7 +1432,7 @@ plugins_init(char **devplugs) strvec_addp(&devplugins, path); - if(plugin_load(path, errbuf, sizeof(errbuf), + if(plugin_load(path, "dev", errbuf, sizeof(errbuf), PLUGIN_LOAD_FORCE | PLUGIN_LOAD_DEBUG)) { TRACE(TRACE_ERROR, "plugins", "Unable to load development plugin: %s\n%s", path, errbuf); @@ -1254,6 +1442,8 @@ plugins_init(char **devplugs) } } hts_mutex_unlock(&plugin_mutex); + + plguin_repo_load(); } @@ -1272,7 +1462,7 @@ plugins_reload_dev_plugin(void) const char *path; for(int i = 0; (path = devplugins[i]) != NULL; i++) { - if(plugin_load(path, errbuf, sizeof(errbuf), + if(plugin_load(path, "dev", errbuf, sizeof(errbuf), PLUGIN_LOAD_FORCE | PLUGIN_LOAD_DEBUG | PLUGIN_LOAD_BY_USER)) TRACE(TRACE_ERROR, "plugins", "Unable to reload development plugin: %s\n%s", path, errbuf); @@ -1291,19 +1481,19 @@ plugin_remove(plugin_t *pl) { char path[PATH_MAX]; - autoplugin_set_installed(pl->pl_id, 0); + autoplugin_set_installed(pl->pl_fqid, 0); usage_event("Plugin remove", 1, - USAGE_SEG("plugin", pl->pl_id)); + USAGE_SEG("plugin", pl->pl_fqid)); - TRACE(TRACE_DEBUG, "plugin", "Uninstalling %s", pl->pl_id); + TRACE(TRACE_DEBUG, "plugin", "Uninstalling %s", pl->pl_fqid); - snprintf(path, sizeof(path), "%s/installedplugins/%s.zip", - gconf.persistent_path, pl->pl_id); + snprintf(path, sizeof(path), "%s/%s/installed/%s.zip", + gconf.persistent_path, storage_prefix, pl->pl_fqid); fa_unlink(path, NULL, 0); - snprintf(path, sizeof(path), "%s/plugins/%s", - gconf.persistent_path, pl->pl_id); + snprintf(path, sizeof(path), "%s/%s/settings/%s", + gconf.persistent_path, storage_prefix, pl->pl_fqid); fa_unlink_recursive(path, NULL, 0, 0); plugin_unload(pl); @@ -1323,9 +1513,10 @@ plugin_install(plugin_t *pl, const char *package) { char errbuf[200]; char path[200]; + scoped_char *zippath = NULL; usage_event(pl->pl_can_upgrade ? "Plugin upgrade" : "Plugin install", 1, - USAGE_SEG("plugin", pl->pl_id, + USAGE_SEG("plugin", pl->pl_fqid, "source", package ? "File" : "Repo")); if(package == NULL) @@ -1341,7 +1532,7 @@ plugin_install(plugin_t *pl, const char *package) } TRACE(TRACE_INFO, "plugins", "Downloading plugin %s from %s", - pl->pl_id, package); + pl->pl_fqid, package); prop_link(_p("Downloading"), status); prop_set(pl->pl_status, "canInstall", PROP_SET_INT, 0); @@ -1356,7 +1547,7 @@ plugin_install(plugin_t *pl, const char *package) prop_unlink(status); prop_set_string(status, errbuf); TRACE(TRACE_INFO, "plugins", "Failed to download plugin %s from %s -- %s", - pl->pl_id, package, errbuf); + pl->pl_fqid, package, errbuf); cleanup: prop_set(pl->pl_status, "canInstall", PROP_SET_INT, 1); @@ -1370,23 +1561,27 @@ plugin_install(plugin_t *pl, const char *package) buf[0] != 0x50 || buf[1] != 0x4b || buf[2] != 0x03 || buf[3] != 0x04) { prop_link(_p("Corrupt plugin bundle"), status); TRACE(TRACE_INFO, "plugins", "Plugin %s from %s -- not a valid bundle", - pl->pl_id, package); + pl->pl_fqid, package); hexdump("BUNDLE", buf, MIN(b->b_size, 64)); goto cleanup; } TRACE(TRACE_INFO, "plugins", "Plugin %s valid ZIP archive %d bytes", - pl->pl_id, (int)b->b_size); + pl->pl_fqid, (int)b->b_size); - snprintf(path, sizeof(path), "%s/installedplugins", gconf.persistent_path); + snprintf(path, sizeof(path), "%s/%s", gconf.persistent_path, + storage_prefix); + fa_makedir(path); + snprintf(path, sizeof(path), "%s/%s/installed", gconf.persistent_path, + storage_prefix); fa_makedir(path); plugin_unload(pl); prop_link(_p("Installing"), status); - snprintf(path, sizeof(path), "%s/installedplugins/%s.zip", - gconf.persistent_path, pl->pl_id); + snprintf(path, sizeof(path), "%s/%s/installed/%s.zip", + gconf.persistent_path, storage_prefix, pl->pl_fqid); if(fa_unlink(path, errbuf, sizeof(errbuf))) { TRACE(TRACE_DEBUG, "plugins", "First unlinking %s -- %s", @@ -1412,15 +1607,20 @@ plugin_install(plugin_t *pl, const char *package) goto cleanup; } - snprintf(path, sizeof(path), - "zip://%s/installedplugins/%s.zip", gconf.persistent_path, - pl->pl_id); - #ifdef STOS arch_sync_path(path); #endif - if(plugin_load(path, errbuf, sizeof(errbuf), + zippath = plugin_resolve_zip_path(path); + if(zippath == NULL) { + prop_unlink(status); + TRACE(TRACE_ERROR, "plugins", "Unable to load %s -- %s", path, + "Not a valid plugin archive"); + prop_set_string(status, errbuf); + goto cleanup; + } + + if(plugin_load(zippath, pl->pl_origin, errbuf, sizeof(errbuf), PLUGIN_LOAD_FORCE | PLUGIN_LOAD_AS_INSTALLED | PLUGIN_LOAD_BY_USER)) { prop_unlink(status); @@ -1584,14 +1784,21 @@ BE_REGISTER(plugin); void plugin_open_file(prop_t *page, const char *url) { - char path[200]; char errbuf[200]; buf_t *b; - snprintf(path, sizeof(path), "zip://%s/plugin.json", url); - b = fa_load(path, - FA_LOAD_ERRBUF(errbuf, sizeof(errbuf)), - NULL); + + scoped_char *zippath = plugin_resolve_zip_path(url); + if(zippath == NULL) { + nav_open_errorf(page, _("Unable to load plugin.json: %s"), + "Not a valid plugin archive"); + return; + } + + scoped_char *plugin_json = fmt("%s/plugin.json", zippath); + b = fa_load(plugin_json, + FA_LOAD_ERRBUF(errbuf, sizeof(errbuf)), + NULL); if(b == NULL) { nav_open_errorf(page, _("Unable to load plugin.json: %s"), errbuf); return; @@ -1609,7 +1816,7 @@ plugin_open_file(prop_t *page, const char *url) if(id != NULL) { hts_mutex_lock(&plugin_mutex); - plugin_t *pl = plugin_find(id, 1); + plugin_t *pl = plugin_make(id, "local"); plugin_install(pl, url); hts_mutex_unlock(&plugin_mutex); } else { @@ -1775,7 +1982,7 @@ plugin_select_view(const char *plugin_id, const char *filename) hts_mutex_lock(&plugin_mutex); LIST_FOREACH(pl, &plugins, pl_link) - if(!strcmp(pl->pl_id, plugin_id)) + if(!strcmp(pl->pl_fqid, plugin_id)) break; if(pl != NULL) { @@ -1820,7 +2027,7 @@ void plugin_uninstall(const char *id) { hts_mutex_lock(&plugin_mutex); - plugin_t *pl = plugin_find(id, 0); + plugin_t *pl = plugin_find(id); if(pl != NULL) { plugin_remove(pl); } @@ -2034,7 +2241,7 @@ plugin_autoinstall(const char *id) { int errcode = -1; hts_mutex_lock(&plugin_mutex); - plugin_t *pl = plugin_find(id, 0); + plugin_t *pl = plugin_find(id); if(pl != NULL) { errcode = plugin_install(pl, NULL); @@ -2061,7 +2268,7 @@ plugin_probe_for_autoinstall(fa_handle_t *fh, const uint8_t *buf, size_t len, autoplugin_t *ap; const char *installme = NULL; - if(!autoinstall) + if(!0) return; hts_mutex_lock(&autoplugin_mutex); @@ -2101,7 +2308,7 @@ plugin_check_prefix_for_autoinstall(const char *uri) autoplugin_t *ap; const char *installme = NULL; - if(!autoinstall || devplugins) + if(!0 || devplugins) return -1; hts_mutex_lock(&autoplugin_mutex); diff --git a/src/plugins.h b/src/plugins.h index bff6a0e6d6..d993f0e73a 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -25,7 +25,7 @@ struct prop; void plugins_init(char **devplugins); -void plugins_init2(void); +void plugins_load_all(void); int plugins_upgrade_check(void); diff --git a/src/runcontrol.c b/src/runcontrol.c index 6bf42b5814..c36353df62 100644 --- a/src/runcontrol.c +++ b/src/runcontrol.c @@ -293,24 +293,24 @@ runcontrol_init(void) if(gconf.can_standby) { init_autostandby(); init_sleeptimer(rc); - settings_create_action(dir, _p("Standby"), + settings_create_action(dir, _p("Standby"), NULL, do_standby, NULL, 0, NULL); } if(gconf.can_poweroff) - settings_create_action(dir, _p("Power off system"), + settings_create_action(dir, _p("Power off system"), NULL, do_power_off, NULL, 0, NULL); if(gconf.can_logout) - settings_create_action(dir, _p("Logout"), + settings_create_action(dir, _p("Logout"), NULL, do_logout, NULL, 0, NULL); if(gconf.can_open_shell) - settings_create_action(dir, _p("Open shell"), + settings_create_action(dir, _p("Open shell"), NULL, do_open_shell, NULL, 0, NULL); if(!gconf.can_not_exit) - settings_create_action(dir, _p("Quit"), + settings_create_action(dir, _p("Quit"), NULL, do_exit, NULL, 0, NULL); if(gconf.shell_fd > 0) { diff --git a/src/service.c b/src/service.c index c2142444c8..17fd49f902 100644 --- a/src/service.c +++ b/src/service.c @@ -103,11 +103,7 @@ service_init(void) // $global.service.all all_services = prop_create(gs, "all"); -#if ENABLE_PLUGINS - service_create0("showtime:plugin", - NULL, _p("Plugins"), "plugin:start", - "plugin", NULL, 0, 1, SVC_ORIGIN_SYSTEM); -#endif + service_create0("showtime:discovered", NULL, _p("Local network"), "discovered:", "network", NULL, 0, 1, SVC_ORIGIN_SYSTEM); diff --git a/src/settings.c b/src/settings.c index 5d647b1cfe..98cbbb60ec 100644 --- a/src/settings.c +++ b/src/settings.c @@ -298,11 +298,12 @@ settings_create_separator(prop_t *parent, prop_t *caption) * */ setting_t * -settings_create_action(prop_t *parent, prop_t *title, +settings_create_action(prop_t *parent, prop_t *title, const char *subtype, prop_callback_t *cb, void *opaque, int flags, prop_courier_t *pc) { setting_t *s = setting_create_leaf(parent, title, "action", "action", flags); + prop_set(s->s_root, "subtype", PROP_SET_STRING, subtype); s->s_sub = prop_subscribe(PROP_SUB_NO_INITIAL_UPDATE, PROP_TAG_CALLBACK, cb, opaque, PROP_TAG_NAMED_ROOT, s->s_root, "node", @@ -1720,7 +1721,7 @@ setting_get_dir(const char *key) upgrade = addgroup(pc, _p("Software upgrade")); filebrowse = addgroup(pc, _p("File browsing")); runcontrol = addgroup(pc, _p("Starting and stopping")); - plugins = addgroup(pc, _p("Plugins")); + plugins = addgroup(pc, _p("Plugin repositories")); resets = addgroup(pc, _p("Reset")); } diff --git a/src/settings.h b/src/settings.h index 0238b6c3f2..8002a0d7eb 100644 --- a/src/settings.h +++ b/src/settings.h @@ -56,6 +56,7 @@ void settings_add_int(setting_t *s, int delta); int settings_get_type(const setting_t *s); setting_t *settings_create_action(prop_t *parent, prop_t *title, + const char *subtype, prop_callback_t *cb, void *opaque, int flags, prop_courier_t *pc); diff --git a/src/text/fontstash.c b/src/text/fontstash.c index 23473014ec..8cf5896b62 100644 --- a/src/text/fontstash.c +++ b/src/text/fontstash.c @@ -343,11 +343,11 @@ fontstash_init(void) pc = prop_concat_create(fontstash_browse_nodes); prop_t *top = prop_create_root(NULL); - settings_create_action(top, _p("Reset main font to default"), + settings_create_action(top, _p("Reset main font to default"), NULL, reset_main, NULL, SETTINGS_RAW_NODES, NULL); - settings_create_action(top, _p("Reset narrow font to default"), + settings_create_action(top, _p("Reset narrow font to default"), NULL, reset_cond, NULL, SETTINGS_RAW_NODES, NULL); - settings_create_action(top, _p("Reset subtitle font to default"), + settings_create_action(top, _p("Reset subtitle font to default"), NULL, reset_subs, NULL, SETTINGS_RAW_NODES, NULL); prop_t *x = prop_create_root(NULL); diff --git a/src/ui/glw/glw.c b/src/ui/glw/glw.c index e83f955c29..3c531e1859 100644 --- a/src/ui/glw/glw.c +++ b/src/ui/glw/glw.c @@ -1785,6 +1785,7 @@ glw_focus_step(glw_t *w, int forward) return 0; e = event_create_action(forward ? ACTION_DOWN : ACTION_UP); + e->e_nav = prop_ref_inc(w->glw_root->gr_prop_nav); while(w->glw_focused != NULL) { w = w->glw_focused; @@ -2017,6 +2018,7 @@ glw_pointer_event_deliver(glw_t *w, glw_pointer_event_t *gpe) case GLW_POINTER_RIGHT_PRESS: e = event_create_action(ACTION_ITEMMENU); + e->e_nav = prop_ref_inc(gr->gr_prop_nav); e->e_flags |= EVENT_MOUSE | EVENT_SCREEN_POSITION; e->e_screen_x = gpe->screen_x; e->e_screen_y = gpe->screen_y; @@ -2043,6 +2045,7 @@ glw_pointer_event_deliver(glw_t *w, glw_pointer_event_t *gpe) glw_path_modify(w, 0, GLW_IN_PRESSED_PATH, NULL); e = event_create_action_multi((const action_type_t[]){ ACTION_CLICK, ACTION_ACTIVATE}, 2); + e->e_nav = prop_ref_inc(gr->gr_prop_nav); e->e_flags |= flags | EVENT_SCREEN_POSITION; e->e_screen_x = gpe->screen_x; e->e_screen_y = gpe->screen_y; @@ -2068,6 +2071,7 @@ glw_touch_longpress(glw_root_t *gr) gr->gr_pointer_press_time = 0; glw_t *w = gr->gr_pointer_press; event_t *e = event_create_action(ACTION_ITEMMENU); + e->e_nav = prop_ref_inc(gr->gr_prop_nav); int r = glw_event_to_widget(w, e); event_release(e); if(r) { @@ -3171,6 +3175,7 @@ glw_osk_done(glw_root_t *gr, int submit) if(w != NULL) { if(submit) { event_t *e = event_create_action(ACTION_SUBMIT); + e->e_nav = prop_ref_inc(gr->gr_prop_nav); glw_event_to_widget(w, e); event_release(e); } else { diff --git a/src/ui/glw/glw_x11.c b/src/ui/glw/glw_x11.c index 1510699edf..ec69cf3089 100644 --- a/src/ui/glw/glw_x11.c +++ b/src/ui/glw/glw_x11.c @@ -58,7 +58,7 @@ typedef struct glw_x11 { glw_root_t gr; - int running; + int *running; hts_thread_t thread; Display *display; @@ -989,7 +989,7 @@ glw_x11_mainloop(glw_x11_t *gx11) glw_set_fullscreen(&gx11->gr, gx11->is_fullscreen); - while(gx11->running) { + while(*gx11->running) { autohide_cursor(gx11); @@ -1323,40 +1323,20 @@ glw_x11_thread(void *aux) /** * */ -static void * -glw_x11_start(struct prop *nav) +void +glw_x11_main(int *running) { glw_x11_t *gx11 = calloc(1, sizeof(glw_x11_t)); gx11->gr.gr_prop_ui = prop_create_root("ui"); - gx11->gr.gr_prop_nav = nav ?: nav_spawn(); - gx11->running = 1; + gx11->gr.gr_prop_nav = nav_spawn(); + gx11->running = running; + glw_x11_thread(gx11); hts_thread_create_joinable("glw", &gx11->thread, glw_x11_thread, gx11, 0); - return gx11; -} - -/** - * - */ -static prop_t * -glw_x11_stop(void *aux) -{ - glw_x11_t *gx11 = aux; glw_root_t *gr = &gx11->gr; - prop_t *nav = gr->gr_prop_nav; - gx11->running = 0; - hts_thread_join(&gx11->thread); prop_destroy(gr->gr_prop_ui); glw_release_root(gr); - return nav; } - - - -const linux_ui_t ui_glw = { - .start = glw_x11_start, - .stop = glw_x11_stop, -}; diff --git a/support/artwork/movian-128.png b/support/artwork/movian-128.png new file mode 100644 index 0000000000..40ee4e250b Binary files /dev/null and b/support/artwork/movian-128.png differ diff --git a/support/command-movian.wrapper b/support/command-movian.wrapper new file mode 100755 index 0000000000..545e26ffb5 --- /dev/null +++ b/support/command-movian.wrapper @@ -0,0 +1,8 @@ +#!/bin/bash +export PATH="$SNAP/bin:$SNAP/gnome-platform/usr/bin:$PATH" +export LD_LIBRARY_PATH="$SNAP_LIBRARY_PATH:$SNAP/gnome-platform/usr/lib/x86_64-linux-gnu:$SNAP/gnome-platform/usr/lib/x86_64-linux-gnu/mesa:$SNAP/gnome-platform/usr/lib/x86_64-linux-gnu/pulseaudio:$SNAP/lib" +export LIBGL_DRIVERS_PATH="$SNAP/gnome-platform/usr/lib/x86_64-linux-gnu/dri" +export XLOCALEDIR="$SNAP/gnome-platform/usr/share/X11/locale" +export XCURSOR_PATH="$SNAP/gnome-platform/usr/share/icons" +export HOME=`getent passwd $UID | cut -d ':' -f 6` +exec "$SNAP/bin/movian" "$@" diff --git a/support/movian.desktop b/support/movian.desktop new file mode 100644 index 0000000000..9ac756311c --- /dev/null +++ b/support/movian.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=Movian +GenericName=Media Player +Icon=${SNAP}/usr/share/movian/icons/movian-128.png +TryExec=movian +Exec=movian %U +Terminal=false +Categories=Audio;Music;Player;AudioVideo; +StartupWMClass=movian diff --git a/support/snap.yaml b/support/snap.yaml new file mode 100644 index 0000000000..a5f5822a48 --- /dev/null +++ b/support/snap.yaml @@ -0,0 +1,25 @@ +name: movian +version: @@VERSION@@ +summary: Media player +architectures: +- amd64 +confinement: strict +grade: stable +plugs: + gnome-3-26-1604: + default-provider: gnome-3-26-1604 + interface: content + target: $SNAP/gnome-platform +apps: + movian: + command: command-movian.wrapper + environment: + TMPDIR: $XDG_RUNTIME_DIR + plugs: + - desktop + - home + - network + - network-bind + - opengl + - pulseaudio + - x11