Compare commits

..

No commits in common. "master" and "v0.1.9" have entirely different histories.

17 changed files with 144 additions and 277 deletions

View File

@ -1,12 +1,10 @@
#include "audio_support.h"
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#include <QSound>
extern void play_audio(const app_settings::selected_audio& item)
{
// Play audio
if (item.name != Audio_Empty && item.name != Audio_Custom)
{
// Find bundled audio
@ -18,7 +16,4 @@ extern void play_audio(const app_settings::selected_audio& item)
if (item.name == Audio_Custom && !item.path.isEmpty())
QSound::play(item.path);
}
#else
extern void play_audio(const app_settings::selected_audio& item)
{}
#endif

View File

@ -64,17 +64,6 @@ static fs::path autostart_path()
void autostart::enable(const std::string& path_to_me)
{
// Ensure autostart directory exists at all
if (!fs::exists(autostart_dir()))
{
std::error_code ec;
if (!fs::create_directory(autostart_dir(), ec))
{
qDebug() << "Failed to create autostart directory. Error: " << QString::fromStdString(ec.message());
return;
}
}
// Put .desktop file to ~/.config/autostart
std::ofstream ofs(autostart_path());
if (ofs.is_open())

View File

@ -4,10 +4,10 @@
// App version
#define QBREAK_VERSION_MAJOR 0
#define QBREAK_VERSION_MINOR 1
#define QBREAK_VERSION_SUFFIX 17
#define QBREAK_VERSION_SUFFIX 9
// How often UI is updated - interval in seconds
#define INTERVAL_UPDATE_UI (10)
#define INTERVAL_UPDATE_UI (60)
// How often progress bar is updated - interval in milliseconds
#define INTERVAL_UPDATE_PROGRESS (1000)

View File

@ -6,9 +6,9 @@
#if defined(TARGET_LINUX)
#include <QObject>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusReply>
#include <QtDBus/QDBusInterface>
#include <QDBusConnection>
#include <QDBusReply>
#include <QDBusInterface>
// Thanks to https://stackoverflow.com/questions/222606/detecting-keyboard-mouse-activity-in-linux
@ -210,7 +210,6 @@ int get_idle_time_kde_wayland()
#endif
static bool Warning_X11InWayland = false;
int get_idle_time_dynamically()
{
const char* wl_display = std::getenv("WAYLAND_DISPLAY");
@ -236,14 +235,8 @@ int get_idle_time_dynamically()
#else
// Restrict to X11
if (wl_display)
{
// One time error message
if (!Warning_X11InWayland) {
qDebug() << "Wayland is found, but app built for X11 only. Idle tracking is not supported.";
Warning_X11InWayland = true;
}
return 0;
}
return get_idle_time_x11();
#endif
}

View File

@ -6,67 +6,17 @@
#include <QCommandLineParser>
#include <QTranslator>
#include <QFileInfo>
#include <QDateTime>
#if defined(TARGET_LINUX)
# include <syslog.h>
#endif
// Handler for Qt log messages that sends output to syslog as well as standard error.
void SyslogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
Q_UNUSED(context)
std::string timePrefix = QDateTime::currentDateTime().toString().toStdString();
QByteArray localMsg = msg.toLocal8Bit();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "debug: %s %s\n", timePrefix.c_str(), localMsg.constData());
#if defined(TARGET_LINUX)
syslog(LOG_DEBUG, "debug: %s", localMsg.constData());
#endif
break;
case QtInfoMsg:
fprintf(stderr, "info: %s %s\n", timePrefix.c_str(), localMsg.constData());
#if defined(TARGET_LINUX)
syslog(LOG_INFO, "info: %s", localMsg.constData());
#endif
break;
case QtWarningMsg:
fprintf(stderr, "warning: %s %s\n", timePrefix.c_str(), localMsg.constData());
#if defined(TARGET_LINUX)
syslog(LOG_WARNING, "warning: %s", localMsg.constData());
#endif
break;
case QtCriticalMsg:
fprintf(stderr, "critical: %s %s\n", timePrefix.c_str(), localMsg.constData());
#if defined(TARGET_LINUX)
syslog(LOG_CRIT, "critical: %s", localMsg.constData());
#endif
break;
case QtFatalMsg:
fprintf(stderr, "fatal: %s %s\n", timePrefix.c_str(), localMsg.constData());
#if defined(TARGET_LINUX)
syslog(LOG_ALERT, "fatal: %s", localMsg.constData());
#endif
abort();
}
}
int main(int argc, char *argv[])
{
RunGuard guard("QBreak app - runguard");
RunGuard guard("adjfaifaif");
if ( !guard.tryToRun() )
return 0;
// Needed to enable logging to syslog or journald.
qInstallMessageHandler(SyslogMessageHandler);
QApplication app(argc, argv);
QCoreApplication::setOrganizationName("voipobjects.com");
QCoreApplication::setOrganizationDomain("voipobjects.com");
QCoreApplication::setOrganizationName("qbreak.com");
QCoreApplication::setOrganizationDomain("qbreak.com");
QCoreApplication::setApplicationName("QBreak");
QTranslator translator;
@ -77,9 +27,19 @@ int main(int argc, char *argv[])
app.setQuitOnLastWindowClosed(false);
QCommandLineParser parser;
QCommandLineOption test_1(QStringList() << "t1" << "test_1");
parser.addOption(test_1);
// Run app with break intervals & duration 60 seconds.
// This should trigger the notification in 30 seconds before breaks.
QCommandLineOption test_2(QStringList() << "t2" << "test_2");
parser.addOption(test_2);
parser.process(app);
// Put itself into app menu
auto exe_path = QCoreApplication::applicationFilePath();
auto exe_path = QFileInfo(QCoreApplication::arguments().front()).absoluteFilePath();
const char* appimage = std::getenv("APPIMAGE");
if (appimage != nullptr)
exe_path = appimage;
@ -90,6 +50,12 @@ int main(int argc, char *argv[])
MainWindow w;
w.hide();
if (parser.isSet(test_1))
w.test_1();
else
if (parser.isSet(test_2))
w.test_2();
int retcode = app.exec();
return retcode;

View File

@ -12,46 +12,20 @@
#include <QAction>
#include <QSettings>
#include <QDebug>
#include <QDesktopWidget>
#include <QSvgGenerator>
#include <QPalette>
#include <QScreen>
#include <QWindow>
#include <QFileInfo>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QDateTime>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
# include <QDesktopWidget>
# include <QSound>
#endif
#include <string>
static void dispatchToMainThread(std::function<void()> callback, std::chrono::milliseconds delay = std::chrono::milliseconds(0))
{
// any thread
QTimer* timer = new QTimer();
timer->moveToThread(qApp->thread());
timer->setSingleShot(true);
timer->setInterval(delay);
QObject::connect(timer, &QTimer::timeout, [=]()
{
// main thread
callback();
timer->deleteLater();
});
QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0));
}
#include <QSound>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
// Defer init call for 100 ms - attempt to resolve the problem with non working setTooltip()
dispatchToMainThread([this](){init();}, std::chrono::milliseconds(100));
init();
}
MainWindow::~MainWindow()
@ -143,43 +117,10 @@ void MainWindow::init()
shiftTo(AppState::Counting);
}
static int str_to_seconds(const QString& s)
{
if (s.isEmpty())
throw std::runtime_error("Bad parameter value.");
if (s.back() == 'h')
return s.left(s.size()-1).toInt() * 3600;
if (s.back() == 'm')
return s.left(s.size()-1).toInt() * 60;
if (s.back() == 's')
return s.left(s.size()-1).toInt();
if (s.back().isDigit())
return s.toInt();
throw std::runtime_error("Bad parameter value");
}
void MainWindow::loadConfig()
{
mAppConfig = app_settings::load();
// Check for command line options
QCommandLineParser parser;
QCommandLineOption
param_break("break-duration", "Long break duration.", "break-duration"),
param_work("work-duration", "Work time duration.", "work-duration"),
param_idle("idle-timeout", "Idle timeout.", "idle-timeout");
parser.addOptions({param_break, param_work, param_idle});
parser.process(*QApplication::instance());
if (parser.isSet(param_break))
mAppConfig.longbreak_length = str_to_seconds(parser.value(param_break));
if (parser.isSet(param_work))
mAppConfig.longbreak_interval = str_to_seconds(parser.value(param_work));
if (parser.isSet(param_idle))
mAppConfig.idle_timeout = str_to_seconds(parser.value(param_idle));
app_settings settings;
mAppConfig = settings.load();
}
void MainWindow::applyConfig()
@ -212,6 +153,39 @@ void MainWindow::applyConfig()
autostart::disable();
}
void MainWindow::schedule()
{
;
}
void MainWindow::test_1()
{
// 10 seconds test break
mAppConfig.longbreak_length = 10;
mAppConfig.longbreak_postpone_interval = 10;
mAppConfig.longbreak_interval = 10;
mAppConfig.window_on_top = true;
mAppConfig.verbose = true;
applyConfig();
onUpdateUI();
shiftTo(AppState::Break);
}
void MainWindow::test_2()
{
// 60 seconds test break
mAppConfig.longbreak_length = 60;
mAppConfig.longbreak_postpone_interval = 60;
mAppConfig.longbreak_interval = 60;
mAppConfig.window_on_top = true;
mAppConfig.verbose = true;
applyConfig();
onUpdateUI();
}
void MainWindow::showMe()
{
QScreen* screen = nullptr;
@ -235,13 +209,8 @@ void MainWindow::showMe()
// qDebug() << "Window moved to screen " << screen->name() + " / " + screen->model() + " " + screen->manufacturer();
}
}
// else
// qDebug() << "Screen not found!";
#if defined(DEBUG)
showFullScreen();
//showMaximized();
#else
#if !defined(DEBUG)
showFullScreen();
#endif
}
@ -286,11 +255,10 @@ void MainWindow::createTrayIcon()
mTrayIcon->setContextMenu(menu);
mTrayIcon->setIcon(getTrayIcon());
// mTrayIcon->setToolTip(AppName);
mTrayIcon->setToolTip(AppName);
mTrayIcon->show();
connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(onTrayIconActivated(QSystemTrayIcon::ActivationReason)));
mTrayIcon->show();
}
static int msec2min(int msec)
@ -315,12 +283,13 @@ QString state2str(AppState state)
return QString();
}
void MainWindow::shiftTo(AppState newState)
{
if (newState == mState)
return;
#if defined(DEBUG)
qDebug() << state2str(mState) << " -> " << state2str(newState);
#endif
switch (newState)
{
@ -348,75 +317,59 @@ void MainWindow::shiftTo(AppState newState)
}
mState = newState;
// Run deferred in main UI thread
dispatchToMainThread([this](){
onUpdateUI();
});
onUpdateUI();
}
void MainWindow::onUpdateUI()
{
qDebug() << "UI timer fired.";
void MainWindow::onUpdateUI() {
int idle_milliseconds = 0;
switch (mState) {
case AppState::None:
// Do nothing, app is not started
break;
int idle_milliseconds = 0;
switch (mState) {
case AppState::None:
// Do nothing, app is not started
break;
case AppState::Idle:
// Detected idle, don't count this time as working
// But check - maybe idle is over
idle_milliseconds = get_idle_time_dynamically();
if (idle_milliseconds < mAppConfig.idle_timeout * 60 * 1000) {
shiftTo(AppState::Counting);
return;
}
break;
case AppState::Idle:
// Detected idle, don't count this time as working
// But check - maybe idle is over
idle_milliseconds = get_idle_time_dynamically();
qDebug() << "Idle found: " << idle_milliseconds / 1000 << "s";
case AppState::Break:
// Break is active
if (mTrayIcon)
mTrayIcon->setToolTip(QString());
break;
if (idle_milliseconds < mAppConfig.idle_timeout * 1000)
{
shiftTo(AppState::Counting);
return;
}
break;
case AppState::Break:
// Break is active
if (mTrayIcon)
mTrayIcon->setToolTip(QString());
break;
case AppState::Counting:
// Working, break is closing
// Check maybe it is idle ?
if (!mIdleStart && mAppConfig.idle_timeout)
{
idle_milliseconds = get_idle_time_dynamically();
qDebug() << "Idle found: " << idle_milliseconds / 1000 << "s";
if (idle_milliseconds >= mAppConfig.idle_timeout * 1000)
{
shiftTo(AppState::Idle);
return;
}
}
// Update tray icon
if (mTrayIcon) {
auto remaining_milliseconds = mBreakStartTimer->remainingTime();
if (remaining_milliseconds < 60000)
mTrayIcon->setToolTip(
tr("Less than a minute left until the next break."));
else
mTrayIcon->setToolTip(
tr("There are %1 minutes left until the next break.")
.arg(msec2min(remaining_milliseconds)));
}
else
qDebug() << "No tray icon available.";
break;
case AppState::Counting:
// Working, break is closing
// Check maybe it is idle ?
if (!mIdleStart && mAppConfig.idle_timeout) {
idle_milliseconds = get_idle_time_dynamically();
if (idle_milliseconds >= mAppConfig.idle_timeout * 60 * 1000) {
shiftTo(AppState::Idle);
return;
}
}
ui->mSkipButton->setVisible(mPostponeCount > 0);
// Update tray icon
if (mTrayIcon) {
auto remaining_milliseconds = mBreakStartTimer->remainingTime();
if (remaining_milliseconds < 60000)
mTrayIcon->setToolTip(
tr("Less than a minute left until the next break."));
else
mTrayIcon->setToolTip(
tr("There are %1 minutes left until the next break.")
.arg(msec2min(remaining_milliseconds)));
}
break;
}
ui->mSkipButton->setVisible(mPostponeCount > 0);
}
void MainWindow::onLongBreakNotify()
@ -424,7 +377,7 @@ void MainWindow::onLongBreakNotify()
mTrayIcon->showMessage(tr("New break"),
tr("New break will start in %1 secs").arg(Default_Notify_Length),
getAppIcon(),
INTERVAL_NOTIFICATION * 1000);
INTERVAL_NOTIFICATION);
}
void MainWindow::onLongBreakStart()
@ -477,7 +430,7 @@ void MainWindow::onLongBreakEnd()
}
// Play selected audio. When break is postponed - audio is not played
if (0 == mPostponeCount)
if (!mPostponeCount)
play_audio(mAppConfig.play_audio);
// Run script
@ -519,10 +472,7 @@ void MainWindow::onProgress()
if (remaining < 0)
remaining = 0;
auto text = QString("Remaining: ") + secondsToText(remaining);
ui->mRemainingLabel->setText(text);
if (mTrayIcon)
mTrayIcon->setToolTip(text);
ui->mRemainingLabel->setText(QString("Remaining: ") + secondsToText(remaining));
if (percents > 100)
{
@ -554,7 +504,7 @@ void MainWindow::onIdleStart()
// Stop main & notify timers
mBreakStartTimer->stop();
mBreakNotifyTimer->stop();
qDebug() << "Stop main and notify timers. Remaining time is " << mRemainingWorkInterval.value() << "s";
qDebug() << "Stop main and notify timers.";
}
void MainWindow::onIdleEnd()
@ -567,14 +517,9 @@ void MainWindow::onIdleEnd()
// Update timer(s) duration
if (mRemainingWorkInterval)
{
qDebug() << "Reset main timer to " << *mRemainingWorkInterval << "s";
mBreakStartTimer->setInterval(std::chrono::seconds(*mRemainingWorkInterval));
if (mRemainingWorkInterval > INTERVAL_NOTIFICATION)
{
mBreakNotifyTimer->setInterval(std::chrono::seconds(*mRemainingWorkInterval - INTERVAL_NOTIFICATION));
qDebug() << "Reset notify timer to " << *mRemainingWorkInterval - INTERVAL_NOTIFICATION << "s";
}
mRemainingWorkInterval.reset();
}
else

