neocontrol.cpp 19.4 KB
Newer Older
1
#include "neocontrol.h"
2
#include <QPainter>
3

4 5
NeoControl::NeoControl(QWidget * parent, Qt::WFlags f)
:  QWidget(parent)
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
{
#ifdef QTOPIA
    this->setWindowState(Qt::WindowMaximized);
#else
    Q_UNUSED(f);
#endif
    bBack = new QPushButton(this);
    connect(bBack, SIGNAL(clicked()), this, SLOT(backClicked()));

    bNext = new QPushButton(tr("Next"), this);
    connect(bNext, SIGNAL(clicked()), this, SLOT(nextClicked()));

    bSave = new QPushButton(tr("Save"), this);
    connect(bSave, SIGNAL(clicked()), this, SLOT(saveClicked()));

    chkFso = new QCheckBox(tr("Use FSO (freesmartphone.org)"), this);
22 23
    connect(chkFso, SIGNAL(stateChanged(int)), this,
            SLOT(fsoStateChanged(int)));
24

25 26 27 28
    chkCharge = new QCheckBox(tr("Log charging"), this);
    connect(chkCharge, SIGNAL(stateChanged(int)), this,
            SLOT(chargeStateChanged(int)));

29
    label = new QLabel(this);
30 31
    normalFont = label->font();
    smallFont = QFont(normalFont.family(), (3 * normalFont.pointSize()) / 5);
32 33 34 35 36 37 38 39 40 41 42 43
    lineEdit = new QLineEdit(this);

    label4 = new QLabel(this);
    label5 = new QLabel(this);

    slider4 = new MixerSlider(this);
    slider5 = new MixerSlider(this);

    buttonLayout = new QHBoxLayout();
    buttonLayout->setAlignment(Qt::AlignBottom);
    buttonLayout->addWidget(bBack);
    buttonLayout->addWidget(bNext);
44
    buttonLayout->addWidget(chkCharge);
45 46 47 48 49 50 51 52

    layout = new QVBoxLayout(this);
    layout->addWidget(label);
    layout->addWidget(label4);
    layout->addWidget(slider4);
    layout->addWidget(label5);
    layout->addWidget(slider5);
    layout->addWidget(lineEdit);
53
    layout->addWidget(bSave);
54 55 56
    layout->addWidget(chkFso);
    layout->addLayout(buttonLayout);

57
    showScreen(NeoControl::ScreenCharge);
58 59 60 61 62 63 64
}

NeoControl::~NeoControl()
{

}

65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
// Qtopia compat for Qt only PC version
#ifndef QTOPIA
namespace Qtopia
{
    QByteArray readFile(const char *path);
    QDateTime rtcNow();
}
QByteArray Qtopia::readFile(const char *path)
{
    QFile f(path);
    if (!f.open(QIODevice::ReadOnly)) {
        qWarning() << "readFile failed" << path << ":" << f.errorString();
        return QByteArray();
    }
    QByteArray content = f.readAll();
    f.close();
    return content;
}

QDateTime Qtopia::rtcNow()
{
    QByteArray secsStr = readFile("/sys/class/rtc/rtc0/since_epoch").trimmed();
    uint secs = secsStr.toUInt();
    return QDateTime::fromTime_t(secs);
}
#endif

92 93
void NeoControl::backClicked()
{
94
    switch (screen) {
95 96 97
    case ScreenInit:
        close();
        break;
98
    case ScreenRtc:
99 100
        showScreen(ScreenInit);
        break;
101 102 103
    case ScreenMixer:
        showScreen(ScreenRtc);
        break;
104 105 106 107 108 109
    case ScreenModem:
        showScreen(ScreenMixer);
        break;
    case ScreenSysfs:
        showScreen(ScreenModem);
        break;
110
    case ScreenCharge:
111 112 113 114 115 116 117
        showScreen(ScreenSysfs);
        break;
    }
}

