diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f07d275 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# ignore build directories +src/obj_arm_gnueabi/ +src/obj_emu/ +# ignore QtCreator project files +pbxmms2client.cflags +pbxmms2client.config +pbxmms2client.creator* +pbxmms2client.cxxflags +pbxmms2client.files +pbxmms2client.includes +# other +*~ diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..d1e2a3e --- /dev/null +++ b/ChangeLog @@ -0,0 +1,11 @@ +pbxmms2client v0.006 (first release on GitHub) + - fix ALSA lib warning "Invalid CTL" + - fix saving volume level + - update copyrights +.05.2020 + +pbxmms2client v0.005 + - various internal improvements + - added displaying battery charge + - added displaying current playtime +18.03.2020 diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..e69de29 diff --git a/scripts/pbxmms2client.app b/scripts/pbxmms2client.app new file mode 100755 index 0000000..fb39eed --- /dev/null +++ b/scripts/pbxmms2client.app @@ -0,0 +1,28 @@ + # + # pbxmms2client.app + # + # Copyright (C) 2014-2020 Programmist11180 + # + # This file is part of PBXMMS2client. + # + # PBXMMS2client 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. + # + # PBXMMS2client 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 PBXMMS2client. If not, see . + # + +#!/bin/sh +export LD_PRELOAD=/mnt/ext1/system/lib/preloadable_libiconv.so +export LD_LIBRARY_PATH=/mnt/ext1/system/lib +export XDG_CACHE_HOME=/mnt/ext1/system/cache +export XDG_CONFIG_HOME=/mnt/ext1/system/config + +exec /mnt/ext1/system/bin/pbxmms2client.app diff --git a/scripts/xmms2-launcher b/scripts/xmms2-launcher new file mode 100755 index 0000000..38cdddb --- /dev/null +++ b/scripts/xmms2-launcher @@ -0,0 +1,28 @@ +# +# xmms2-launcher +# +# Copyright (C) 2014-2020 Programmist11180 +# +# This file is part of PBXMMS2client. +# +# PBXMMS2client 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. +# +# PBXMMS2client 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 PBXMMS2client. If not, see . +# + +#!/bin/sh +export LD_PRELOAD=/mnt/ext1/system/lib/preloadable_libiconv.so +export LD_LIBRARY_PATH=/mnt/ext1/system/lib +export XDG_CACHE_HOME=/mnt/ext1/system/cache +export XDG_CONFIG_HOME=/mnt/ext1/system/config + +exec /mnt/ext1/system/bin/xmms2-launcher.bin -- --plugindir=/mnt/ext1/system/lib/xmms2_plugins --quiet diff --git a/scripts/xmms2_cli b/scripts/xmms2_cli new file mode 100755 index 0000000..9918dec --- /dev/null +++ b/scripts/xmms2_cli @@ -0,0 +1,29 @@ +# +# xmms2-cli +# +# Copyright (C) 2014-2020 Programmist11180 +# +# This file is part of PBXMMS2client. +# +# PBXMMS2client 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. +# +# PBXMMS2client 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 PBXMMS2client. If not, see . +# + +#!/bin/sh +export LD_PRELOAD=/mnt/ext1/system/lib/preloadable_libiconv.so +export LD_LIBRARY_PATH=/mnt/ext1/system/lib +export XDG_CACHE_HOME=/mnt/ext1/system/cache +export XDG_CONFIG_HOME=/mnt/ext1/system/config + +cd /mnt/ext1/system/bin +exec /mnt/ext1/system/bin/xmms2 "$@" diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..5657051 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,73 @@ + # + # Makefile + # + # Copyright (C) 2014-2020 Programmist11180 + # + # This file is part of PBXMMS2client. + # + # PBXMMS2client 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. + # + # PBXMMS2client 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 PBXMMS2client. If not, see . + # + +OUT = pbxmms2client +include /usr/local/pocketbook/common.mk + +SOURCES += fork.cpp main.cpp client_screen.cpp settings.cpp log.cpp xmms.cpp + +CXXFLAGS+= -Wall -Wextra -I$(HOME)/pb_programming/pbtk-0.2.1 \ +`pkg-config --cflags xmms2-client freetype2 glib-2.0` + +ifeq (${BUILD},emu) +CXXFLAGS+= -std=c++11 -Wno-write-strings `pkg-config --cflags sigc++-2.0` +LDFLAGS+= -L$(HOME)/pb_programming/pblibs_emulator +endif + +ifeq (${BUILD},arm_gnueabi) +CXXFLAGS+= -std=c++98 -I$(HOME)/pb_programming/sigc++-arm-bin/include/sigc++-2.0 +LDFLAGS+= -L$(HOME)/pb_programming/pblibs_arm_gnueabi +endif + +LIBS+= -linkview -lpbtk -lsigc-2.0 -lxmmsclient -lglib-2.0 -lxmmsclient-glib -lpthread -lpng12 + + +PIXMAPS= + +PIXMAPS_C=$(PIXMAPS:.xpm=.c) +PIXMAPS_OBJS=$(addprefix $(OBJDIR)/,$(PIXMAPS_C:.c=.o)) + +all: $(PROJECT) + +$(PROJECT): $(PIXMAPS_C) $(OBJDIR) $(OBJS) $(PIXMAPS_OBJS) + $(CXX) -o $@ $(OBJS) $(PIXMAPS_OBJS) $(LDFLAGS) $(LIBS) + +# $(PROJECT) : $(OBJDIR) $(SYSTEM_LINK) $(OBJS) $(BITMAP_OBJS) +# $(LD) -o $@ $(OBJS) $(BITMAP_OBJS) $(LDFLAGS) $(LIBS) + +#$(OBJDIR): +# mkdir -p $(OBJDIR) +# mkdir -p $(OBJDIR) + +$(OBJDIR)/%.cxx.o: %.cxx + $(CXX) -c -o $@ $(CXXFLAGS) $(INCLUDES) $(CDEPS) $< +$(OBJDIR)/%.cpp.o: %.cpp + $(CXX) -c -o $@ $(CXXFLAGS) $(INCLUDES) $(CDEPS) $< + +$(OBJDIR)/images/%.o: images/%.c + $(CC) -c -o $@ $(CFLAGS) $(INCLUDES) $(CDEPS) $< + +$(PIXMAPS_C): $(PIXMAPS) + +images/%.c: images/%.xpm + ./xpbres -c $@ $< + +-include $(OBJDIR)/*.d diff --git a/src/client_screen.cpp b/src/client_screen.cpp new file mode 100644 index 0000000..1acb16a --- /dev/null +++ b/src/client_screen.cpp @@ -0,0 +1,722 @@ +/* + * client_screen.cpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#include +#include +#include +#include +#include "client_screen.hpp" +#include "fork.hpp" +#include "log.hpp" +#include "settings.hpp" +#include "xmms.hpp" + +using std::vector; + +const int CREATE_PLAYLIST_num = 41; +const int CLEAR_PLAYLIST_num = 42; +const int DELETE_PLAYLIST_num = 43; +static imenu playlist_menu [] = { + {ITEM_HEADER, PBXMMS2CLIENT_num, PBXMMS2CLIENT, NULL}, + {ITEM_ACTIVE, CREATE_PLAYLIST_num, "Create playlist", NULL}, + {ITEM_ACTIVE, CLEAR_PLAYLIST_num, "Clear playlist", NULL}, + {ITEM_ACTIVE, DELETE_PLAYLIST_num, "Delete playlist", NULL}, + {0, 0, NULL, NULL} +}; + +const int ADD_FILE_num = 51; +const int ADD_DIR_num = 52; +const int TRACK_DELETE_num = 32; +const int TRACK_INFO_num = 33; +const int GO_TO_num =34; +const int GO_TO_ACTIVE_TRACK_num = 35; +const int GO_TO_POS_num = 36; +static imenu track_menu [] = { + {ITEM_HEADER, PBXMMS2CLIENT_num, PBXMMS2CLIENT, NULL}, + {ITEM_ACTIVE, TRACK_INFO_num, "Track information", NULL}, + {ITEM_ACTIVE, GO_TO_ACTIVE_TRACK_num, "Go to current track", NULL}, + {ITEM_ACTIVE, GO_TO_POS_num, "Go to track №...", NULL}, + {ITEM_ACTIVE, ADD_FILE_num, "Add file", NULL}, + {ITEM_ACTIVE, ADD_DIR_num, "Add directory", NULL}, + {ITEM_ACTIVE, TRACK_DELETE_num, "Delete track", NULL}, + {0, 0, NULL, NULL} +}; + +const char str_empty [] = ""; + +struct track_item +{ + xmmsc_result_t *track_result; + const char *title, *artist, *album, *url, *filename, *genre, *date, *sample_format, *comment, *performer, *composer, *copyright, *original_artist; + int id, duration, tracknr, bitrate, size, samplerate, channels; +}; + +struct playlist_item +{ + vector tracks; + xmmsc_result_t *playlist_result; + const char *name; +}; + +extern client_screen *clsc; +extern int playback_status; +static vector playlists; +static xmmsc_result_t *playlists_result; +static int active_track = -1, active_playlist = -1, prev_playtime; +pthread_t bcast_thread; + +short int find_playlist (const char *name) +{ + for (unsigned short int pl=0; plmark_active_track(-1); + clsc->mark_active_playlist(find_playlist(msg.name)); + break; + case CURRENT_POS: + write_to_log(LOG_DBG,"broadcast CURRENT_POS, playlist: '%s'; position: %d",msg.name,msg.current_position); + pl=find_playlist(msg.name); + clsc->mark_active_playlist(pl); + if (pl==-1) write_to_log(LOG_WARN,"Playlist '%s' not found.",msg.name); + else clsc->mark_active_track(msg.current_position); + clsc->show_batt_power(NULL); + prev_playtime=0; + break; + case PLAYBACK_STATUS: + playback_status=msg.playback_status; + switch (playback_status) { + case XMMS_PLAYBACK_STATUS_PLAY: set_wake_lock(true); + break; + case XMMS_PLAYBACK_STATUS_STOP: + case XMMS_PLAYBACK_STATUS_PAUSE: set_wake_lock(false); + break; + default: + write_to_log(LOG_WARN,"Unknown playback status: %d",playback_status); + break; + }; + break; + case PLAYLIST_CHANGED: + write_to_log(LOG_DBG,"broadcast PLAYLIST_CHANGED, name: %s; type: %d,",msg.name,msg.pc.type); + switch (msg.pc.type) { + case XMMS_PLAYLIST_CHANGED_ADD: + pl=find_playlist(msg.name); + playlists.at(pl).tracks.push_back(track_item()); + if (clsc->track_get_info(msg.pc.id, pl)==-1) + { + playlists.at(pl).tracks.pop_back(); + break; + }; + clsc->track_form_info(playlists.at(pl).tracks.size()-1); + clsc->tracks_list->update(); + break; + case XMMS_PLAYLIST_CHANGED_INSERT: + write_to_log(LOG_ERR,"broadcast XMMS_PLAYLIST_CHANGED_INSERT not implemented."); + break; + case XMMS_PLAYLIST_CHANGED_SHUFFLE: + break; + case XMMS_PLAYLIST_CHANGED_REMOVE: + if (clsc->playlists_list->getSelectedIndex()==active_playlist) + clsc->tracks_list->erase(clsc->tracks_list->getItem(msg.pc.position)); + clsc->update(); + pl=find_playlist(msg.name); + if (msg.pc.positiontracks_clear(pl); + if (active_playlist==clsc->playlists_list->getSelectedIndex()) clsc->tracks_list->clear(); + clsc->tracks_list->update(); + break; + case XMMS_PLAYLIST_CHANGED_MOVE: + break; + case XMMS_PLAYLIST_CHANGED_SORT: + break; + case XMMS_PLAYLIST_CHANGED_UPDATE: + break; + default: + //clsc->update(); + write_to_log(LOG_WARN,"Unknown playlist broadcast type: %d",msg.pc.type); + break; + }; + break; + case READER_STATUS: sem_wait(reader_sem); + break; + case PLAYTIME: clsc->show_playtime(msg.playtime); + break; + default: + write_to_log(LOG_WARN,"Unknown broadcast type: %d",msg.type); + break; + }; + }; + return NULL; +} + +client_screen::client_screen (void): PBWidget ("mscreen", NULL) +{ + pthread_create(&bcast_thread, NULL, client_screen::broadcast_handler, NULL); + SetOrientation(pbxmms_settings->orientation); + font = OpenFont(DEFAULTFONT, pbxmms_settings->font_size, 1); + /*if (font==NULL) { + print_error("Failed to open font!"); + font = OpenFont(DEFAULTFONT, pbxmms_settings->font_size, 1); + };*/ + active_font = OpenFont(DEFAULTFONTB, pbxmms_settings->font_size, 1); + /*if (active_font==NULL) { + print_error("Failed to open font!"); + active_font = OpenFont(DEFAULTFONTB, pbxmms_settings->font_size, 1); + };*/ + this->setWidgetFont(this->font); + tracks_title = new PBLabel("tracks_title", this); + tracks_title->setCanBeFocused(false); + tracks_title->setText(" Title - Album - Artist "); + this->addWidget(tracks_title); + tracks_list = new PBListBox ("tracks_list", this); + this->addWidget(tracks_list); + playlists_list = new PBListBox("playlists_list", this); + this->addWidget(playlists_list); + playtime = new PBLabel("", this); + playtime->setCanBeFocused(false); + this->addWidget(playtime); + battery = new PBLabel("", this); + battery->setCanBeFocused(false); + this->addWidget(battery); + this->set_widgets(); + playlists_list->onFocusedWidgetChanged.connect(sigc::mem_fun(this, &client_screen::playlist_show)); + load_all_playlists(); + last_dir.assign("/mnt"); + mark_active_playlist(playlist_current_active()); + playlist_current_pos(); + mark_active_track(active_track); + playback_status = server_playback_status(); + //playlists_list->setFocused(true); + tracks_list->setFocused(true); + playlists_list->selectItem(active_playlist); + tracks_list->selectItem(active_track); + onFocusedWidgetChanged.connect(sigc::mem_fun(this, &client_screen::show_batt_power)); + tracks_list->onFocusedWidgetChanged.connect(sigc::mem_fun(this, &client_screen::show_batt_power)); +} + +client_screen::~client_screen () +{ + pthread_cancel(bcast_thread); + while (!playlists.empty()) { + client_screen::tracks_clear (playlists.size()-1); + xmmsc_result_unref(playlists.back().playlist_result); + playlists.pop_back(); + }; + if (playlists_result) xmmsc_result_unref(playlists_result); + delete tracks_title; + delete tracks_list; + delete playlists_list; + delete battery; + CloseFont(font); + CloseFont(active_font); +} + +void client_screen::set_widgets (void) +{ + switch (pbxmms_settings->orientation) { + case ROTATE0: this->setSize(0, 0, ScreenWidth(), ScreenHeight()); + tracks_title->setSize(1, 5, this->w()-10, 25); + tracks_list->setSize(1, tracks_title->h()+5, this->w()-10, this->h()-150-tracks_title->h()); + playlists_list->setSize(1, tracks_list->y()+tracks_list->h()+5, this->w()/2, + this->h()-tracks_list->y()-tracks_list->h()-10); + playtime->setSize(playlists_list->x()+playlists_list->w()+5,tracks_list->y()+ + tracks_list->h()+5,220,25); + battery->setSize(playlists_list->x()+playlists_list->w()+5,playtime->y()+playtime->h()+5, + 140,25); + break; + case ROTATE90: // FIXME: add support for all orientations + case ROTATE180: + case ROTATE270: + write_to_log(LOG_WARN,"Orientation %d not implemented.",pbxmms_settings->orientation); + break; + default: + write_to_log(LOG_WARN,"Unknown orientation: %d, set back to default.",pbxmms_settings->orientation); + pbxmms_settings->orientation = ROTATE0; + client_screen::set_widgets(); + return; + }; + FullUpdate(); //FullUpdateHQ(); // NOTE: what a difference between FullUpdate() and FullUpdateHQ() ? +} + +void client_screen::show_batt_power (PBWidget *) +{ + string str1; + char str2[50]; + sprintf(str2,"Battery: %d%%",GetBatteryPower()); + str1.assign(str2); + clsc->battery->setText(str1); + clsc->battery->update(); +} + +void client_screen::show_playtime (int ptime) +{ + string str1; + char str2[50]; + if (ptime - prev_playtime < 5000) return; // FIXME: add way to change playtime update interval in settings window + else prev_playtime = ptime; + if (GetDialogShow()) return; + sprintf(str2,"%2d m : %2d s / %2d m : %2d s",div(div(ptime,1000).quot,60).quot,div(div(ptime,1000).quot,60).rem, + div(div(playlists.at(active_playlist).tracks.at(active_track).duration,1000).quot,60).quot, + div(div(playlists.at(active_playlist).tracks.at(active_track).duration,1000).quot,60).rem ); + str1.assign(str2); + clsc->playtime->setText(str1); + clsc->playtime->update(); +} + +void client_screen::track_add_handler (int isok, PBFileChooser *pbfc) +{ + if (isok) { + char *path = new char[pbfc->getPath().length()+10]; + sprintf(path, "file://%s", pbfc->getPath().c_str()); + result_is_error(xmmsc_playlist_add_url + (pbconnection, playlists.at(clsc->playlists_list->getSelectedIndex()).name, path), true); + delete [] path; + }; + clsc->last_dir.assign(pbfc->getPath().c_str(), pbfc->getPath().find_last_of('/')); + clsc->update(); + return; +} + +void client_screen::track_delete_handler (int isok) +{ + if (isok==1) { + result_is_error(xmmsc_playlist_remove_entry(pbconnection, + playlists.at(clsc->playlists_list->getSelectedIndex()).name, clsc->tracks_list->getSelectedIndex())); + }; +} + +void client_screen::track_information (void) +{ + track_item & t = playlists.at + (this->playlists_list->getSelectedIndex()).tracks.at(this->tracks_list->getSelectedIndex()); + char *inf = new char [1024]; + sprintf(inf, "Title: %s\nAlbum: %s\nArtist: %s\nPerformer: %s\nGenre: %s\nDate: %s\nOriginal artist: %s\nComposer: %s\nCopyright: %s\n" + "Comment: %s\nDuration: %d mn %d s\n" + "Track number: %d\nBitrate: %d kbps\nSamplerate: %d Hz\nSample format: %s\n" + "Channels: %d\nSize: %d,%d MB\n", t.title, t.album, t.artist, t.performer, t.genre, t.date, t.original_artist, t.composer, t.copyright, + t.comment, + div(t.duration,60000).quot, div(div(t.duration,60000).rem,1000).quot, t.tracknr, t.bitrate/1000, + t.samplerate, t.sample_format, t.channels, div((t.size/1000),1000).quot, div((t.size/1000),1000).rem); + Message(ICON_INFORMATION, PBXMMS2CLIENT, inf, long_timeout); + delete [] inf; +} + +void client_screen::pos_selector_handler (int page) +{ + if (page==99999) clsc->tracks_list->selectItem(clsc->tracks_list->itemsCount()-1); + else if (page>clsc->tracks_list->itemsCount()) Message(ICON_WARNING, PBXMMS2CLIENT, "Position is outside of the playlist.", 10000); + else clsc->tracks_list->selectItem(page-1); + return; +} + +void client_screen::dir_add_handler (char *path) +{ + if (path) + { + char *p = new char[strlen(path)+10]; +#ifdef __EMU__ + sprintf(p, "file://%s", strstr(path, "ext2")+4); +#else + sprintf(p, "file://%s", path); +#endif + result_is_error(xmmsc_playlist_radd + (pbconnection, playlists.at(clsc->playlists_list->getSelectedIndex()).name, p), true); + delete [] p; + }; + clsc->update(); + return; +} + +void client_screen::screen_menus_handler (int index) +{ + char *p; + int bsize = 1024; + switch (index) { + case CREATE_PLAYLIST_num: p = new char[bsize]; + strcpy(p, "new_playlist"); + OpenKeyboard(PBXMMS2CLIENT, p, bsize, KBD_NORMAL | KBD_SENDKEYBOARDSTATEEVENTS //| + /*KBD_PASSEVENTS*/, clsc->playlist_create_handler); + break; + case CLEAR_PLAYLIST_num: p = new char[bsize]; + sprintf(p, "Clear playlist \"%s\" ?", playlists.at(clsc->playlists_list->getSelectedIndex()).name); + Dialog(ICON_QUESTION, PBXMMS2CLIENT, p, "Yes", "No", clsc->playlist_clear_handler); + delete [] p; + break; + case DELETE_PLAYLIST_num: p = new char[bsize]; + sprintf(p, "Delete playlist \"%s\" ?", playlists.at(clsc->playlists_list->getSelectedIndex()).name); + Dialog(ICON_QUESTION, PBXMMS2CLIENT, p, "Yes", "No", clsc->playlist_delete_handler); + delete [] p; + break; + case ADD_FILE_num: OpenFileChooser("Add files", clsc->last_dir.data(), "*\n*.mp3\n*.flac\n*.ogg", + PBFileChooser::PBFC_OPEN, (pb_dialoghandler) track_add_handler); + break; + case TRACK_DELETE_num: if (clsc->tracks_list->itemsCount()) { + p = new char[bsize]; + sprintf(p, "Delete track \"%s\" ?", playlists.at(clsc->playlists_list->getSelectedIndex()). + tracks.at(clsc->tracks_list->getSelectedIndex()).filename); + Dialog(ICON_QUESTION, PBXMMS2CLIENT, p, "Yes", "No", client_screen::track_delete_handler); + delete [] p; + } + else Message(ICON_WARNING, PBXMMS2CLIENT, "No track selected!", long_timeout); + break; + case TRACK_INFO_num: if (clsc->tracks_list->itemsCount()) clsc->track_information(); + else Message(ICON_WARNING, PBXMMS2CLIENT, "No track selected!", long_timeout); + break; + case GO_TO_ACTIVE_TRACK_num: if (active_playlist==-1 || active_track==-1) { + Message(ICON_WARNING, PBXMMS2CLIENT, "Current track is absent.", long_timeout); + break; + }; + if (clsc->playlists_list->getSelectedIndex()!=active_playlist) + clsc->playlists_list->selectItem(active_playlist); + clsc->tracks_list->selectItem(active_track); + break; + case GO_TO_POS_num: OpenPageSelector(clsc->pos_selector_handler); + break; + case ADD_DIR_num: static char buf[512]; + OpenDirectorySelector(PBXMMS2CLIENT, buf, 512, clsc->dir_add_handler); + break; + default: write_to_log(LOG_WARN, "screen_menus_handler(): unknown menu namber: %d", index); + break; + } +} + +void client_screen::key_screen_handler (int key) +{ + switch (key) { + case KEY_MENU: if (this->tracks_list->isFocused()) { + if (this->tracks_list->itemsCount()) OpenMenu(track_menu,1,this->tracks_list->getSelectedItem()->x(), + this->tracks_list->getSelectedItem()->y(), client_screen::screen_menus_handler); + else OpenMenu(track_menu, 1, 0, 0, client_screen::screen_menus_handler); + }; + if (this->playlists_list->isFocused()) + OpenMenu(playlist_menu, 1, this->playlists_list->getSelectedItem()->x(), + this->playlists_list->getSelectedItem()->y(), screen_menus_handler); + break; + case KEY_OK: if (this->tracks_list->isFocused() && this->tracks_list->itemsCount()) + client_screen::track_clicked(NULL, 0); + break; + default: + break; + } +} + +short int client_screen::track_get_info (int track_id, int playlist_num) +{ + playlists.at(playlist_num).tracks.back().track_result=xmmsc_medialib_get_info(pbconnection, track_id); + xmmsv_t *track_value = result_is_error(playlists.at(playlist_num).tracks.back().track_result, false); + if (track_value == NULL) return -1; + xmmsv_t *track_info = xmmsv_propdict_to_dict(track_value, NULL); + if (value_is_error(track_info)) return -1; + xmmsv_t *dict_entry = NULL; + if (! xmmsv_dict_get (track_info, "title", &dict_entry) || + ! xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().title)) { + playlists.at(playlist_num).tracks.back().title = str_empty; + }; + if (!xmmsv_dict_get (track_info, "album", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().album)) { + playlists.at(playlist_num).tracks.back().album = str_empty; + }; + if (!xmmsv_dict_get (track_info, "artist", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().artist)) { + playlists.at(playlist_num).tracks.back().artist = str_empty; + }; + if (!xmmsv_dict_get (track_info, "duration", &dict_entry) || + !xmmsv_get_int (dict_entry, &playlists.at(playlist_num).tracks.back().duration)) { + playlists.at(playlist_num).tracks.back().duration = 0; + }; + if (!xmmsv_dict_get (track_info, "genre", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().genre)) { + playlists.at(playlist_num).tracks.back().genre = str_empty; + }; + if (!xmmsv_dict_get (track_info, "tracknr", &dict_entry) || + !xmmsv_get_int (dict_entry, &playlists.at(playlist_num).tracks.back().tracknr)) { + playlists.at(playlist_num).tracks.back().tracknr = 0; + }; + if (!xmmsv_dict_get (track_info, "bitrate", &dict_entry) || + !xmmsv_get_int (dict_entry, &playlists.at(playlist_num).tracks.back().bitrate)) { + playlists.at(playlist_num).tracks.back().bitrate = 0; + }; + if (!xmmsv_dict_get (track_info, "date", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().date)) { + playlists.at(playlist_num).tracks.back().date = str_empty; + }; + if (!xmmsv_dict_get (track_info, "size", &dict_entry) || + !xmmsv_get_int (dict_entry, &playlists.at(playlist_num).tracks.back().size)) { + playlists.at(playlist_num).tracks.back().size = 0; + }; + if (!xmmsv_dict_get (track_info, "samplerate", &dict_entry) || + !xmmsv_get_int (dict_entry, &playlists.at(playlist_num).tracks.back().samplerate)) { + playlists.at(playlist_num).tracks.back().samplerate = 0; + }; + if (!xmmsv_dict_get (track_info, "sample_format", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().sample_format)) { + playlists.at(playlist_num).tracks.back().sample_format = str_empty; + }; + if (!xmmsv_dict_get (track_info, "channels", &dict_entry) || + !xmmsv_get_int (dict_entry, &playlists.at(playlist_num).tracks.back().channels)) { + playlists.at(playlist_num).tracks.back().channels = 0; + }; + if (!xmmsv_dict_get (track_info, "comment", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().comment)) { + playlists.at(playlist_num).tracks.back().comment = str_empty; + }; + if (!xmmsv_dict_get (track_info, "performer", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().performer)) { + playlists.at(playlist_num).tracks.back().performer = str_empty; + }; + if (!xmmsv_dict_get (track_info, "composer", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().composer)) { + playlists.at(playlist_num).tracks.back().composer = str_empty; + }; + if (!xmmsv_dict_get (track_info, "copyright", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().copyright)) { + playlists.at(playlist_num).tracks.back().copyright = str_empty; + }; + if (!xmmsv_dict_get (track_info, "original_artist", &dict_entry) || + !xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().original_artist)) { + playlists.at(playlist_num).tracks.back().original_artist = str_empty; + }; + + //xmmsv_dict_foreach(track_info, &dict_foreach2, NULL); + + xmmsv_dict_get (track_info, "url", &dict_entry); + xmmsv_get_string (dict_entry, &playlists.at(playlist_num).tracks.back().url); + playlists.at(playlist_num).tracks.back().filename=strrchr(playlists.at(playlist_num).tracks.back().url,'/')+1; + playlists.at(playlist_num).tracks.back().id = track_id; + xmmsv_unref (track_info); + return 0; +} + +void client_screen::playlist_load (int pnum) +{ + client_screen::tracks_clear(pnum); + if (playlists.at(pnum).playlist_result) xmmsc_result_unref(playlists.at(pnum).playlist_result); + playlists.at(pnum).playlist_result = xmmsc_playlist_list_entries (pbconnection, playlists.at(pnum).name); + xmmsv_t *_tracks = result_is_error(playlists.at(pnum).playlist_result, false); + if (_tracks == NULL) return; + xmmsv_list_iter_t *iter; + int tnum; + xmmsv_t *list_entry; + xmmsv_get_list_iter(_tracks, &iter); + xmmsv_list_iter_first(iter); + while (xmmsv_list_iter_valid(iter)) { + xmmsv_list_iter_entry(iter, &list_entry); + xmmsv_get_int(list_entry, &tnum); + xmmsv_list_iter_next(iter); + playlists.at(pnum).tracks.push_back(track_item()); + if (track_get_info(tnum, pnum)==-1) playlists.at(pnum).tracks.pop_back(); + }; +} + +void client_screen::playlist_create_handler (char *name) +{ + if (name) { + result_is_error(xmmsc_playlist_create(pbconnection, name), true); + clsc->load_all_playlists(); // FIXME: we must not reload all playlists in this place + clsc->update(); + delete [] name; + }; +} + +void client_screen::tracks_clear (int pnum) +{ + while (!playlists.at(pnum).tracks.empty()) { + if (playlists.at(pnum).tracks.back().track_result) + xmmsc_result_unref(playlists.at(pnum).tracks.back().track_result); + playlists.at(pnum).tracks.pop_back(); + }; +} + +void client_screen::playlist_clear_handler (int ok) +{ + if (ok==1) result_is_error(xmmsc_playlist_clear(pbconnection, + playlists.at(clsc->playlists_list->getSelectedIndex()).name), true); +} + +void client_screen::playlist_delete_handler (int ok) +{ + if (ok==1) { + result_is_error(xmmsc_playlist_remove(pbconnection, + playlists.at(clsc->playlists_list->getSelectedIndex()).name), true); + clsc->tracks_list->clear(); + clsc->tracks_clear(clsc->playlists_list->getSelectedIndex()); + xmmsc_result_unref(playlists.at(clsc->playlists_list->getSelectedIndex()).playlist_result); + playlists.erase(playlists.begin()+clsc->playlists_list->getSelectedIndex()); + if (clsc->playlists_list->getSelectedIndex()playlists_list->getSelectedIndex()==active_playlist) active_playlist=-1; + clsc->mark_active_playlist(active_playlist); + clsc->playlists_list->erase(clsc->playlists_list->getSelectedItem()); + clsc->playlists_list->onFocusedWidgetChanged.emit(NULL); + clsc->playlists_list->update(); + }; +} + +void client_screen::track_form_info (unsigned int track_num) +{ + static char itm [2048]; + sprintf(itm, "%s - %s - %s\n", + //playlists.at(client_screen::playlists_list->getSelectedIndex()).tracks.at(track_num).tracknr, + playlists.at(client_screen::playlists_list->getSelectedIndex()).tracks.at(track_num).title, + playlists.at(client_screen::playlists_list->getSelectedIndex()).tracks.at(track_num).album, + playlists.at(client_screen::playlists_list->getSelectedIndex()).tracks.at(track_num).artist); + client_screen::tracks_list->addItem(string(itm)); + return; +} + +void client_screen::playlist_show (PBWidget *) +{ + client_screen::tracks_list->clear(); + for (unsigned int i=0; igetSelectedIndex()).tracks.size(); i++) + track_form_info (i); + if (playlists.at(client_screen::playlists_list->getSelectedIndex()).tracks.size()!=0) + client_screen::mark_active_track(active_track); + client_screen::tracks_list->update(); +} + +void client_screen::load_all_playlists (void) +{ + xmmsv_t *_playlist_t; + xmmsv_list_iter_t *iter; + const char *n; + playlists_list->clear(); + tracks_list->clear(); + playlists.clear(); + if (playlists_result) xmmsc_result_unref(playlists_result); + playlists_result = xmmsc_playlist_list(pbconnection); + _playlist_t = result_is_error(playlists_result, false); + if (!_playlist_t) return; + xmmsv_get_list_iter(_playlist_t, &iter); + xmmsv_list_iter_first(iter); + while (xmmsv_list_iter_valid(iter)) { + xmmsv_list_iter_entry(iter, &_playlist_t); + xmmsv_get_string(_playlist_t, &n); + if (n[0]!='_') { + playlists.push_back(playlist_item()); + playlists.back().name = n; + playlists.back().playlist_result = NULL; + playlists_list->addItem(string(playlists.back().name)); + client_screen::playlist_load(playlists.size()-1); + }; + xmmsv_list_iter_next(iter); + }; + playlists_list->update(); + write_to_log(LOG_INFO,"All playlists loaded."); + return; +} + +void client_screen::track_clicked (PBListBox *, int) +{ + if (active_playlist==this->playlists_list->getSelectedIndex() && + active_track==this->tracks_list->getSelectedIndex()) { + if (playback_status==XMMS_PLAYBACK_STATUS_PLAY) + set_playback(XMMS_PLAYBACK_STATUS_PAUSE, false); + else set_playback(XMMS_PLAYBACK_STATUS_PLAY, false); + return; + }; + if (active_playlist!=this->playlists_list->getSelectedIndex()) { + result_is_error(xmmsc_playlist_load + (pbconnection, playlists.at(this->playlists_list->getSelectedIndex()).name), true); + result_is_error(xmmsc_playlist_set_next(pbconnection, this->tracks_list->getSelectedIndex()), true); + set_playback(XMMS_PLAYBACK_STATUS_PLAY, true); + active_track=-1; + return; + }; + if (active_playlist==this->playlists_list->getSelectedIndex() && + active_track!=this->tracks_list->getSelectedIndex()) + { + result_is_error(xmmsc_playlist_set_next(pbconnection, this->tracks_list->getSelectedIndex()), true); + set_playback(XMMS_PLAYBACK_STATUS_PLAY, true); + return; + }; +} + +void client_screen::mark_active_playlist (short int playlist) +{ + if (active_playlist!=-1) + client_screen::playlists_list->getItem(active_playlist)->setWidgetFont(client_screen::font); + if (playlist!=-1) + client_screen::playlists_list->getItem(playlist)->setWidgetFont(client_screen::active_font); + client_screen::playlists_list->update(); + active_playlist=playlist; + return; +} + +void client_screen::mark_active_track (short int track) +{ + if (client_screen::playlists_list->getSelectedIndex()==active_playlist) + { + if (active_track!=-1) + client_screen::tracks_list->getItem(active_track)->setWidgetFont(client_screen::font); + if (track!=-1) + client_screen::tracks_list->getItem(track)->setWidgetFont(active_font); + client_screen::tracks_list->update(); + }; + active_track=track; + return; +} diff --git a/src/client_screen.hpp b/src/client_screen.hpp new file mode 100644 index 0000000..eef641c --- /dev/null +++ b/src/client_screen.hpp @@ -0,0 +1,81 @@ +/* + * client_screen.hpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#ifndef CLIENT_SCREEN_HPP +#define CLIENT_SCREEN_HPP + +#include +#include +#include +//#include +#include + +using std::string; + +const int PBXMMS2CLIENT_num = 0; + +class client_screen: public PBWidget +{ +public: + client_screen (void); + ~client_screen (); + + PBListBox *tracks_list; + + void track_clicked (PBListBox *, int); + void key_screen_handler (int key); +private: + // FAIL: PBPagedListBox is broken in pbtk 0.2.1 + PBListBox *playlists_list; + //PBPagedListBox *tracks_list; + PBLabel *tracks_title, *playtime, *battery; + ifont *font, *active_font; + string last_dir; + + // handlers + static void track_add_handler (int isok, PBFileChooser *pbfc); + static void track_delete_handler (int isok); + static void screen_menus_handler (int index); + static void playlist_create_handler (char *name); + static void playlist_clear_handler (int ok); + static void playlist_delete_handler (int ok); + static void pos_selector_handler (int page); + static void dir_add_handler (char *path); + // screen functions + void set_widgets (void); + void show_batt_power (PBWidget *); + void show_playtime (int ptime); + void mark_active_track (short int track); + void mark_active_playlist (short int playlist); + void playlist_show (PBWidget *); + void track_information (void); + // other functions + void load_all_playlists (void); + void playlist_load (int pnum); + void tracks_clear (int pnum); + void track_form_info (unsigned int track_num); + short int track_get_info (int track_id, int playlist_num); + static void *broadcast_handler (void *); +}; + +extern client_screen *clsc; + +#endif diff --git a/src/fork.cpp b/src/fork.cpp new file mode 100644 index 0000000..4565964 --- /dev/null +++ b/src/fork.cpp @@ -0,0 +1,185 @@ +/* + * fork.cpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#include +#include +#include "fork.hpp" + +GMainLoop *mloop; +pthread_mutex_t pipe_mutex; +xmmsc_result_t *playlist_loaded_res, *current_pos_res, *playback_status_res, *playlist_changed_res, + *reader_status_res, *playtime_res; +xmmsc_connection_t *fork_connection; +void *gmain; +extern sem_t *reader_sem; +extern int bcast_pipe[2]; + +void msg_to_pipe (const pipe_msg *msg) +{ + pthread_mutex_lock(&pipe_mutex); + write(bcast_pipe[1], msg, sizeof(pipe_msg)); + pthread_mutex_unlock(&pipe_mutex); + return; +} + +int playlist_loaded_handler (xmmsv_t *value, void *) +{ + pipe_msg msg; + const char *s; + msg.type=PLAYLIST_LOADED; + xmmsv_get_string(value, &s); + strcpy(msg.name, s); + msg_to_pipe(&msg); + return 1; +} + +int current_pos_handler (xmmsv_t *value, void *) +{ + pipe_msg msg; + xmmsv_t *dict_entry=NULL; + const char *s; + msg.type=CURRENT_POS; + xmmsv_dict_get (value, "position", &dict_entry); + xmmsv_get_int (dict_entry, &msg.current_position); + xmmsv_dict_get (value, "name", &dict_entry); + xmmsv_get_string(dict_entry, &s); + strcpy(msg.name, s); + msg_to_pipe(&msg); + return 1; +} + +int playback_status_handler (xmmsv_t *value, void *) +{ + pipe_msg msg; + msg.type=PLAYBACK_STATUS; + xmmsv_get_int (value, &msg.playback_status); + msg_to_pipe(&msg); + return 1; +} + +int playlist_changed_handler (xmmsv_t *value, void *) +{ + pipe_msg msg; + xmmsv_t *dict_entry=NULL; + const char *s; + msg.type=PLAYLIST_CHANGED; + xmmsv_dict_get (value, "type", &dict_entry); + xmmsv_get_int (dict_entry, &msg.pc.type); + xmmsv_dict_get (value, "name", &dict_entry); + xmmsv_get_string(dict_entry, &s); + strcpy(msg.name, s); + switch (msg.pc.type) { + case XMMS_PLAYLIST_CHANGED_ADD: + xmmsv_dict_get (value, "id", &dict_entry); + xmmsv_get_int (dict_entry, &msg.pc.id); + xmmsv_dict_get (value, "position", &dict_entry); + xmmsv_get_int (dict_entry, &msg.pc.position); + break; + case XMMS_PLAYLIST_CHANGED_CLEAR: + break; + case XMMS_PLAYLIST_CHANGED_REMOVE: + xmmsv_dict_get (value, "position", &dict_entry); + xmmsv_get_int (dict_entry, &msg.pc.position); + break; + default: + break; + }; + //xmmsv_dict_foreach(value, dict_foreach, NULL); + msg_to_pipe(&msg); + return 1; +} + +int reader_status_handler (xmmsv_t *value, void *) +{ + int reader_status; + xmmsv_get_int(value, &reader_status); + if (reader_status==XMMS_MEDIAINFO_READER_STATUS_RUNNING) + { + pipe_msg msg; + msg.type=READER_STATUS; + msg_to_pipe(&msg); + } + else sem_post(reader_sem); + return 1; +} + +int playtime_handler (xmmsv_t *value, void *) +{ + static unsigned int count; + pipe_msg msg; + msg.type=PLAYTIME; + if (!xmmsv_get_int(value, &msg.playtime)) + return 1; + count++; + if (count >10) + { + count=0; + msg_to_pipe(&msg); + }; + return 1; +} + +void fork_sigterm_handler (int) +{ + g_main_loop_quit (mloop); + xmmsc_result_disconnect (playlist_loaded_res); + xmmsc_result_unref (playlist_loaded_res); + xmmsc_result_disconnect (current_pos_res); + xmmsc_result_unref (current_pos_res); + xmmsc_result_disconnect (playback_status_res); + xmmsc_result_unref (playback_status_res); + xmmsc_result_disconnect (reader_status_res); + xmmsc_result_unref (reader_status_res); + xmmsc_result_disconnect (playtime_res); + xmmsc_result_unref (playtime_res); + xmmsc_mainloop_gmain_shutdown(fork_connection, gmain); + pthread_mutex_destroy(&pipe_mutex); + xmmsc_unref(fork_connection); + g_main_loop_unref (mloop); + return; +} + +void fork_main (void) +{ + signal(SIGTERM, fork_sigterm_handler); + pthread_mutex_init(&pipe_mutex, NULL); + fork_connection = xmmsc_init("PBXMMS2client_fork"); + if (fork_connection==NULL) exit(EXIT_FAILURE); + char *xmms_env = getenv("XMMS_PATH"); + if (!xmmsc_connect(fork_connection, xmms_env)) exit(EXIT_FAILURE); + free(xmms_env); + gmain = xmmsc_mainloop_gmain_init(fork_connection); + mloop = g_main_loop_new(NULL, false); + playlist_loaded_res = xmmsc_broadcast_playlist_loaded(fork_connection); + xmmsc_result_notifier_set(playlist_loaded_res, playlist_loaded_handler, NULL); + current_pos_res = xmmsc_broadcast_playlist_current_pos(fork_connection); + xmmsc_result_notifier_set(current_pos_res, current_pos_handler, NULL); + playback_status_res = xmmsc_broadcast_playback_status(fork_connection); + xmmsc_result_notifier_set(playback_status_res, playback_status_handler, NULL); + playlist_changed_res = xmmsc_broadcast_playlist_changed(fork_connection); + xmmsc_result_notifier_set(playlist_changed_res, playlist_changed_handler, NULL); + reader_status_res = xmmsc_broadcast_mediainfo_reader_status(fork_connection); + xmmsc_result_notifier_set(reader_status_res, reader_status_handler, NULL); + playtime_res = xmmsc_signal_playback_playtime(fork_connection); + xmmsc_result_notifier_set(playtime_res, playtime_handler, NULL); + g_main_loop_run (mloop); + return; +} diff --git a/src/fork.hpp b/src/fork.hpp new file mode 100644 index 0000000..48277b1 --- /dev/null +++ b/src/fork.hpp @@ -0,0 +1,48 @@ +/* + * fork.hpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#ifndef FORK_HPP +#define FORK_HPP + +enum MSG_TYPE {PLAYLIST_LOADED, CURRENT_POS, PLAYBACK_STATUS, PLAYLIST_CHANGED, + READER_STATUS, PLAYTIME}; + +const int MAX_PLAYLIST_NAME = 512; + +struct _playlist_changed { + int id, position, type; +}; + +struct pipe_msg +{ + MSG_TYPE type; + char name[MAX_PLAYLIST_NAME]; + union { + int current_position; + int playback_status; + int playtime; + struct _playlist_changed pc; + }; +}; + +void fork_main (void); + +#endif // FORK_HPP diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 0000000..7957a12 --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,142 @@ +/* + * log.cpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#include +#include +#include +#include +#include +#include "log.hpp" + +#define LOG_DIR USERDATA "/log" +FILE *log_file=NULL; +int log_pipe[2]; +bool log_worked; +pthread_t log_thread; +pthread_mutex_t log_mutex; + +using namespace std; + +short int log_init (void) +{ + if (log_file && log_worked) return 0; // log already started + const char log_file_path [] = LOG_DIR "/pbxmms2client.log"; + if (access(LOG_DIR, W_OK | R_OK)) // NOTE: log dir may absent in some causes + { + if (errno==ENOENT) + { + if (mkdir(LOG_DIR, S_IRWXU | S_IRWXG | S_IRWXO)) + { + cerr<log_enabled || level>pbxmms_settings->log_level) return -1; + char buffer[256]; + int len; + va_list args; + va_start(args,msg); + len=1+vsprintf(buffer,msg,args); + va_end(args); + if (len<0) return -1; + pthread_mutex_lock(&log_mutex); + write(log_pipe[1],&level,sizeof(level)); + write(log_pipe[1],&len,sizeof(len)); + write(log_pipe[1],buffer,len); + pthread_mutex_unlock(&log_mutex); + return 0; +} diff --git a/src/log.hpp b/src/log.hpp new file mode 100644 index 0000000..3e8f8d7 --- /dev/null +++ b/src/log.hpp @@ -0,0 +1,32 @@ +/* + * log.hpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#ifndef LOG_HPP +#define LOG_HPP + +#include "settings.hpp" + +short int log_init (void); +short int log_end (void); +void *log_handler (void*); +short int write_to_log (LOG_LEVEL level, const char *msg, ...); + +#endif // LOG_HPP diff --git a/src/main.cpp b/src/main.cpp new file mode 100755 index 0000000..94aca76 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,254 @@ +/* + * main.cpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#include +#include +#include +#include "client_screen.hpp" +#include "fork.hpp" +#include "log.hpp" +#include "settings.hpp" +#include "xmms.hpp" + +xmmsc_connection_t *pbconnection; +settings *pbxmms_settings; +client_screen *clsc; +sem_t *fork_sem, *reader_sem; +int bcast_pipe[2], playback_status; + +pid_t fork_pid = 0; + +using namespace std; + +const int TRACK_PLAY_PAUSE_num=1; +const int TRACK_STOP_num= 2; +const int SETTINGS_num = 3; +const int SERVER_num = 4; +const int STATS_num = 41; +const int PLUGINS_num = 42; +const int REHASH_num = 43; +const int HELP_num = 5; +const int ABOUT_num = 51; +const int EXIT_AND_HALT_num = 6; +const int EXIT_num = 7; + +// WARNING: last element must be an empty element! +static imenu server_menu [] = { + {ITEM_HEADER, PBXMMS2CLIENT_num, PBXMMS2CLIENT, NULL}, + {ITEM_ACTIVE, STATS_num, "Stats", NULL}, + {ITEM_ACTIVE, PLUGINS_num, "List loaded plugins", NULL}, + {ITEM_ACTIVE, REHASH_num, "Rehash the medialib", NULL}, + {0, 0, NULL, NULL} +}; +static imenu help_menu [] = +{ + {ITEM_HEADER, PBXMMS2CLIENT_num, PBXMMS2CLIENT, NULL}, + {ITEM_ACTIVE, ABOUT_num, "About", NULL}, + {0, 0, NULL, NULL} +}; +static imenu main_menu [] = { + {ITEM_HEADER, PBXMMS2CLIENT_num, PBXMMS2CLIENT, NULL}, + {ITEM_ACTIVE, TRACK_PLAY_PAUSE_num, "Start/pause playback", NULL}, + {ITEM_ACTIVE, TRACK_STOP_num, "Stop playback", NULL}, + {ITEM_ACTIVE, SETTINGS_num, "Settings", NULL}, + {ITEM_SUBMENU, SERVER_num, "Server", server_menu}, + {ITEM_SUBMENU, HELP_num, "Help", help_menu}, + {ITEM_ACTIVE, EXIT_AND_HALT_num, "Exit and shutdown server", NULL}, + {ITEM_ACTIVE, EXIT_num, "Exit", NULL}, + {0, 0, NULL, NULL} +}; + +static void rehash_handler (int b) +{ + if (b==1) result_is_error(xmmsc_medialib_rehash(pbconnection, 0), true); + return; +} + +static void main_menu_handler (int index) +{ + switch (index) { + case ABOUT_num: Message(ICON_INFORMATION, PBXMMS2CLIENT, + "PBXMMS2client v0.006 - graphical client for XMMS2 player on PocketBook E-Ink readers\n" + "Copyright © 2014-2020 Programmist11180\n" + "Website https://github.com/Programmist11180/pbxmms2client\n" + "The PBXMMS2client is licensed under the GPL 3\n", long_timeout); + break; + case SETTINGS_num: pbxmms_settings->change_settings(); + break; + case EXIT_AND_HALT_num: server_halt(); + CloseApp(); + break; + case EXIT_num: CloseApp(); + break; + case STATS_num: server_stats(); + break; + case PLUGINS_num: server_list_plugins(); + break; + case REHASH_num: + Dialog(ICON_QUESTION, PBXMMS2CLIENT, "Rehash the medialib?", "Yes", "No", rehash_handler); + break; + case TRACK_PLAY_PAUSE_num: if (playback_status==XMMS_PLAYBACK_STATUS_PLAY) + set_playback(XMMS_PLAYBACK_STATUS_PAUSE); + else set_playback(XMMS_PLAYBACK_STATUS_PLAY); + break; + case TRACK_STOP_num: set_playback(XMMS_PLAYBACK_STATUS_STOP); + break; + case -1: + break; + default: write_to_log(LOG_WARN, "main_menu_handler(): unknown menu namber: %d", index); + break; + }; + return; +} + +int inkview_handler (int event_type, int param_one, int param_two) +{ + write_to_log(LOG_DBG, "iv_handler() - type: %d (%s), par1: 0x%x, par2: 0x%x", event_type, iv_evttype(event_type), param_one, param_two); + switch (event_type) { + case EVT_INIT: + clsc = new client_screen; + write_to_log(LOG_DBG, "Object 'client_screen' created."); + sem_post(fork_sem); + clsc->tracks_list->onFocusedWidgetChanged.emit(NULL); + write_to_log(LOG_INFO,"%s started successfully.",PBXMMS2CLIENT); + break; + case EVT_ORIENTATION: write_to_log(LOG_WARN,"Change orientation not implemented."); + break; + case EVT_POINTERLONG: if (clsc->tracks_list->isFocused() && clsc->tracks_list->itemsCount()) + clsc->track_clicked(NULL, 0); + break; + case EVT_KEYPRESS: + switch (param_one) { + // BUG: Don't call OpenMenu at position x=0 y=0 ! It crashes the application + case KEY_BACK: OpenMenu(main_menu, 1, 10, 10, main_menu_handler); + break; + case KEY_OK: + case KEY_MENU: + clsc->key_screen_handler (param_one); + break; + case KEY_NEXT: pbxmms_settings->vol_level += pbxmms_settings->vol_inc; + set_volume(); + break; + case KEY_PREV: pbxmms_settings->vol_level -= pbxmms_settings->vol_inc; + set_volume(); + break; + default: + break; + }; + break; + case EVT_EXIT: + kill(fork_pid, SIGTERM); + sem_close(fork_sem); + xmmsc_unref(pbconnection); + if (clsc) delete clsc; + write_to_log(LOG_INFO,"%s shutdown.",PBXMMS2CLIENT); + log_end(); + if (pbxmms_settings) delete pbxmms_settings; + set_wake_lock(false); + break; + case EVT_CONFIGCHANGED: clsc->update(); + break; + default: clsc->handle (event_type, param_one, param_two); + break; + }; + return 0; +} + +void main_segfault_handler (int) +{ + kill(fork_pid, SIGKILL); + signal(SIGSEGV, SIG_DFL); + if (pbxmms_settings) + if (pbxmms_settings->log_enabled) + { + write_to_log(LOG_ERR, "Segmentation fault in main process!"); + log_end(); + }; +} + +void main_sigabort_handler (int) +{ + kill(fork_pid, SIGTERM); + signal(SIGABRT, SIG_DFL); + if (pbxmms_settings) + if (pbxmms_settings->log_enabled) + { + write_to_log(LOG_ERR, "Main process aborted!"); + log_end(); + }; +} + +void main_main (void) +{ + if (signal(SIGSEGV,main_segfault_handler)==SIG_ERR) + cerr<<"Failed to set SIGSEGV handler!"<log_enabled) + if (log_init()==-1) + { + pbxmms_settings->log_enabled = 0; + Message(ICON_ERROR,PBXMMS2CLIENT,"Log thread initialization failed!",200); + }; + write_to_log(LOG_INFO,"%s startup.",PBXMMS2CLIENT); + write_to_log(LOG_INFO,"Model: %s; Hardware: %s; Software: %s",GetDeviceModel(),GetHardwareType(),GetSoftwareVersion()); + write_to_log(LOG_DBG,"Screen: width: %d, heigth: %d",ScreenWidth(),ScreenHeight()); + if ( (pbconnection = xmmsc_init("PBXMMS2client_main"))==NULL) + { + write_to_log(LOG_ERR, "Connection initialization failed: not enough memory."); + exit(EXIT_FAILURE); + }; + server_run(); + change_output(); + InkViewMain(inkview_handler); +} + +int main (int , char **) +{ + if (pipe(bcast_pipe)==-1) { + cerr<<"Failed to create a pipe: "< + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#include +#include +#include "log.hpp" +#include "settings.hpp" +#include "xmms.hpp" + +using namespace std; + +// Settings names +char output_name[]="output", volume_level_name[]="volume_level", volume_inc_name[]="volume_inc", + font_name[]="font", log_name[]="log_file", log_level_name[]="log_level"; + +// Settings values +char *output_type[]={"Internal", "Bluetooth", NULL}, *log_var[]={"Enabled","Disabled",NULL}, + *log_level_type[]={"ERROR","WARNING","INFO","DEBUG",NULL}, + *vol_incs [] = {"1", "2", "3", "4", "5", NULL}; // WARNING: last element must be an empty element! + +iconfig *settings_icfg; +iconfigedit settings_icfgedit [] = +{ + {CFG_CHOICE, NULL, "Playback output", "Internal - built-in speakers and headphones, " + "Bluetooth - A2DP profile for audio devices.", output_name, output_type[0], output_type, NULL}, + {CFG_NUMBER, NULL, "Volume level", "Volume level must be between 0% and 100%", + volume_level_name, "50", NULL, NULL}, + {CFG_INDEX, NULL, "Volume increment", "The value that is increased or decreased volume", + volume_inc_name, vol_incs[0], vol_incs, NULL}, + {CFG_FONT, NULL, "Font", NULL, font_name, DEFAULTFONT, NULL, NULL}, + {CFG_CHOICE, NULL, "Log file", NULL, log_name, log_var[0], log_var, NULL}, + {CFG_CHOICE, NULL, "Log verbosity", NULL, log_level_name, log_level_type[2], log_level_type, NULL}, + {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL} +}; + + +settings::settings () +{ + settings_icfg = OpenConfig(CONFIGPATH "/xmms2/clients/pbxmms2client.cfg", settings_icfgedit); + output = get_output(ReadString(settings_icfg, output_name, output_type[0])); + vol_level = ReadInt(settings_icfg, volume_level_name, 50); + vol_inc = 1+ReadInt(settings_icfg, volume_inc_name, 2); + orientation = ReadInt(settings_icfg, "orientation", 0); + //font = ReadString(settings_icfg, font_name, DEFAULTFONT); + font_size = ReadInt(settings_icfg, "font_size", 20); + //low_power = ReadInt(settings_icfg, "low_power", 25); + log_enabled = get_log_enabled(ReadString(settings_icfg, log_name, log_var[0])); + log_level = get_log_level(ReadString(settings_icfg,log_level_name,log_level_type[1])); +} + +void settings::save () +{ + //WriteString(settings_icfg, output_name, output); + WriteInt(settings_icfg, volume_level_name, vol_level); + //WriteInt(settings_icfg, volume_inc_name, vol_inc); + //WriteString(settings_icfg, font_name, font); + SaveConfig(settings_icfg); +} + +settings::~settings () +{ + pbxmms_settings->save(); + CloseConfig(settings_icfg); +} + +bool settings::get_log_enabled (const char *str) +{ + if (!strcmp(str,log_var[0])) return true; + else if (!strcmp(str,log_var[1])) return false; + else + { + cerr<save(); + NotifyConfigChanged(); + write_to_log(LOG_INFO,"Configuration saved."); + return; +} + +void settings::item_handler (char *item) +{ + if (!strcmp(item, output_name)) + { + pbxmms_settings->output = pbxmms_settings->get_output(ReadString(settings_icfg, output_name, output_type[0])); + change_output(); + return; + }; + if (!strcmp(item, volume_level_name)) + { + pbxmms_settings->vol_level = ReadInt(settings_icfg, volume_level_name, 50); + set_volume(); + return; + }; + if (!strcmp(item, volume_inc_name)) + { + pbxmms_settings->vol_inc = 1+ReadInt(settings_icfg, volume_inc_name, 3); + return; + }; + if (!strcmp(item, log_name)) + { + pbxmms_settings->log_enabled = pbxmms_settings->get_log_enabled(ReadString(settings_icfg, log_name, log_var[0])); + if (pbxmms_settings->log_enabled) log_init(); + else log_end(); + return; + }; +} + +void settings::change_settings (void) +{ + OpenConfigEditor(PBXMMS2CLIENT, settings_icfg, settings_icfgedit, settings::config_handler, + settings::item_handler); +} + +short int set_volume () +{ + static char vol [60]; + //xmmsc_result_t *res; + if (pbxmms_settings->vol_level < 0) pbxmms_settings->vol_level = 0; + if (pbxmms_settings->vol_level > 100) pbxmms_settings->vol_level = 100; + WriteInt(settings_icfg, volume_level_name, pbxmms_settings->vol_level); + // BUG: setting volume via xmms currently not working on reader + /*res = xmmsc_playback_volume_set(pbconnection, "left", pbxmms_settings->vol_level); + result_is_error(res, true); + res = xmmsc_playback_volume_set(pbconnection, "right", pbxmms_settings->vol_level); + result_is_error(res, true);*/ + + sprintf(vol, "/mnt/ext1/system/bin/amixer --quiet sset Softmaster %d%%", pbxmms_settings->vol_level); +#ifndef __EMU__ + system(vol); +#endif + return 0; +} diff --git a/src/settings.hpp b/src/settings.hpp new file mode 100644 index 0000000..27dde24 --- /dev/null +++ b/src/settings.hpp @@ -0,0 +1,60 @@ +/* + * settings.hpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#ifndef SETTINGS_HPP +#define SETTINGS_HPP + +static char PBXMMS2CLIENT [] = "PBXMMS2client"; + +enum OUTPUT {INTERNAL, BLUETOOTH}; +enum LOG_LEVEL {LOG_ERR, LOG_WARN, LOG_INFO, LOG_DBG}; + +class settings +{ +public: + settings (); + ~settings (); + void save (); + void change_settings (void); + + OUTPUT output; // NOTE: Internal - speakers and headphones connector, Bluetooth - a2dpd + int vol_level; + int vol_inc; + int orientation; + char *font; + int font_size; + //int low_power; + bool log_enabled; + LOG_LEVEL log_level; + +private: + static void config_handler (void); + static void item_handler (char *item); + bool get_log_enabled (const char *str); + LOG_LEVEL get_log_level (const char *str); + OUTPUT get_output (const char *str); +}; + +extern settings *pbxmms_settings; + +short int set_volume (); + +#endif // SETTINGS_HPP diff --git a/src/xmms.cpp b/src/xmms.cpp new file mode 100644 index 0000000..e0b5dfd --- /dev/null +++ b/src/xmms.cpp @@ -0,0 +1,246 @@ +/* + * xmms.cpp + * + * Copyright (C) 2014-2020 Programmist11180 + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#include +#include +#include +#include +#include "log.hpp" +#include "xmms.hpp" + +using namespace std; + +const char wake_lock_file [] = "/sys/power/wake_lock"; +const char wake_unlock_file [] = "/sys/power/wake_unlock"; + +void print_error (const char *err_text) +{ + cout<output) + { + case INTERNAL: + result_is_error(xmmsc_config_set_value(pbconnection, "alsa.device", "default_sm"), true); + //result_is_error(xmmsc_config_set_value(pbconnection, "alsa.mixer", "PCM"), true); + result_is_error(xmmsc_config_set_value(pbconnection, "alsa.mixer_dev", "default"), true); + //result_is_error(xmmsc_config_set_value(pbconnection, "alsa.mixer_index", "0"), true); + break; + case BLUETOOTH: + if (result_is_error(xmmsc_config_set_value(pbconnection, "alsa.device", "a2dpd_sm"), true)) + write_to_log(LOG_INFO,"Output set to Bluetooth."); + else write_to_log(LOG_ERR, "Unable to set output to Bluetooth"); + result_is_error(xmmsc_config_set_value(pbconnection, "alsa.mixer_dev", "a2dpd"), true); + break; + }; +#endif +} + +short int set_wake_lock (bool on) +{ +#ifndef __EMU__ + FILE *lk; + const char chars [] = "test"; + if (on) + { + lk = fopen("/sys/power/wake_lock", "w"); + if (lk==NULL) + { + write_to_log(LOG_ERR,"Failed to open wake_lock file: %s",strerror(errno)); + return -1; + }; + if (fwrite(chars, sizeof(char), 5, lk)==0) print_error("fwrite() wake_lock failed"); + } + else + { + lk = fopen("/sys/power/wake_unlock", "w"); + if (lk==NULL) + { + print_error("fopen() wake_unlock fail"); + return -1; + }; + if (fwrite(chars, sizeof(char), 5, lk)==0) print_error("fwrite() wake_unlock failed"); + }; + if (fclose(lk)==EOF) print_error("fclose() failed"); +#endif + return 0; +} + +int server_playback_status (void) +{ + int status; + xmmsc_result_t *status_res = xmmsc_playback_status(pbconnection); + xmmsv_t *status_val = result_is_error(status_res, false); + xmmsv_get_int(status_val, &status); + write_to_log(LOG_DBG,"Obtained current playback status: %d",status); + xmmsc_result_unref(status_res); + return status; +} + +void set_playback (int status, bool tickle) +{ + switch (status) { + case XMMS_PLAYBACK_STATUS_STOP: result_is_error(xmmsc_playback_stop(pbconnection), true); + break; + case XMMS_PLAYBACK_STATUS_PAUSE: result_is_error(xmmsc_playback_pause(pbconnection), true); + break; + case XMMS_PLAYBACK_STATUS_PLAY: result_is_error(xmmsc_playback_start(pbconnection), true); + break; + default: + write_to_log(LOG_WARN,"Trying to set invalid playback type %d",status); + break; + }; + set_volume(); + if (tickle && status) result_is_error(xmmsc_playback_tickle(pbconnection), true); +} + +#ifdef __EMU__ +// NOTE: This function is used only for debug +void dict_foreach (const char *name, xmmsv_t *value, void *) +{ + int t; + const char *s; + cout<<"dict_foreach: '"< + * + * This file is part of PBXMMS2client. + * + * PBXMMS2client 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. + * + * PBXMMS2client 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 PBXMMS2client. If not, see . + */ + +#ifndef COMMON_HPP +#define COMMON_HPP + +#include + +const int long_timeout = 60000; + +extern xmmsc_connection_t *pbconnection; + +// result +//void print_error (const char *err_text); +xmmsv_t *result_is_error (xmmsc_result_t *res, bool unref = false); +bool value_is_error (xmmsv_t *value); +// server +void server_run (void); +void server_halt (void); +void server_stats (void); +void server_list_plugins (void); +int server_playback_status (void); +void set_playback (int status, bool tickle = false); +void change_output (void); +// reader +short set_wake_lock (bool on); + +// debug +#ifdef __EMU__ +void dict_foreach (const char *name, xmmsv_t *value, void *); +#endif + +#endif // COMMON_HPP