View File

@ -53,18 +53,19 @@ private:
std::optional<int> mRemainingWorkInterval;
app_settings::config mAppConfig;
int mPostponeCount = 0;
int mPostponeCount;
// Time when idle was started
std::optional<std::chrono::steady_clock::time_point> mIdleStart;
int mLastIdleMilliseconds = 0;
int mLastIdleMilliseconds;
AppState mState = AppState::None;
void init();
void loadConfig();
void applyConfig();
void schedule();
void createTrayIcon();
void showMe();
void hideMe();

View File

@ -1,7 +1,8 @@
QT += core gui svg multimedia widgets dbus
QT += core gui svg multimedia
CONFIG += debug_and_release console
CONFIG += debug_and_release
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets dbus
CONFIG += c++17 lrelease embed_translations
@ -53,9 +54,10 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
unix:LIBS += -L/usr/X11R6/lib/ \
-lX11 -lXext -lXss -ldl
Debug: DEFINES += DEBUG
Release: DEFINES += QT_NO_DEBUG_OUTPUT
debug: DEFINES += DEBUG
# When using wayland:
# unix:LIBS += -L/usr/local/lib \
# -lwayland-client-unstable++ -lwayland-client-extra++ -lwayland-client++

View File

@ -27,8 +27,8 @@ const QString Primary_Monitor = "[Primary]";
// Default behavior is not play any audio on break end.
const QString Audio_Empty = "None";
const QString Audio_Retro = "Retro";
const QString Audio_Gagarin = "Gagarin";
const QString Audio_Default_1 = "Default 1";
const QString Audio_Default_2 = "Default 2";
const QString Audio_Custom = "Custom...";
struct audio_item
@ -38,8 +38,8 @@ struct audio_item
};
const std::map<QString, QString> AudioMap {
{Audio_Retro, ":/assets/sound/alarm-retro.wav"},
{Audio_Gagarin, ":/assets/sound/alarm-poehali.wav"},
{Audio_Default_1, ":/assets/sound/alarm-retro.wav"},
{Audio_Default_2, ":/assets/sound/alarm-poehali.wav"},
};
@ -71,7 +71,7 @@ public:
selected_audio play_audio;
QString script_on_break_finish;
// Zero means "idle is not tracked". Value in seconds.
// Zero means "idle is not tracked"
int idle_timeout = Default_Idle_Timeout;
};