void NeoControl::nextClicked()
{
118
    switch (screen) {
119
    case ScreenInit:
120 121 122
        showScreen(ScreenRtc);
        break;
    case ScreenRtc:
123 124 125 126 127 128 129 130 131
        showScreen(ScreenMixer);
        break;
    case ScreenMixer:
        showScreen(ScreenModem);
        break;
    case ScreenModem:
        showScreen(ScreenSysfs);
        break;
    case ScreenSysfs:
132
        showScreen(ScreenCharge);
133
        break;
134
    case ScreenCharge:
135 136 137 138 139 140
        break;
    }
}

void NeoControl::saveClicked()
{
141 142 143
    if (screen == ScreenMixer) {
        system
            ("alsactl -f /opt/qtmoko/etc/alsa/gta04_initial_alsa.state store");
144
    }
145 146 147 148
    if (screen == ScreenModem) {
        QSettings cfg("Trolltech", "Modem");
        cfg.setValue("OPSYS/Value", lineEdit->text());
        cfg.sync();
Radek Polak's avatar
Radek Polak committed
149 150 151 152 153 154 155 156 157 158 159
        QMessageBox::information(this, tr("Modem settings"),
                                 tr
                                 ("Settings will be activated after restarting QtMoko with POWER button"));
    }
    if (screen == ScreenCharge) {
        QSettings cfg("Trolltech", "qpe");
        cfg.setValue("Charging/LogInterval", lineEdit->text().toInt());
        cfg.sync();
        QMessageBox::information(this, tr("Log settings"),
                                 tr
                                 ("Settings will be activated after restarting QtMoko with POWER button"));
160
    }
161 162 163 164
}

void NeoControl::showScreen(NeoControl::Screen scr)
{
165
    if (scr == ScreenMixer) {
166 167
        openAlsaMixer();
    }
168
    if (this->screen == ScreenMixer) {
169 170
        closeAlsaMixer();
    }
171
    if (this->screen == ScreenSysfs) {
172 173
        label->setFont(normalFont);
    }
174
    if (scr == ScreenSysfs) {
175 176
        label->setFont(smallFont);
    }
177
    if (this->screen == ScreenCharge) {
178 179
        setFont(normalFont);
    }
180
    if (scr == ScreenCharge) {
181 182
        setFont(smallFont);
    }
183 184 185

    this->screen = scr;

186 187 188
    label->setVisible(scr == ScreenInit || scr == ScreenRtc
                      || scr == ScreenMixer || scr == ScreenModem
                      || scr == ScreenSysfs);
189
    bBack->setText(scr == ScreenInit ? tr("Quit") : tr("Back"));
190
    lineEdit->setVisible(scr == ScreenModem);
191
    chkFso->setVisible(scr == ScreenModem);
192
    chkCharge->setVisible(scr == ScreenCharge);
193 194 195 196
    label4->setVisible(scr == ScreenMixer);
    label5->setVisible(scr == ScreenMixer);
    slider4->setVisible(scr == ScreenMixer);
    slider5->setVisible(scr == ScreenMixer);
197
    bSave->setVisible(scr == ScreenMixer || scr == ScreenModem);
198

199
    switch (scr) {
200 201 202
    case ScreenInit:
        label->setText(tr("Neo hardware tool"));
        break;
203 204 205
    case ScreenRtc:
        updateRtc();
        break;
206 207 208 209 210 211
    case ScreenMixer:
        updateMixer();
        break;
    case ScreenModem:
        updateModem();
        break;
212 213
    case ScreenCharge:
        updateCharge();
214 215 216 217 218 219 220 221 222 223
        break;
    case ScreenSysfs:
        updateSysfs();
        break;

    default:
        break;
    }
}

224 225
static int computeCurrent(int secs, int chargeBefore, int chargeAfter)
{
226 227
    if (secs == 0)
        return 0;
228 229 230 231 232
    return ((chargeBefore - chargeAfter) * 36) / (10 * secs);
}

