/** * GraphTFTng plugin for the Video Disk Recorder * * display.c * * (c) 2006-2013 Jörg Wendel * * This code is distributed under the terms and conditions of the * GNU GENERAL PUBLIC LICENSE. See the file COPYING for details. * **/ //*************************************************************************** // Includes //*************************************************************************** #include #include #include #include #include #include #include #include // the renderdevices #include "fbrenderer.h" #include "dmyrenderer.h" //*************************************************************************** // Object //*************************************************************************** cGraphTFTDisplay::cGraphTFTDisplay(const char* aSyntaxVersion) { syntaxVersion = aSyntaxVersion; versionReported = no; startedAt = time(0); // init renderer renderer = 0; isDummyRender = no; // init thread stuff _active = no; comThread = 0; touchThread = 0; // init contents currentSection = 0; lastSection = 0; _eventsReady = no; _menu.charInTabs[0] = _menu.charInTabs[1] = _menu.charInTabs[2] = _menu.charInTabs[3] = _menu.charInTabs[4] = _menu.charInTabs[5] = _menu.charInTabs[6] = 0; _mode = NormalView; _sectionName = ""; channelType = ctTv; _channel = 0; _presentChannel = 0; _presentEvent = 0; _followingEvent = 0; _volume = cDevice::CurrentVolume(); _mute = cDevice::PrimaryDevice()->IsMute(); _showRecording = -1; _replay.control = 0; _replay.name = ""; _replay.fileName = ""; _replay.lastMode = ModeUnknown; _menu.currentRowLast = na; _menu.currentRow = na; _menu.current = ""; _menu.visibleRows = 0; _menu.topRow = na; _menu.lineHeight = na; _menu.drawingRow = na; displayActive = yes; forceNextDraw = yes; nextCyclicForce = 0; forceWriteThrough = no; wakeup = no; userDumpFile = 0; userDumpWidth = 0; userDumpHeight = 0; _event = 0; _recording = ""; _coverPath = ""; _music.filename = ""; _music.artist = ""; _music.album = ""; _music.genre = ""; _music.comment = ""; _music.year = na; _music.frequence = 0; _music.bitrate = 0; _music.smode = ""; _music.index = 0; _music.count = 0; _music.status = ""; _music.currentTrack = ""; _music.loop = no; _music.shuffle = no; _music.shutdown = no; _music.recording = no; _music.rating = 0; _music.lyrics = no; _music.copy = no; _music.timer = no; // snapshot snapshotPending = no; // calibration stuff calibration.cursorX = 0; calibration.cursorY = 0; calibration.instruction = ""; calibration.info = ""; calibration.state = csUnknown; mouseX = 0; mouseY = 0; mouseKey = 0; touchMenu = 0; touchMenuHideTime = 0; touchMenuHideAt = 0; } cGraphTFTDisplay::~cGraphTFTDisplay() { Stop(); if (touchThread) delete touchThread; if (comThread) delete comThread; if (renderer) delete renderer; } //*************************************************************************** // Init //*************************************************************************** int cGraphTFTDisplay::Init(const char* dev, const char* cfgDir, int port) { const char* pos = 0; int devnum = na; if (!renderer) { if ((pos = strstr(dev, "/dev/fb"))) { devnum = atoi(pos+7); renderer = new FbRenderer(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset, GraphTFTSetup.width, GraphTFTSetup.height, GraphTFTSetup.PluginConfPath, GraphTFTSetup.Iso2Utf, Thms::theTheme->getDir()); } else if (strcmp(dev, "none") == 0) { isDummyRender = yes; renderer = new DummyRenderer(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset, GraphTFTSetup.width, GraphTFTSetup.height, GraphTFTSetup.PluginConfPath, GraphTFTSetup.Iso2Utf, Thms::theTheme->getDir()); } else { tell(0, "Ignoring unexpected device '%s'", dev); } // comthread uses theme width/height instead of display width/height comThread = new ComThread(this, Thms::theTheme->getWidth(), Thms::theTheme->getHeight()); if (comThread->init(renderer, port) != success) { tell(0, "Can't establish listener, tcp communication not available!"); delete comThread; comThread = 0; } else { comThread->Start(); } } #ifdef WITH_TOUCH // touch thread .. touchThread = new cTouchThread(this); touchThread->setDevice(GraphTFTSetup.touchDevice); if (touchThread->open() != success) { tell(0, "Can't establish touch thread, touch panel not available!"); delete touchThread; touchThread = 0; } else { touchThread->setSetting(&GraphTFTSetup.touchSettings); touchThread->Start(); } #endif // either the com thread or a device is needed if (!comThread && devnum == na) return fail; // apply settings renderer->setBorder(GraphTFTSetup.xBorder, GraphTFTSetup.yBorder); renderer->setProperties(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset, Thms::theTheme->getWidth(), Thms::theTheme->getHeight(), GraphTFTSetup.Iso2Utf, Thms::theTheme->getDir()); // initialize the renderer device if (renderer->init(devnum) != 0) return fail; // Show the start image cDisplayItem::setRenderer(renderer); cDisplayItem::setVdrStatus(this); setupChanged(); Start(); return success; } //*************************************************************************** // Broadcast //*************************************************************************** void cGraphTFTDisplay::broadcast(int force) { forceNextDraw = force; wakeup = yes; _doUpdate.Broadcast(); } //*************************************************************************** // Set Mode //*************************************************************************** int cGraphTFTDisplay::setMode(DisplayMode mode, const char* menuName, int force) { if (_mode == ModeCalibration && !force) return done; if (_mode != mode || (menuName && _sectionName != menuName)) { _mode = mode; _sectionName = menuName ? menuName : "Menu"; tell(0, "Mode is set to (0x%X), menu section to '%s'", _mode, _sectionName.c_str()); broadcast(); } return done; } //*************************************************************************** // Setup Changed //*************************************************************************** void cGraphTFTDisplay::setupChanged() { versionReported = no; logDevice = GraphTFTSetup.LogDevice; logLevel = GraphTFTSetup.Level; if (comThread) comThread->setJpegQuality(GraphTFTSetup.JpegQuality); if (touchThread) touchThread->setDevice(GraphTFTSetup.touchDevice, yes); if (renderer) { renderer->flushCache(); renderer->setBorder(GraphTFTSetup.xBorder, GraphTFTSetup.yBorder); renderer->setProperties(GraphTFTSetup.xOffset, GraphTFTSetup.yOffset, Thms::theTheme->getWidth(), Thms::theTheme->getHeight(), GraphTFTSetup.Iso2Utf, Thms::theTheme->getDir()); renderer->setFontPath(Thms::theTheme->getFontPath()); } } //*************************************************************************** // Switch/Set Calibrate //*************************************************************************** void cGraphTFTDisplay::switchCalibrate(int state) { if (isMode(ModeCalibration)) setCalibrate(off); else setCalibrate(on, state); } void cGraphTFTDisplay::setCalibrate(int active, int state) { static int lastActive = no; static cGraphTFTService::DisplayMode lastMode = cGraphTFTDisplay::NormalView; static string lastSection = ""; if (active != lastActive) { lastActive = active; tell(0, "Info: %s calibration mode", active ? "starting" : "stopping"); } calibration.settings = GraphTFTSetup.touchSettings; if (state == csUnknown && touchThread) touchThread->setCalibrate(active); if (active) { // store actual mode lastMode = _mode; lastSection = _sectionName; setMode(ModeCalibration, 0, /*force*/ true); calibration.state = state; if (state == csUnknown) { calibration.settings.swapXY = no; calibration.info = "calibration started"; if (touchThread) touchThread->resetSetting(); } else { calibration.instruction = "verify by touching ..."; calibration.info = "testing calibration"; } calibrateTouch(0, 0); } else { setMode(lastMode, lastSection.c_str(), /*force*/ true); } broadcast(yes); } //*************************************************************************** // Music Plugin Interface //*************************************************************************** void cGraphTFTDisplay::musicAddPlaylistItem(const char* item, int index) { if (index == 0) _music.tracks.clear(); _music.tracks.push_back(item); } void cGraphTFTDisplay::setMusicPlayerState(cTftCS::MusicServicePlayerInfo* p) { if (!p) return; _music.filename = p->filename; _music.artist = p->artist; _music.album = p->album; _music.genre = p->genre; _music.comment = p->comment; _music.year = p->year > 0 ? Str::toStr(p->year) : "--"; _music.frequence = p->frequence; _music.bitrate = p->bitrate; _music.smode = p->smode; _music.index = p->index; _music.count = p->count; _music.status = p->status; _music.currentTrack = p->currentTrack; _music.loop = p->loop; _music.shuffle = p->shuffle; _music.shutdown = p->shutdown; _music.recording = p->recording; _music.rating = p->rating; _music.lyrics = no; // TODO _music.copy = no; // TODO _music.timer = no; // TODO } void cGraphTFTDisplay::setMusicPlayerHelpButtons(cTftCS::MusicServiceHelpButtons* p) { _music.red = p->red; _music.green = p->green; _music.yellow = p->yellow; _music.blue = p->blue; } //*************************************************************************** // Get Channel By Name //*************************************************************************** const cChannel* cGraphTFTDisplay::channelByName(const char* name) { cChannel* channel; if (Str::isEmpty(name) || !isalnum(name[0])) return 0; for (channel = Channels.First(); channel; channel = Channels.Next(channel)) { if (strcmp(channel->ShortName(), name) == 0 || strcmp(channel->Name(), name) == 0) { tell(6, "found channel '%s'", channel->ShortName()); return channel; } } return 0; } //*************************************************************************** // Stop //*************************************************************************** void cGraphTFTDisplay::Stop() { if (_active) { _active = no; broadcast(); Cancel(3); } } //*************************************************************************** // Get Tabbed Text //*************************************************************************** const char* cGraphTFTDisplay::GetTabbedText(const char* s, int Tab) { if (!s) return 0; static char buffer[1000+TB]; const char *a = s; const char *b = strchrnul(a, '\t'); while (*b && Tab-- > 0) { a = b + 1; b = strchrnul(a, '\t'); } if (!*b) return (Tab <= 0) ? a : 0; unsigned int n = b - a; if (n >= sizeof(buffer)) n = sizeof(buffer) - 1; strncpy(buffer, a, n); buffer[n] = 0; return buffer; } //*************************************************************************** // Action //*************************************************************************** void cGraphTFTDisplay::Action() { uint64_t updateIn; int n; tell(0,"GraphTFT plugin display thread started (pid=%d)", getpid()); // display the start image renderer->image(Thms::theTheme->getStartImage().c_str(), 0,0,0,0); renderer->refresh(); // and give the plugin time to collect some data AND give vdr some time to // finish initialization before acquiring the lock sleep(3); while (!Thms::theTheme->isInitialized()) usleep(100000); _mutex.Lock(); // mutex gets ONLY unlocked when sleeping _active = yes; // main loop while (_active) { tell(2, "action loop"); if (touchMenu) { if (touchMenuHideAt && msNow() > touchMenuHideAt-100) touchMenu = 0; forceNextDraw = yes; } if (GraphTFTSetup.redrawEvery && msNow() > nextCyclicForce) { nextCyclicForce = msNow() + SECONDS(GraphTFTSetup.redrawEvery); forceNextDraw = yes; forceWriteThrough = yes; } if (isModeNormal(_mode)) { // do the work ... if (GraphTFTSetup.normalMode == "Standard") n = display(channelType == ctTv ? "NormalTV" : "NormalRadio"); else n = display("Normal" + GraphTFTSetup.normalMode); } else { switch (_mode) { case ReplayNormal: n = display("ReplayNormal"); break; case ReplayMP3: n = display("ReplayMP3"); break; case ReplayDVD: n = display("ReplayDVD"); break; case ReplayImage: n = display("ReplayImage"); break; case ModeCalibration: n = display("Calibration"); break; case ModeMenu: n = display(_sectionName); break; default: n = display("NormalTV"); } } updateIn = SECONDS(60); // the default if (currentSection) { updateIn = currentSection->getNextUpdateTime() - msNow(); // auto hide of touch menu if (touchMenu && touchMenuHideAt) { if ((touchMenuHideAt - msNow()) < updateIn) updateIn = touchMenuHideAt - msNow(); tell(2, "Autohide scheduled in %ldms", updateIn); } } if (updateIn < 10) // 10ms -> the minimum updateIn = 10; // can't calc this inline, due to a format string problem ... ? int s = updateIn/1000; int us = updateIn%1000; tell(1, "Displayed %d Items, schedule next " "update in %d,%03d seconds", n, s, us); wait(updateIn); // snapshot if (snapshotPending) takeSnapshot(); } isyslog("GraphTFT plugin display thread ended (pid=%d)", getpid()); } //*************************************************************************** // Wait //*************************************************************************** int cGraphTFTDisplay::wait(uint64_t updateIn) { uint64_t waitStep = updateIn > 500 ? 500 : updateIn; uint64_t waitUntil = updateIn + msNow(); wakeup = no; while (msNow() < waitUntil && !wakeup) { _doUpdate.TimedWait(_mutex, waitStep); if (waitUntil-msNow() < waitStep) waitStep = waitUntil-msNow(); meanwhile(); } return success; } //*************************************************************************** // Meanwhile //*************************************************************************** int cGraphTFTDisplay::meanwhile() { static bool play = false, forward = false; static int speed = 0; bool aPlay, aForward; int aSpeed; // check some VDR states on changes if (_replay.control && _replay.control->GetReplayMode(aPlay, aForward, aSpeed)) { if (aPlay != play || aSpeed != speed || aForward != forward) { play = aPlay; forward = aForward; speed = aSpeed; tell(2, "Trigger update of replay group (replay mode changed)"); updateGroup(groupReplay); broadcast(); } } return done; } //*************************************************************************** // Take Snapshot //*************************************************************************** void cGraphTFTDisplay::takeSnapshot() { char* path = 0; char* file = 0; snapshotPending = no; // it's better to wait half a second, // giving the menu a chance to close ... _doUpdate.TimedWait(_mutex, 500); // tv view or replay running if (_replay.fileName.length()) { const cRecording* replay; if ((replay = Recordings.GetByName(_replay.fileName.c_str())) && replay->Info()) asprintf(&file, "%s", replay->Info()->Title()); } else { if (_presentEvent) asprintf(&file, "%s", _presentEvent->Title()); } if (!file) { Skins.Message(mtInfo, tr("Can't save snapshot, missing event information")); return ; } strreplace(file, '/', ' '); asprintf(&path, "%s/%s.jpg", GraphTFTSetup.snapshotPath, file); if (cDevice::PrimaryDevice()->GrabImageFile(path, yes, GraphTFTSetup.snapshotQuality, GraphTFTSetup.snapshotWidth, GraphTFTSetup.snapshotHeight)) Skins.Message(mtInfo, tr("Snapshot saved")); else Skins.Message(mtInfo, tr("Error saving snapshot")); renderer->flushCache(); free(path); free(file); } //*************************************************************************** // Stopped Timer //*************************************************************************** bool cGraphTFTDisplay::StoppedTimer(const char* Name) { cTimer* timer = Timers.First(); while (timer) { if (strcmp(Name, timer->File()) == 0) break; timer = Timers.Next(timer); } return timer == 0 || !timer->Recording(); } //*************************************************************************** // Update Programme //*************************************************************************** void cGraphTFTDisplay::UpdateProgramme() { const cChannel* channel = Channels.GetByNumber(_channel); if (channel) { tell(5, "UpdateProgramme for channel '%s'", channel->Name()); cMutexLock lock(&_mutex); const cEvent* present = _presentEvent; const cEvent* following = _followingEvent; _presentEvent = _followingEvent = 0; cSchedulesLock schedulesLock; const cSchedules* schedules = cSchedules::Schedules(schedulesLock); if (schedules) { const cSchedule *schedule = schedules->GetSchedule(channel->GetChannelID()); if (schedule) { _presentEvent = schedule->GetPresentEvent(); _followingEvent = schedule->GetFollowingEvent(); } } if (present != _presentEvent || following != _followingEvent) updateGroup(groupChannel); } } //*************************************************************************** // Osd Channel Switch //*************************************************************************** void cGraphTFTDisplay::ChannelSwitch(const cDevice* Device, int ChannelNumber, bool LiveView) { tell(5, "ChannelSwitch on %p: %d", Device, ChannelNumber); if (LiveView && _channel != ChannelNumber && cDevice::CurrentChannel() != _channel) { const cChannel* channel; cMutexLock lock(&_mutex); _rds.title.clear(); _rds.artist.clear(); _rds.text.clear(); _channel = ChannelNumber; _presentChannel = Channels.GetByNumber(_channel); if ((channel = Channels.GetByNumber(_channel))) { char *temp; string::size_type pos; asprintf(&temp, "%d %s", channel->Number(), channel->Name()); _channelText = temp; if ((pos = _channelText.rfind(';')) != string::npos) _channelText.erase(pos); if ((pos = _channelText.rfind(',')) != string::npos) _channelText.erase(pos); free(temp); if (!isModeMenu(_mode)) { switch (channel->Vpid()) { case 0: case 1: case 0x1fff: channelType = ctRadio; break; default: channelType = ctTv; break; } setMode(NormalView); } } updateGroup(groupChannel); broadcast(); } } //*************************************************************************** // Osd Set Event //*************************************************************************** void cGraphTFTDisplay::OsdSetEvent(const cEvent* event) { tell(5, "OsdSetEvent: '%s'", event ? event->Title() : ""); _event = event; } //*************************************************************************** // Osd Set Event //*************************************************************************** void cGraphTFTDisplay::OsdSetRecording(const cRecording* recording) { tell(5, "OsdSetRecording: '%s'", recording ? recording->Title() : ""); _recording = recording ? recording->FileName() : ""; } //*************************************************************************** // Osd Channel //*************************************************************************** void cGraphTFTDisplay::OsdChannel(const char *Text) { // Elchi workaround still needed (i don't know) .. return ; } //*************************************************************************** // Osd Programme //*************************************************************************** void cGraphTFTDisplay::OsdProgramme(time_t PresentTime, const char* PresentTitle, const char* PresentSubtitle, time_t FollowingTime, const char* FollowingTitle, const char* FollowingSubtitle) { cMutexLock lock(&_mutex); if (PresentTitle) _rds.title = PresentTitle; else _rds.title = ""; if (PresentSubtitle) _rds.artist = PresentSubtitle; else _rds.artist = ""; broadcast(); } //*************************************************************************** // Set Volume //*************************************************************************** void cGraphTFTDisplay::SetVolume(int, bool) { tell(5, "SetVolume to %d, muted is %s", cDevice::CurrentVolume(), cDevice::PrimaryDevice()->IsMute() ? "true" : "false"); if (_volume != cDevice::CurrentVolume() || _mute != cDevice::PrimaryDevice()->IsMute()) { cMutexLock lock(&_mutex); _volume = cDevice::CurrentVolume(); _mute = cDevice::PrimaryDevice()->IsMute(); updateGroup(groupVolume); broadcast(); } } //*************************************************************************** // Recording //*************************************************************************** void cGraphTFTDisplay::Recording(const cDevice* Device, const char* Name, const char* FileName, bool On) { tell(5, "Recording %s to %p", Name, Device); updateTimers(); } //*************************************************************************** // Timer Change //*************************************************************************** void cGraphTFTDisplay::TimerChange(const cTimer *Timer, eTimerChange Change) { tell(5, "TimerChange %s - %d ", Timer ? Timer->File() : "", Change); updateTimers(); } //*************************************************************************** // update Timers //*************************************************************************** void cGraphTFTDisplay::updateTimers() { cMutexLock lock(&_mutex); _timers.clear(); tell(3, "Clearing internal timer list."); for (cTimer* timer = Timers.First(); timer; timer = Timers.Next(timer)) { tell(3, "Adding timer '%s' to list %s", timer->File(), timer->Recording() ? "timer is regording" : ""); _timers.append(timer); } updateGroup(groupRecording); broadcast(); } //*************************************************************************** // Replaying //*************************************************************************** void cGraphTFTDisplay::Replaying(const cControl* Control, const char* Name, const char* FileName, bool On) { static const char* suffixes[] = { ".ogg", ".mpg", ".mpeg", ".mp3", ".avi", 0 }; tell(5, "Replaying '%s' of '%s' file '%s'; control* is (%p)", On ? "start" : "stop", Name, Str::notNull(FileName), Control); cMutexLock lock(&_mutex); if (On) { string::size_type i, n; if (!Name) Name = ""; _replay.control = (cControl*)Control; _replay.lastMode = _mode; setMode(ReplayNormal); _replay.fileName = Str::notNull(FileName); // OsdSetRecording(Recordings.GetByName(Str::notNull(FileName))); if (strlen(Name) > 6 && Name[0]=='[' && Name[3]==']' && Name[5]=='(') { for (i = 6; Name[i]; ++i) { if (Name[i] == ' ' && Name[i-1] == ')') break; } if (Name[i]) { // replaying mp3 const char* name = skipspace(Name + i); _replay.name = *name ? name : tr("Unknown title"); setMode(ReplayMP3); } } else if (strcmp(Name, "image") == 0) { if (!FileName) _replay.name = Name; else _replay.name = basename(FileName); setMode(ReplayImage); } else if (strcmp(Name, "DVD") == 0) { _replay.name = Name; setMode(ReplayDVD); } else if (strlen(Name) > 7) { for (i = 0, n = 0; Name[i]; ++i) { if (Name[i] == ' ' && Name[i-1] == ',' && ++n == 4) break; } if (Name[i]) { // replaying DVD const char *name = skipspace(Name + i); _replay.name = *name ? name : tr("Unknown title"); replace(_replay.name.begin(), _replay.name.end(), '_', ' '); setMode(ReplayDVD); } } if (_mode == ReplayNormal) { _replay.name = Name; if ((i = _replay.name.rfind('~')) != string::npos) _replay.name.erase(0, i + 1); } char* tmp; asprintf(&tmp, "%s", _replay.name.c_str()); _replay.name = Str::allTrim(tmp); free(tmp); for (int l = 0; suffixes[l]; l++) { if ((i = _replay.name.rfind(suffixes[l])) != string::npos) _replay.name.erase(i); } tell(5, "reformatted name is '%s'", _replay.name.c_str()); updateGroup(groupReplay); broadcast(); } else { _replay.control = 0; _replay.name = ""; _replay.fileName = ""; setMode(NormalView); broadcast(); } } //*************************************************************************** // Osd Status Message //*************************************************************************** void cGraphTFTDisplay::OsdStatusMessage(const char* Message) { tell(5, "OsdStatusMessage %s", Message); cMutexLock lock(&_mutex); _message = Message ? Message : ""; updateGroup(groupMessage); broadcast(); } //*************************************************************************** // Osd Title //*************************************************************************** void cGraphTFTDisplay::OsdTitle(const char* Title) { tell(5, "OsdTitle %s", Title); if (Title) { string::size_type pos; cMutexLock lock(&_mutex); _message = ""; _menu.title = Title; if ((pos = _menu.title.find('\t')) != string::npos) _menu.title.erase(pos); } } //*************************************************************************** // Osd Event Item (called after corresponding OsdItem!) //*************************************************************************** void cGraphTFTDisplay::OsdEventItem(const cEvent* Event, const char* Text, int Index, int Count) { tell(5, "OsdEventItem '%s' at index (%d), count (%d)", Event ? Event->Title() : "", Index, Count); cMutexLock lock(&_mutex); _eventsReady = no; if (_menu.items[Index].text != Text) tell(0, "Fatal: Item index don't match!"); else { _menu.items[Index].event = Event; if (Event) _menu.items[Index].channel = Channels.GetByChannelID(Event->ChannelID()); } if (Index == Count-1) { _eventsReady = yes; tell(3, "OsdEventItem Force update due to index/count (%d/%d)", Index, Count); updateGroup(groupMenu); broadcast(); } } //*************************************************************************** // Osd Item //*************************************************************************** void cGraphTFTDisplay::OsdItem(const char* Text, int Index) { tell(5, "OsdItem '%s' at index (%d)", Text, Index); // for (unsigned int i = 0; i < strlen(Text); i++) // tell(0, "(%d) '%c' (0x%x)", i, Text[i], (unsigned char)Text[i]); cMutexLock lock(&_mutex); _eventsReady = no; if (Text) { MenuItem item; item.text = Text; item.type = itNormal; item.event = 0; item.channel = 0; item.tabCount = 0; int count = 1; char* pp; _menu.currentRow = na; const char* p = Text; while (*p) { if (*p == '\t') count++; p++; } _message = ""; for (int i = 0; i < MaxTabs; ++i) { const char* tp = GetTabbedText(Text, i); if (tp) { char* tab; int len; tab = strdup(tp); len = strlen(Str::allTrim(tab)); // bigger than last row ? if (_menu.charInTabs[i] < len) _menu.charInTabs[i] = len; // detect partingLine if (i == 0 && strstr(tab, "-----") && (pp = strchr(tab, ' '))) { char* e; *pp = 0; pp++; item.type = itPartingLine; if ((e = strchr(pp, ' '))) *e = 0; item.tabs[0] = Str::allTrim(pp); } else if (strstr(tab, "-----") && count < 2) { // arghdirector item.type = itPartingLine; item.tabs[0] = ""; } else if (i == 1 && strstr(item.tabs[0].c_str(), "-----")) { char* p; // some special handling for lines of epgsearch plugin like // ---------------------------------- \t Die 24.10.2006 ------------------------ item.type = itPartingLine; if ((p = strstr(tab, " ---"))) *p = 0; if (!strstr(tab, "-----")) item.tabs[0] = Str::allTrim(tab); else item.tabs[0] = ""; tell(5, "Detected parting line '%s'", item.tabs[0].c_str()); } else { item.tabs[i] = tab; // zwei teure lookups !! if (!item.channel) item.channel = channelByName(tab); if (!item.recording) { item.recording = Recordings.GetByName(tab); // TODO, nicht klar ob es ein Zufallstreffer ist :( if (item.recording) _eventsReady = yes; // needed .. ? } } tell(5, "tab (%d) '%s'", i, tp); item.tabCount++; free(tab); } else break; } tell(4, "adding item with (%d) tabs", item.tabCount); _menu.items.push_back(item); } } //*************************************************************************** // Osd Current Item //*************************************************************************** void cGraphTFTDisplay::OsdCurrentItem(const char* Text) { tell(3, "OsdCurrentItem '%s'", Text); if (!Text) return ; string text = Text; if (text != _menu.current) { int i; cMutexLock lock(&_mutex); _message = ""; _menu.current = text; // lookup the item in the list and set the new current position for (i = 0; i < (int)_menu.items.size(); i++) { if (_menu.items[i].text == _menu.current) { _menu.currentRow = i; updateGroup(groupMenu); broadcast(); return ; } } // We don't have found the item, resume // old one with changed values ... if (_menu.currentRow < 0 || _menu.currentRow >= (int)_menu.items.size()) return ; tell(5, "OsdCurrentItem: Text of current item changed to %s", Text); #if __GNUC__ < 3 MenuItem item = _menu.items[_menu.currentRow]; #else MenuItem item = _menu.items.at(_menu.currentRow); #endif vector::iterator tempIterator; tempIterator = _menu.items.begin(); for (int i = 0; i < _menu.currentRow; i++) tempIterator++; tempIterator = _menu.items.erase(tempIterator); item.text = Text; for (int i = 0; i < MaxTabs; ++i) { const char* tab = GetTabbedText(Text, i); if (tab) item.tabs[i] = tab; if (GetTabbedText(Text, i+1) == 0) break; } _menu.items.insert(tempIterator, item); updateGroup(groupMenu); broadcast(); } } //*************************************************************************** // Osd Clear //*************************************************************************** void cGraphTFTDisplay::OsdClear() { tell(5, "OsdClear"); cMutexLock lock(&_mutex); _menu.title = ""; _menu.items.clear(); _eventsReady = no; _menu.text = ""; _menu.current = ""; _menu.charInTabs[0] = _menu.charInTabs[1] = _menu.charInTabs[2] = _menu.charInTabs[3] = _menu.charInTabs[4] = _menu.charInTabs[5] = 0; if (_message != "") { _message = ""; updateGroup(groupMessage); broadcast(); } } //*************************************************************************** // Osd Help Keys //*************************************************************************** void cGraphTFTDisplay::OsdHelpKeys(const char* Red, const char* Green, const char* Yellow, const char* Blue) { tell(5, "OsdHelpKeys: '%s', '%s', '%s', '%s'", Red, Green, Yellow, Blue); string red = Red ? Red : ""; string green = Green ? Green : ""; string yellow = Yellow ? Yellow : ""; string blue = Blue ? Blue : ""; if (_menu.buttons[0] != red || _menu.buttons[1] != green || _menu.buttons[2] != yellow || _menu.buttons[3] != blue) { cMutexLock lock(&_mutex); _menu.buttons[0] = red; _menu.buttons[1] = green; _menu.buttons[2] = yellow; _menu.buttons[3] = blue; updateGroup(groupButton); broadcast(); } } //*************************************************************************** // Osd Text Item //*************************************************************************** void cGraphTFTDisplay::OsdTextItem(const char* Text, bool Scroll) { tell(5, "OsdTextItem: '%s' scroll: %d", Text, Scroll); if (Text) { cMutexLock lock(&_mutex); _rds.text = Text; _menu.text = Text; broadcast(); } } //*************************************************************************** // Osd Menu Display //*************************************************************************** void cGraphTFTDisplay::OsdMenuDisplay(const char* kind) { tell(5, "OsdMenuDisplay - kind '%s'", kind); cMutexLock lock(&_mutex); if (!isModeMenu(_mode)) _menu.lastMode = _mode; setMode(ModeMenu, kind); } //*************************************************************************** // Osd Menu Destroy //*************************************************************************** void cGraphTFTDisplay::OsdMenuDestroy() { tell(5, "OsdMenuDestroy"); cMutexLock lock(&_mutex); // all menues closed ! if (isModeMenu(_mode)) // 'if' is needed for replay via extrecmenu setMode(_menu.lastMode); } //*************************************************************************** // //*************************************************************************** cDisplayItem* cGraphTFTDisplay::getItemAt(int x, int y) { // first check foreground items .. for (cDisplayItem* p = currentSection->getItems()->First(); p; p = currentSection->getItems()->Next(p)) { if (!p->Foreground() || !p->evaluateCondition()) continue; if (p->OnClick() == "" && p->OnDblClick() == "" && p->OnUp() == "" && p->OnDown() == "") continue; if (x >= p->X() && x <= p->X()+p->Width() && y >= p->Y() && y <= p->Y()+p->Height()) { return p; } } // now the other .. for (cDisplayItem* p = currentSection->getItems()->First(); p; p = currentSection->getItems()->Next(p)) { if (p->Foreground() || !p->evaluateCondition()) continue; if (p->OnClick() == "" && p->OnDblClick() == "" && p->OnUp() == "" && p->OnDown() == "") continue; if (x >= p->X() && x <= p->X()+p->Width() && y >= p->Y() && y <= p->Y()+p->Height()) { return p; } } return 0; } //*************************************************************************** // Mouse Event //*************************************************************************** void cGraphTFTDisplay::mouseEvent(int x, int y, int button, int flag, int data) { static int whipeDiff = 0; if (!currentSection) return ; mouseX = x; mouseY = y; mouseKey = button; cMutexLock lock(&_mutex); cDisplayItem* p = getItemAt(x, y); if (p) tell(4, "Mouse action on item %d [%s] at (%d/%d)", p->Item(), p->Text().c_str(), x, y); if (isMode(ModeCalibration)) { calibrateTouch(x, y); updateGroup(groupCalibrate); broadcast(yes); if (calibration.state < csTest) return ; } if (button == cGraphTftComService::mbWheelUp) { if (!p || p->OnUp() == "") cRemote::Put(cKey::FromString("Up")); else processAction(p, p->OnUp()); } else if (button == cGraphTftComService::mbWheelDown) { if (!p || p->OnDown() == "") cRemote::Put(cKey::FromString("Down")); else processAction(p, p->OnDown()); } else if (button == cGraphTftComService::mbRight) { cRemote::Put(cKey::FromString("Back")); } else if (button == cGraphTftComService::mbLeft) { if (!p) return ; if (flag & cGraphTftComService::efVWhipe) { tell(3, "vertical whipe of (%d) pixel", data); whipeDiff += data; int step = abs(whipeDiff) / p->WhipeRes(); if (step) { tell(3, "do step of (%d)", step); if (whipeDiff < 0) { if (p->OnDown() != "") processAction(p, p->OnDown(), step); } else { if (p->OnUp() != "") processAction(p, p->OnUp(), step); } whipeDiff = whipeDiff % p->WhipeRes(); } return ; } whipeDiff = 0; // menue navigation area ? if (p->Item() == itemMenuNavigationArea && !touchMenu && _menu.lineHeight > 0) { int clickRow; int yOff = y - p->Y(); int currentRowY = (_menu.currentRow - _menu.topRow) * _menu.lineHeight; if (yOff < currentRowY) clickRow = yOff / _menu.lineHeight + _menu.topRow; else if (yOff < currentRowY + _menu.lineHeightSelected) clickRow = _menu.currentRow; else clickRow = (yOff-_menu.lineHeightSelected) / _menu.lineHeight + _menu.topRow+1; if (_menu.currentRow < clickRow) // down for (int i = _menu.currentRow; i < clickRow; i++) cRemote::Put(cKey::FromString("Down")); else if (_menu.currentRow > clickRow) // up for (int i = _menu.currentRow; i > clickRow; i--) cRemote::Put(cKey::FromString("Up")); } // check if item with defined action if (p->OnClick() != "" && flag == cGraphTftComService::efNone) { if (p->OnClick().find("touchMenu") == 1) { char* val; char* val2; char* click; asprintf(&click, "%s", p->OnClick().c_str()); // touch menu handling if ((val = strchr(click, ':')) && *(val++)) { if ((val2 = strchr(val, ':')) && *(val2++) && atoi(val) == touchMenu) touchMenu = atoi(val2); else touchMenu = atoi(val); } else touchMenu = !touchMenu; if (p->Delay()) touchMenuHideTime = p->Delay(); if (touchMenuHideTime) touchMenuHideAt = msNow() + touchMenuHideTime; tell(4, "touch menu switched to (%d) hide in (%d) seconds", touchMenu, touchMenuHideTime); free(click); broadcast(yes); } else { // no key, and not the 'touchMenu' command processAction(p, p->OnClick()); } } if (p->OnDblClick() != "" && (flag & cGraphTftComService::efDoubleClick)) { processAction(p, p->OnDblClick()); } } } //*************************************************************************** // Process Action //*************************************************************************** int cGraphTFTDisplay::processAction(cDisplayItem* p, string action, int step) { string v; Scan scan(action.c_str()); string name; eKeys key; int value = 0; string str; scan.eat(); key = cKey::FromString(scan.lastIdent()); tell(4, "Performing mouse action (%d) times, key '%s', %sfound in VDRs keytab", step, scan.all(), key != kNone ? "" : "not "); // first check if 'normal' key actions if (key != kNone) { for (int i = 0; i < abs(step); i++) { scan.reset(); while (scan.eat() == success) { if ((key = cKey::FromString(scan.lastIdent())) != kNone) cRemote::Put(key); } } return success; } // perform special action on theme variable if (!scan.isIdent() || p->lookupVariable(scan.lastIdent(), v) != success) { tell(0, "Error: Invalid variable '%s' in '%s'", scan.lastIdent(), scan.all()); return fail; } name = scan.lastIdent(); if (scan.eat() != success || !scan.isOperator()) { tell(0, "Error: Invalid operator '%s' in '%s'", scan.lastIdent(), scan.all()); return fail; } // get the actual value of the variable value = atoi(v.c_str()); // perform action on the value if (scan.hasValue("++")) value += step; else if (scan.hasValue("--")) value -= step; else if (scan.hasValue(":")) { int v1; if (scan.eat() != success || !scan.isNum()) { tell(0, "Missing int value in '%s', ignoring", scan.all()); return fail; } v1 = scan.lastInt(); if (value == v1 && scan.eat() == success && scan.isOperator() && scan.hasValue(":")) { if (scan.eat() != success || !scan.isNum()) { tell(0, "Missing second int value in '%s', ignoring", scan.all()); return fail; } value = scan.lastInt(); } else { value = v1; } } else { tell(0, "Unexpected operation in '%s', ignoring", scan.all()); return fail; } tell(6, "Setting '%s' from (%s) to (%d)", name.c_str(), v.c_str(), value); p->setVariable(name.c_str(), value); broadcast(yes); return done; } //*************************************************************************** // Calibrate Touch Device //*************************************************************************** int cGraphTFTDisplay::calibrateTouch(int x, int y) { static double upperLeftX; static double upperLeftY; string s; int offset = 20; if (Thms::theTheme->lookupVar("calibrationFrameOffset", s) == success) offset = atoi(s.c_str()); calibration.state++; switch (calibration.state) { case csUpperLeft: { calibration.instruction = "Click upper left corner"; calibration.cursorX = offset; calibration.cursorY = offset; calibration.lastX = 0; calibration.lastY = 0; break; } case csUpperRight: { upperLeftX = x; upperLeftY = y; calibration.instruction = "Click upper right corner"; calibration.cursorX = Thms::theTheme->getWidth() - offset; calibration.cursorY = offset; break; } case csLowerLeft: { // check for swap if (abs(calibration.lastY - y) > abs(calibration.lastX - x)) { calibration.settings.swapXY = yes; if (touchThread) touchThread->resetSetting(yes); calibration.info = "detected flags (swapXY)"; // restart calibration due to XY swap! calibration.state = csUnknown; calibrateTouch(0, 0); break; } calibration.settings.scaleX = ((double)(Thms::theTheme->getWidth() - 2*offset)) / ((double)(x-calibration.lastX)); calibration.instruction = "Click lower left corner"; calibration.cursorX = offset; calibration.cursorY = Thms::theTheme->getHeight() - offset; break; } case csLowerRight: { calibration.settings.scaleY = ((double)(Thms::theTheme->getHeight()- 2*offset)) / ((double)(y-calibration.lastY)); // upperLeftY calibration.settings.offsetX = (int)((((double)offset)/calibration.settings.scaleX) - upperLeftX); calibration.settings.offsetY = (int)((((double)offset)/calibration.settings.scaleY) - upperLeftY); calibration.settings.scaleWidth = Thms::theTheme->getWidth(); calibration.settings.scaleHeight = Thms::theTheme->getHeight(); calibration.instruction = "Click lower right corner"; calibration.cursorX = Thms::theTheme->getWidth() - offset; calibration.cursorY = Thms::theTheme->getHeight() - offset; break; } case csDone: { if (touchThread) { touchThread->setSetting(&calibration.settings); touchThread->setCalibrate(off); GraphTFTSetup.touchSettings = calibration.settings; GraphTFTSetup.Store(yes); } tell(0, "Calibration done, offset (%d/%d), scale (%f/%f)", calibration.settings.offsetX, calibration.settings.offsetY, calibration.settings.scaleX, calibration.settings.scaleY); calibration.info = "verify by touching ..."; calibration.instruction = "Calibration done"; calibration.cursorX = x; calibration.cursorY = y; } case csTest: { calibration.info = "verify by touching ..."; calibration.instruction = "Calibration done"; calibration.cursorX = x; calibration.cursorY = y; break; } default: { calibration.cursorX = x; calibration.cursorY = y; } } tell(0, "Callibration step '%s'", calibration.instruction.c_str()); calibration.lastX = x; calibration.lastY = y; return done; } //*************************************************************************** // Display //*************************************************************************** int cGraphTFTDisplay::display(string sectionName) { int count = 0; LogDuration ld("cGraphTFTDisplay::display()"); if (!displayActive) return 0; if (isModeNormal(_mode)) UpdateProgramme(); else if (isModeMenu(_mode) && !Thms::theTheme->getSection(sectionName)) { tell(0, "Info: Section faked to '%s' due to section '%s' not defined!", "Menu", sectionName.c_str()); sectionName = "Menu"; } if (!(currentSection = Thms::theTheme->getSection(sectionName))) return 0; // set/reset force flag cDisplayItem::setForce(forceNextDraw); forceNextDraw = no; if (cDisplayItem::getForce()) tell(1, "Force draw of all items now %s", forceWriteThrough ? "- write through" : ""); // section changed if (currentSection != lastSection) { tell(0, "Section changed from '%s' to '%s'", lastSection ? lastSection->getName().c_str() : "", currentSection->getName().c_str()); lastSection = currentSection; _menu.topRow = na; _menu.lineHeight = na; cDisplayItem::clearSelectedItem(); clear(); cDisplayItem::setForce(yes); } // for now ... if (isModeMenu(_mode)) { cDisplayItem::setForce(yes); tell(3, "force due to menu section!"); // reset x of menu items for (string::size_type i = 0; i < _menu.items.size(); i++) _menu.items[i].nextX = 0; } // check if any foreground item is visible for (cDisplayItem* p = currentSection->getItems()->First(); p; p = currentSection->getItems()->Next(p)) { if (p->Foreground() && p->evaluateCondition()) { cDisplayItem::setForce(yes); break ; } } // draw items for (cDisplayItem* p = currentSection->getItems()->First(); p; p = currentSection->getItems()->Next(p)) { if (!p->isForegroundItem() && !p->Foreground()) count += p->refresh(); } for (cDisplayItem* p = currentSection->getItems()->First(); p; p = currentSection->getItems()->Next(p)) { if (p->isForegroundItem() || p->Foreground()) count += p->refresh(); } // update display if (count) refresh(); return count; } void cGraphTFTDisplay::clear() { renderer->clear(); } //*************************************************************************** // Refresh //*************************************************************************** void cGraphTFTDisplay::refresh() { LogDuration ld("cGraphTFTDisplay::refresh()"); // refresh local display if (!isDummyRender) renderer->refresh(forceWriteThrough); forceWriteThrough = no; // refresh tcp client if (comThread) comThread->refresh(); // dump to file ... if (GraphTFTSetup.DumpImage) { static int lastDumpAt = msNow(); int lastDumpDiff = lastDumpAt ? msNow() - lastDumpAt : 0; if (lastDumpDiff > (GraphTFTSetup.DumpRefresh * 1000)) { if (isDummyRender) // with dummy renderer refresh only for dump renderer->refresh(); // dump lastDumpAt = msNow(); renderer->dumpImage2File("/graphTFT.png", GraphTFTSetup.DumpImageX, GraphTFTSetup.DumpImageY); } } if (userDumpFile) { if (isDummyRender) // with dummy renderer refresh only for dump renderer->refresh(); renderer->dumpImage2File(userDumpFile, userDumpWidth, userDumpHeight); free(userDumpFile); userDumpFile = 0; } if (!versionReported && time(0) > startedAt+30) { versionReported = yes; if (Thms::theTheme->check(syntaxVersion) != success) { Skins.Message(mtInfo, tr("graphTFT: The syntax version " "of the theme file don't match!")); tell(0, "Syntax version of theme '%s' mismatch!", Thms::theTheme->getName().c_str()); } } } //*************************************************************************** // Trigger Dump //*************************************************************************** void cGraphTFTDisplay::triggerDump(const char* file, int width, int height) { userDumpWidth = width == na ? GraphTFTSetup.DumpImageX : width; userDumpHeight = height == na ? GraphTFTSetup.DumpImageY : height; free(userDumpFile); userDumpFile = strdup(file); wakeup = yes; _doUpdate.Broadcast(); } int cGraphTFTDisplay::updateGroup(int group) { if (!currentSection) return ignore; return currentSection->updateGroup(group); }