View File

@ -55,7 +55,7 @@ void SettingsDialog::init()
ui->mPreferredMonitorCombobox->setCurrentIndex(found_idx);
// Fill audio combo box
auto audios = {Audio_Empty, Audio_Retro, Audio_Gagarin, Audio_Custom};
auto audios = {Audio_Empty, Audio_Default_1, Audio_Default_2, Audio_Custom};
ui->mAudioComboBox->addItems(audios);
mCustomAudioIdx = audios.size() - 1;
@ -70,7 +70,7 @@ void SettingsDialog::init()
connect(ui->mAudioComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onAudioIndexChanged(int)));
// Idle timeout
ui->mIdleTimeoutSpinbox->setValue(c.idle_timeout / 60);
ui->mIdleTimeoutSpinbox->setValue(c.idle_timeout);
mSkipAudioChangeEvent = false;
}
@ -90,7 +90,7 @@ void SettingsDialog::accept()
if (c.play_audio.name == Audio_Custom)
c.play_audio.path = mCustomAudioPath;
c.idle_timeout = ui->mIdleTimeoutSpinbox->value() * 60;
c.idle_timeout = ui->mIdleTimeoutSpinbox->value();
app_settings::save(c);
emit accepted();

View File

@ -131,7 +131,7 @@
<item row="9" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Shell command to run when break finish</string>
<string>Command to run when break finish</string>
</property>
</widget>
</item>