void NeoControl::paintEvent(QPaintEvent *)
{
233
    if (screen != ScreenCharge) {
234 235 236
        return;
    }

237 238
    QList < QString > lines = chargeLog.split('\n');
    if (lines.count() < 2)
239 240
        return;

241 242
    QList < QDateTime > dates;
    QList < int >charges;
243 244 245 246 247

    QDateTime dtMin(QDate(2999, 1, 1));
    QDateTime dtMax(QDate(1899, 1, 1));
    int chargeMax = 0;

248 249 250
    for (int i = 0; i < lines.count(); i++) {
        QList < QString > values = lines.at(i).split('\t');
        if (values.count() < 2) {
251 252
            continue;
        }
253 254
        QDateTime dt =
            QDateTime::fromString(values.at(0), "yyyy-MM-dd hh:mm:ss");
255 256 257 258 259 260 261 262 263 264 265 266 267 268
        int charge = values.at(1).toInt();
        dates.append(dt);
        charges.append(charge);

        dtMin = (dtMin < dt ? dtMin : dt);
        dtMax = (dtMax > dt ? dtMax : dt);
        chargeMax = (chargeMax > charge ? chargeMax : charge);
    }

    int w = (9 * this->width()) / 10;
    int h = bNext->y() - bNext->height();

    int totalSecs = dtMin.secsTo(dtMax);

269
    if (chargeMax == 0 || totalSecs == 0)
270 271 272
        return;

    QPainter p(this);
273 274 275 276
    p.fillRect(0, 0, width(), height(), Qt::black);
    p.setBackground(Qt::black);
    p.setBrush(Qt::white);
    p.setPen(Qt::white);
277 278 279 280 281 282 283 284 285 286 287

    int fontW = p.fontMetrics().width('w');
    int fontH = p.fontMetrics().height();

    p.translate(fontW, fontH);

    p.drawLine(0, h, w, h);
    p.drawLine(0, 0, 0, h);

    QPen pen = p.pen();

288
    for (int round = 0; round <= 1; round++) {
289
        int chargeX = 0x7fffffff;
290 291 292 293 294 295 296 297 298 299 300 301 302 303
        int currentX = 0x7fffffff;
        int hourTextX = 0x7fffffff;

        int x1 = -1;
        int y1 = -1;
        int prevSecs = -1;
        int prevCharge = -1;

        for (int i = 0; i < dates.count(); i++) {
            QDateTime dt = dates.at(i);
            int charge = charges.at(i);
            int secs = dtMin.secsTo(dt);

            int x2 = (w * secs) / totalSecs;
304 305 306
            int y2 = (h * charge) / chargeMax;

            y2 = h - (5 * y2 / 6);      // flip y and add 1/6 for charge value text
307 308 309 310 311 312

            // Draw time on x axis
            if (abs(x2 - hourTextX) > 5 * fontW) {
                if (round == 0) {
                    p.drawText(x2, h + fontH, dt.toString("hh:mm"));
                    p.setPen(Qt::darkGreen);
313
                    p.drawLine(x2, y2, x2, h);
314 315 316 317 318 319
                    p.setPen(pen);
                }
                hourTextX = x2;
            }
            // Draw charge point and charge value
            p.drawEllipse(x2 - 2, y2 - 2, 4, 4);
320
            int shiftY = -h / 6;
321 322
            int y = y2 + shiftY;
            if (abs(x2 - chargeX) > 2 * fontW) {
323
                QString text = QString::number(charge);
324 325 326 327 328 329 330 331 332 333 334 335

                p.setPen(Qt::darkGreen);
                p.drawLine(x2, y, x2, y2);
                p.setPen(pen);

                p.save();
                p.translate(x2, y);
                p.rotate(90);
                p.drawText(0, 0, text);
                p.restore();

                chargeX = x2;
336 337 338 339 340 341
            }
            // Draw charge line and in the middle write current
            if (x1 >= 0) {
                p.drawLine(x1, y1, x2, y2);
                y = (y1 + y2) / 2;
                int x = (x1 + x2) / 2;
342
                int shiftY = h / 4;
343
                if (abs(x - currentX) > 2 * fontW) {
344 345 346
                    int current =
                        computeCurrent(secs - prevSecs, prevCharge, charge);
                    if (round == 0) {
347 348
                        p.setPen(Qt::red);
                        p.drawLine(x, y, x, y + shiftY);
349 350 351 352
                        p.setPen(pen);
                    }
                    QString text = QString::number(current) + "mA";
                    int textW = p.fontMetrics().width(text);
353 354 355
                    p.save();
                    p.translate(x, y + shiftY);
                    p.rotate(90);
356
                    p.fillRect(0, -fontH, textW, fontH, Qt::red);
357 358
                    p.drawText(0, 0, text);
                    p.restore();
359 360
                    currentX = x;
                }
361 362
            }

363 364 365 366 367
            x1 = x2;
            y1 = y2;
            prevSecs = secs;
            prevCharge = charge;
        }
368 369 370
    }
}

371 372
void NeoControl::updateRtc()
{
373
    if (screen != ScreenRtc)
374 375 376 377 378 379
        return;

    QDateTime rtcNow = Qtopia::rtcNow();
    QString rtcDate = Qtopia::readFile("/sys/class/rtc/rtc0/date").trimmed();
    QString rtcTime = Qtopia::readFile("/sys/class/rtc/rtc0/time").trimmed();

380 381
    QByteArray wakealarmStr =
        Qtopia::readFile("/sys/class/rtc/rtc0/wakealarm").trimmed();
382
    QString alarmStr;
383
    if (wakealarmStr.isEmpty()) {
384 385 386 387 388 389 390
        alarmStr = tr("not set");
    } else {
        uint wakealarmSecs = wakealarmStr.toUInt();
        QDateTime wakealarmDt = QDateTime::fromTime_t(wakealarmSecs);
        alarmStr = wakealarmDt.toString();
    }

391 392 393 394 395 396 397
    label->setText(QString
                   (tr
                    ("RTC (Real time clock)\n\nDate: %1\nTime: %2\nLocal: %3\nAlarm: %4"))
                   .arg(rtcDate)
                   .arg(rtcTime)
                   .arg(rtcNow.toString())
                   .arg(alarmStr));
398 399 400 401

    QTimer::singleShot(1000, this, SLOT(updateRtc()));
}

402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
int NeoControl::openAlsaMixer()
{
    int ret = 0;
    QString text(tr("Call volume settings\n\n"));

    if ((ret = snd_mixer_open(&mixerFd, 0)) < 0) {
        text += QString("snd_mixer_open error %1").arg(ret);
        goto err;
    }
    if ((ret = snd_mixer_attach(mixerFd, "default")) < 0) {
        text += QString("snd_mixer_attach error %1").arg(ret);
        goto err;
    }
    if ((ret = snd_mixer_selem_register(mixerFd, NULL, NULL)) < 0) {
        text += QString("snd_mixer_selem_register error %1").arg(ret);
        goto err;
    }
    if ((ret = snd_mixer_load(mixerFd)) < 0) {
        text += QString("snd_mixer_load error %1").arg(ret);
        goto err;
    }

    goto ok;

426
err:
427 428 429
    if (mixerFd)
        snd_mixer_close(mixerFd);
    mixerFd = NULL;
430
ok:
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448
    label->setText(text);

    return ret;
}

void NeoControl::closeAlsaMixer()
{
    if (mixerFd) {
        snd_mixer_detach(mixerFd, "default");
        snd_mixer_close(mixerFd);
        mixerFd = NULL;
    }

    system("alsactl -f /opt/qtmoko/etc/alsa/gta04_initial_alsa.state restore");
}

void NeoControl::updateMixer()
{
449
    if (screen != ScreenMixer) {
450 451
        return;
    }
452
    if (slider4->sliding || slider5->sliding) {
453 454 455 456 457 458 459 460 461
        QTimer::singleShot(100, this, SLOT(updateMixer()));
        return;
    }

    snd_mixer_elem_t *elem;
    snd_mixer_elem_t *elem4 = NULL;
    snd_mixer_elem_t *elem5 = NULL;

    for (elem = snd_mixer_first_elem(mixerFd); elem;
462
         elem = snd_mixer_elem_next(elem)) {
463 464
        QString elemName = QString(snd_mixer_selem_get_name(elem));

465
        if (elemName == "DAC2 Digital Fine") {
466
            elem4 = elem;
467
        } else if (elemName == "Analog") {
468 469 470 471 472 473 474
            elem5 = elem;
        }
    }

    slider4->setMixerElem(elem4, true);
    slider5->setMixerElem(elem5, false);

475 476
    label4->setText(tr("Playback %1").arg(slider4->volume));    // Mono Playback Volume
    label5->setText(tr("Microphone %1").arg(slider5->volume));  // Mono Sidetone Playback Volume
477 478 479 480 481 482 483 484 485

    label->setText(tr("Call volume settings"));

    QTimer::singleShot(1000, this, SLOT(updateMixer()));
}