View File

@ -2,11 +2,11 @@
# I use this script on two different hosts so there are logic to find proper Qt installation
export QT_HOME=/home/$USER/tools/qt/5.15.2/gcc_64
export QT_HOME=/home/$USER/qt5.15/5.15.2/gcc_64
if [ ! -d "$QT_HOME" ] ; then
export QT_HOME=/home/$USER/tools/qt/5.15.2/gcc_64
export QT_HOME=/home/$USER/qt5.15/5.15.2/gcc_64
fi
# Build .appimage
/usr/bin/python3 build_qbreak.py release
/usr/bin/python3 build_qbreak.py

View File

@ -2,9 +2,9 @@
# I use this script on two different hosts so there are logic to find proper Qt installation
export QT_HOME=/home/$USER/tools/qt/5.15.2/gcc_64
export QT_HOME=/home/$USER/qt5.15/5.15.2/gcc_64
if [ ! -d "$QT_HOME" ] ; then
export QT_HOME=/home/$USER/qt/5.15.2/gcc_64
export QT_HOME=/home/$USER/qt5.15/5.15.2/gcc_64
fi
# Build .appimage

View File

@ -1,12 +0,0 @@
#!/bin/bash
# I use this script on two different hosts so there are logic to find proper Qt installation
export QT_HOME=/home/$USER/tools/qt/6.8.0/gcc_64
if [ ! -d "$QT_HOME" ] ; then
export QT_HOME=/home/$USER/tools/qt/6.8.0/gcc_64
fi
# Build .appimage
/usr/bin/python3 build_qbreak.py release

View File

@ -1,12 +0,0 @@
#!/bin/bash
# I use this script on two different hosts so there are logic to find proper Qt installation
export QT_HOME=/home/$USER/tools/qt/6.8.0/gcc_64
if [ ! -d "$QT_HOME" ] ; then
export QT_HOME=/home/$USER/tools/qt/6.8.0/gcc_64
fi
# Build .appimage
/usr/bin/python3 build_qbreak.py debug

View File

@ -84,7 +84,7 @@ if platform.system() == 'Linux':
'-qmake=' + os.environ['QT_HOME'] + '/bin/qmake',
'-unsupported-allow-new-glibc',
#'-no-translations',
'-extra-plugins=iconengines,platformthemes/libqgtk3.so,platforms/libqxcb.so'
'-extra-plugins=iconengines,platformthemes/libqgtk3.so'
]
desktop_path = 'appimage_dir/usr/share/applications/qbreak.desktop'

Binary file not shown.