QString NeoControl::getQpeEnv()
{
    QFile f("/opt/qtmoko/qpe.env");
486 487 488
    if (!f.open(QFile::ReadOnly)) {
        QMessageBox::critical(this, tr("FSO"),
                              tr("Failed to read") + " " + f.fileName());
489 490 491 492 493 494 495 496 497 498 499 500
        return "";
    }
    QString content = f.readAll();
    f.close();
    return content;
}

void NeoControl::setQpeEnv(bool fso)
{
    QString content = getQpeEnv();
    QString fsoStr = "export QTOPIA_PHONE=Fso";
    QString atStr = "export QTOPIA_PHONE=AT";
501
    if (fso) {
502
        content = content.replace(atStr, fsoStr);
503
    } else {
504 505 506
        content = content.replace(fsoStr, atStr);
    }
    QFile f("/opt/qtmoko/qpe.env");
507 508 509
    if (!f.open(QFile::WriteOnly)) {
        QMessageBox::critical(this, tr("FSO"),
                              tr("Failed to write to") + " " + f.fileName());
510 511 512 513
        return;
    }
    f.write(content.toLatin1());
    f.close();
514 515 516
    QMessageBox::information(this, tr("FSO"),
                             tr
                             ("You have to restart your phone for changes to take place"));
517 518 519 520
}

void NeoControl::fsoStateChanged(int)
{
521
    if (updatingScreen) {
522 523 524 525 526 527 528 529
        return;
    }
    QTimer::singleShot(0, this, SLOT(fsoChange()));
}

void NeoControl::fsoChange()
{
    bool checked = chkFso->isChecked();
530 531 532 533 534
    if (!checked) {
        QProcess::execute("qterminal",
                          QStringList() << "-c" << "update-rc.d" << "-f" <<
                          "fso-deviced" << "remove");
        setQpeEnv(false);       // disable FSO
535 536
        return;
    }
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557
    if (!QFile::exists("/usr/sbin/fsogsmd")) {
        QMessageBox::information(this, tr("FSO"),
                                 tr
                                 ("FSO packages have to be downloaded and installed. Please make sure you have internet connection now."));
        QProcess::execute("raptor",
                          QStringList() << "-u" << "-i" << "fso-gsmd-openmoko"
                          << "fso-usaged-openmoko");
        QMessageBox::information(this, tr("FSO"),
                                 tr
                                 ("QtMoko needs very recent FSO, it will be downloaded from http://activationrecord.net/radekp/pub/libfsogsm.so.0.0.0"));
        QProcess::execute("qterminal",
                          QStringList() << "-c" << "wget" <<
                          "http://activationrecord.net/radekp/pub/libfsogsm.so.0.0.0");
        QProcess::execute("qterminal",
                          QStringList() << "-c" << "mv" << "libfsogsm.so.0.0.0"
                          <<
                          "/usr/lib/cornucopia/libs/fsogsm/libfsogsm.so.0.0.0");
    }
    QProcess::execute("qterminal",
                      QStringList() << "-c" << "update-rc.d" << "fso-deviced" <<
                      "defaults");
558 559 560 561 562
    setQpeEnv(true);
}

void NeoControl::updateModem()
{
563
    if (screen != ScreenModem) {
564 565
        return;
    }
566
    updatingScreen = true;
567

Radek Polak's avatar
Radek Polak committed
568
    if (!lineEdit->hasFocus()) {
569 570
        QSettings cfg("Trolltech", "Modem");
        lineEdit->setText(cfg.value("OPSYS/Value", "AT_OPSYS=0,2").toString());
Radek Polak's avatar
Radek Polak committed
571 572 573
        label->
            setText
            ("AT_OPSYS=0,2 is 2G only\nAT_OPSYS=3,2 is 3G\n3G=modem troubles");
574
    }
575 576 577 578 579

    QString qpeEnv = getQpeEnv();
    QString fsoStr = "export QTOPIA_PHONE=Fso";
    chkFso->setChecked(qpeEnv.indexOf(fsoStr) >= 0);

580
    updatingScreen = false;
581 582 583
    QTimer::singleShot(1000, this, SLOT(updateModem()));
}

584 585 586
static void appendValue(QString desc, QString file, QString * text,
                        QByteArray replaceBefore = "", QByteArray replaceAfter =
                        "")
587 588 589 590 591
{
    text->append(desc);
    text->append(": ");

    QFile f(file);
592
    if (!f.open(QFile::ReadOnly)) {
593
        text->append("failed to open " + file + " " + f.errorString() + "\n");
594
    } else {
595
        QByteArray content = f.readAll();
596
        if (content.length() == 0) {
597
            text->append('\n');
598 599
        } else {
            if (replaceBefore.count() > 0) {
600 601
                content = content.replace(replaceBefore, replaceAfter);
            }
602 603 604 605 606 607 608 609
            text->append(content);
        }
        f.close();
    }
}

void NeoControl::updateSysfs()
{
610
    if (screen != ScreenSysfs) {
611 612 613 614
        return;
    }

    QString text;
615 616 617 618 619
    appendValue(tr("Battery"), "/sys/class/power_supply/bq27000-battery/uevent",
                &text, "POWER_SUPPLY_", "  ");
    appendValue(tr("USB"), "/sys/class/power_supply/twl4030_usb/uevent", &text,
                "POWER_SUPPLY_", "  ");
    appendValue(tr("USB max current"),
620
                "/sys/class/power_supply/twl4030_usb/max_current", &text);
621

622 623 624 625
    label->setText(text);

    QTimer::singleShot(1000, this, SLOT(updateSysfs()));
}
626

627 628 629 630 631 632
#ifdef QTOPIA
#define CHARGE_LOG_FILE "/var/log/charging"
#else
#define CHARGE_LOG_FILE "/mnt/neo/var/log/charging"
#endif

633 634
void NeoControl::updateCharge()
{
635
    if (screen != ScreenCharge) {
636 637
        return;
    }
638 639 640 641
    updatingScreen = true;

    QFile f(CHARGE_LOG_FILE);
    bool isLogging = f.exists();
Radek Polak's avatar
Radek Polak committed
642

643
    chkCharge->setChecked(isLogging);
Radek Polak's avatar
Radek Polak committed
644 645 646 647
    label->setVisible(!isLogging);
    lineEdit->setVisible(!isLogging);
    bSave->setVisible(!isLogging);

648
    if (isLogging) {
Radek Polak's avatar
Radek Polak committed
649
        setFont(smallFont);
650 651 652 653
        if (f.open(QIODevice::ReadOnly)) {
            chargeLog = f.readAll();
            f.close();
        }
Radek Polak's avatar
Radek Polak committed
654 655 656 657 658 659 660 661 662
    } else {
        setFont(normalFont);
        label->setText(tr("Logging interval"));
        if (!lineEdit->hasFocus()) {
            QSettings cfg("Trolltech", "qpe");
            cfg.beginGroup("Charging");
            int chargingLogInterval = cfg.value("LogInterval", 300).toInt();
            lineEdit->setText(QString::number(chargingLogInterval));
        }
663
    }
664

Radek Polak's avatar
Radek Polak committed
665
    QTimer::singleShot(3000, this, SLOT(updateCharge()));
666
    update();
667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
    updatingScreen = false;
}

void NeoControl::chargeStateChanged(int state)
{
    if (updatingScreen)
        return;

    QFile f(CHARGE_LOG_FILE);
    if (state == Qt::Checked) {
        if (!f.exists()) {
            if (f.open(QIODevice::WriteOnly)) {
                f.close();
            }
            QMessageBox::information(this, tr("Charging log"),
                                     tr
683
                                     ("Charging log is now in /var/log/charging. It will take a while until logging starts."));
684 685 686 687 688 689 690 691 692 693
        }
    } else {
        if (QMessageBox::question(this, tr("Stop charging log"),
                                  tr
                                  ("This will delete current log"),
                                  QMessageBox::Yes,
                                  QMessageBox::No) == QMessageBox::Yes) {
            f.remove();
        }
    }
694
}