Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c48fe5f38 | |||
| 61ef94b474 | |||
| aba5e21271 | |||
| 76a63b29c2 | |||
| 37840dfdb1 | |||
| 9ff378d5e0 | |||
| 317b7097f6 | |||
| 64658a7778 | |||
| 447f33bf94 | |||
| 6200504627 | |||
| 6af94a3a6f | |||
| 305ee12496 | |||
| 3f8aa69945 | |||
| 21046d9c35 | |||
| 6c495b6c7f | |||
| 4e51dc85e8 | |||
| 6b88c48c3a | |||
| 266d6ddf09 | |||
| fff67bbedb | |||
|
|
f1ca3ed0ed | ||
| eed9e13e91 | |||
| 24b470cfc6 | |||
| ee486da79d | |||
|
|
dc653f24e6 | ||
|
|
56ec61c92e | ||
|
|
f6762c5946 | ||
| f245f5aef0 | |||
| 6cf8d86ffa | |||
|
|
8c04eb45d2 | ||
| 859c1709e2 | |||
| fc2c6e2b99 | |||
| a0de1f5944 | |||
| 7621a79dbe | |||
|
|
7a88575900 | ||
|
|
ec7c293366 | ||
|
|
36ea53b1df | ||
| d2d899280d |
@ -13,6 +13,9 @@ AboutDlg::AboutDlg(QWidget *parent) :
|
|||||||
.arg(QBREAK_VERSION_MAJOR)
|
.arg(QBREAK_VERSION_MAJOR)
|
||||||
.arg(QBREAK_VERSION_MINOR)
|
.arg(QBREAK_VERSION_MINOR)
|
||||||
.arg(QBREAK_VERSION_SUFFIX);
|
.arg(QBREAK_VERSION_SUFFIX);
|
||||||
|
#if defined(DEBUG)
|
||||||
|
version_text += ". Debug build.";
|
||||||
|
#endif
|
||||||
ui->mAppVersionLabel->setText(version_text);
|
ui->mAppVersionLabel->setText(version_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
URL for used image:
|
|
||||||
https://thenounproject.com/icon/coffee-break-537907/
|
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="m228 72c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm-782.25 360c-11.797 1.1133-21.801 12.148-21.75 24 0 304.82 77.43 453.77 174.75 624h-126.75c-13.297 0-24 10.703-24 24s10.703 24 24 24h864c13.297 0 24-10.703 24-24s-10.703-24-24-24h-126.75c25.375-44.387 49.617-87.379 71.25-132h115.5c46.531 0 84-37.469 84-84v-264c0-46.531-37.465-84-84-84h-13.125c0.69922-19.352 1.125-39.258 1.125-60 0-12.566-11.434-24-24-24h-912c-0.75-0.035156-1.5-0.035156-2.25 0zm27 48h862.5c-4.168 292.51-80.758 424.66-181.5 600h-499.5c-100.74-175.34-177.33-307.49-181.5-600zm907.5 84h15.75c24.383 0 36 11.621 36 36v264c0 24.383-11.617 36-36 36h-93.75c39.703-92.473 68.336-195.91 78-336z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB |
@ -1,3 +1,4 @@
|
|||||||
URL: https://ru.seaicons.com/89980/
|
URL: https://ru.seaicons.com/89980/
|
||||||
License: https://creativecommons.org/licenses/by-sa/4.0/
|
License: https://creativecommons.org/licenses/by-sa/4.0/
|
||||||
|
|
||||||
|
Another URL: https://github.com/alecive/FlatWoken
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB |
@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="1200pt" height="1200pt" version="1.1" viewBox="0 0 1200 1200" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="m228 72c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm324 0c-9.332 0-14.488 4.707-18.375 9.375-5.1367 6.5391-7.332 15.398-4.125 23.25 18.855 47.793 10.898 59.164 0.375 85.5s-22.758 65.922-0.375 129.75c3.3164 12.883 18.977 20.988 31.5 16.5s19.121-20.566 13.5-32.625c-19.188-54.711-10.637-70.312-0.375-96s23.09-63.176 0.375-120.75c-3.4375-9.0625-12.848-14.941-22.5-15zm-782.25 360c-11.797 1.1133-21.801 12.148-21.75 24 0 304.82 77.43 453.77 174.75 624h-126.75c-13.297 0-24 10.703-24 24s10.703 24 24 24h864c13.297 0 24-10.703 24-24s-10.703-24-24-24h-126.75c25.375-44.387 49.617-87.379 71.25-132h115.5c46.531 0 84-37.469 84-84v-264c0-46.531-37.465-84-84-84h-13.125c0.69922-19.352 1.125-39.258 1.125-60 0-12.566-11.434-24-24-24h-912c-0.75-0.035156-1.5-0.035156-2.25 0zm27 48h862.5c-4.168 292.51-80.758 424.66-181.5 600h-499.5c-100.74-175.34-177.33-307.49-181.5-600zm907.5 84h15.75c24.383 0 36 11.621 36 36v264c0 24.383-11.617 36-36 36h-93.75c39.703-92.473 68.336-195.91 78-336z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,10 +1,12 @@
|
|||||||
#include "audio_support.h"
|
#include "audio_support.h"
|
||||||
|
|
||||||
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
#include <QSound>
|
#include <QSound>
|
||||||
|
|
||||||
extern void play_audio(const app_settings::selected_audio& item)
|
extern void play_audio(const app_settings::selected_audio& item)
|
||||||
{
|
{
|
||||||
// Play audio
|
// Play audio
|
||||||
|
|
||||||
if (item.name != Audio_Empty && item.name != Audio_Custom)
|
if (item.name != Audio_Empty && item.name != Audio_Custom)
|
||||||
{
|
{
|
||||||
// Find bundled audio
|
// Find bundled audio
|
||||||
@ -16,4 +18,7 @@ extern void play_audio(const app_settings::selected_audio& item)
|
|||||||
if (item.name == Audio_Custom && !item.path.isEmpty())
|
if (item.name == Audio_Custom && !item.path.isEmpty())
|
||||||
QSound::play(item.path);
|
QSound::play(item.path);
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
extern void play_audio(const app_settings::selected_audio& item)
|
||||||
|
{}
|
||||||
|
#endif
|
||||||
|
|||||||
@ -64,6 +64,17 @@ static fs::path autostart_path()
|
|||||||
|
|
||||||
void autostart::enable(const std::string& path_to_me)
|
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
|
// Put .desktop file to ~/.config/autostart
|
||||||
std::ofstream ofs(autostart_path());
|
std::ofstream ofs(autostart_path());
|
||||||
if (ofs.is_open())
|
if (ofs.is_open())
|
||||||
@ -98,8 +109,21 @@ static fs::path appmenu_install_dir()
|
|||||||
|
|
||||||
void appmenu::install(const std::string& path_to_me)
|
void appmenu::install(const std::string& path_to_me)
|
||||||
{
|
{
|
||||||
|
auto path_to_desktop = appmenu_install_dir() / QBREAK_DESKTOP_NAME;
|
||||||
|
|
||||||
|
// Check if app is installed already.
|
||||||
|
// The code below checks for path to app; as this app is distributed as .AppImage with version numbers - every new version will trigger desktop file rewriting
|
||||||
|
std::ifstream ifs(path_to_desktop);
|
||||||
|
std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
|
||||||
|
if (content.find(path_to_me) != std::string::npos)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove old one
|
||||||
|
std::string uninstall_cmd = "/usr/bin/xdg-desktop-menu uninstall --novendor " + path_to_desktop.string();
|
||||||
|
system(uninstall_cmd.c_str());
|
||||||
|
|
||||||
// Put .desktop file to ~/.config/autostart
|
// Put .desktop file to ~/.config/autostart
|
||||||
std::ofstream ofs(appmenu_install_dir() / QBREAK_DESKTOP_NAME);
|
std::ofstream ofs(path_to_desktop);
|
||||||
if (ofs.is_open())
|
if (ofs.is_open())
|
||||||
{
|
{
|
||||||
ofs << fixup_desktop_file(read_desktop_file(), path_to_me);
|
ofs << fixup_desktop_file(read_desktop_file(), path_to_me);
|
||||||
@ -125,6 +149,9 @@ void appmenu::install(const std::string& path_to_me)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string install_cmd = "/usr/bin/xdg-desktop-menu install --novendor " + path_to_desktop.string();
|
||||||
|
system(install_cmd.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void appmenu::uninstall()
|
void appmenu::uninstall()
|
||||||
|
|||||||
@ -4,15 +4,15 @@
|
|||||||
// App version
|
// App version
|
||||||
#define QBREAK_VERSION_MAJOR 0
|
#define QBREAK_VERSION_MAJOR 0
|
||||||
#define QBREAK_VERSION_MINOR 1
|
#define QBREAK_VERSION_MINOR 1
|
||||||
#define QBREAK_VERSION_SUFFIX 0
|
#define QBREAK_VERSION_SUFFIX 17
|
||||||
|
|
||||||
// How often UI is updated - interval in seconds
|
// How often UI is updated - interval in seconds
|
||||||
#define INTERVAL_UPDATE_UI (60)
|
#define INTERVAL_UPDATE_UI (10)
|
||||||
|
|
||||||
// How often progress bar is updated - interval in milliseconds
|
// How often progress bar is updated - interval in milliseconds
|
||||||
#define INTERVAL_UPDATE_PROGRESS (1000)
|
#define INTERVAL_UPDATE_PROGRESS (1000)
|
||||||
|
|
||||||
// How long notification is shown - in milliseconds
|
// How long notification is shown - in seconds
|
||||||
#define INTERVAL_NOTIFICATION (30000)
|
#define INTERVAL_NOTIFICATION (30)
|
||||||
|
|
||||||
#endif // CONFIG_H
|
#endif // CONFIG_H
|
||||||
|
|||||||
296
app/idle_tracking.cpp
Normal file
296
app/idle_tracking.cpp
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
#include "idle_tracking.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#if defined(TARGET_LINUX)
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QtDBus/QDBusConnection>
|
||||||
|
#include <QtDBus/QDBusReply>
|
||||||
|
#include <QtDBus/QDBusInterface>
|
||||||
|
|
||||||
|
// Thanks to https://stackoverflow.com/questions/222606/detecting-keyboard-mouse-activity-in-linux
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
|
||||||
|
// This requires sudo apt install libxss-dev
|
||||||
|
#include <X11/extensions/scrnsaver.h> // This can require libxss-dev to be installed
|
||||||
|
#include <dlfcn.h>
|
||||||
|
// #include <qmetatype.h>
|
||||||
|
// #include <QDBusConnection>
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Prototype from stackoverflow
|
||||||
|
int get_idle_time()
|
||||||
|
{
|
||||||
|
time_t idle_time;
|
||||||
|
static XScreenSaverInfo *mit_info;
|
||||||
|
Display *display;
|
||||||
|
int screen;
|
||||||
|
|
||||||
|
mit_info = XScreenSaverAllocInfo();
|
||||||
|
if ((display = XOpenDisplay(NULL)) == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
screen = DefaultScreen(display);
|
||||||
|
XScreenSaverQueryInfo(display, RootWindow(display, screen), mit_info);
|
||||||
|
idle_time = (mit_info->idle);
|
||||||
|
XFree(mit_info);
|
||||||
|
XCloseDisplay(display);
|
||||||
|
|
||||||
|
return idle_time;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
int get_idle_time_x11()
|
||||||
|
{
|
||||||
|
void* lib_xss = dlopen("libXss.so", RTLD_LAZY);
|
||||||
|
if (!lib_xss)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
void* lib_x11 = dlopen("libX11.so", RTLD_LAZY);
|
||||||
|
if (!lib_x11)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
typedef XScreenSaverInfo* (*xss_alloc_info)(void);
|
||||||
|
xss_alloc_info alloc_info = (xss_alloc_info)dlsym(lib_xss, "XScreenSaverAllocInfo");
|
||||||
|
|
||||||
|
typedef Display* (*x11_open_display)(void*);
|
||||||
|
x11_open_display open_display = (x11_open_display)dlsym(lib_x11, "XOpenDisplay");
|
||||||
|
|
||||||
|
|
||||||
|
typedef Status (*xss_query_info)( Display* /* display */,
|
||||||
|
Drawable /* drawable */,
|
||||||
|
XScreenSaverInfo* /* info */);
|
||||||
|
xss_query_info query_info = (xss_query_info)dlsym(lib_xss, "XScreenSaverQueryInfo");
|
||||||
|
|
||||||
|
typedef int (*x11_free)(void*);
|
||||||
|
x11_free free_mem = (x11_free)dlsym(lib_x11, "XFree");
|
||||||
|
|
||||||
|
typedef int (*x11_close_display)(Display* display);
|
||||||
|
x11_close_display close_display = (x11_close_display)dlsym(lib_x11, "XCloseDisplay");
|
||||||
|
|
||||||
|
|
||||||
|
time_t idle_time;
|
||||||
|
static XScreenSaverInfo *mit_info;
|
||||||
|
Display *display;
|
||||||
|
int screen;
|
||||||
|
|
||||||
|
mit_info = alloc_info();
|
||||||
|
if ((display = open_display(NULL)) == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
screen = DefaultScreen(display);
|
||||||
|
query_info(display, RootWindow(display, screen), mit_info);
|
||||||
|
idle_time = (mit_info->idle);
|
||||||
|
free_mem(mit_info);
|
||||||
|
close_display(display);
|
||||||
|
|
||||||
|
dlclose(lib_xss);
|
||||||
|
dlclose(lib_x11);
|
||||||
|
return idle_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_idle_time_gnome()
|
||||||
|
{
|
||||||
|
auto bus = QDBusConnection::sessionBus();
|
||||||
|
if (!bus.isConnected())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
QDBusInterface interface( "org.gnome.Mutter.IdleMonitor",
|
||||||
|
"/org/gnome/Mutter/IdleMonitor/Core",
|
||||||
|
"org.gnome.Mutter.IdleMonitor");
|
||||||
|
|
||||||
|
QDBusReply<int> reply = interface.call("GetIdletime");
|
||||||
|
|
||||||
|
return reply.isValid() ? reply.value() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(USE_WAYLAND)
|
||||||
|
#include <wayland-client-protocol-unstable.hpp>
|
||||||
|
|
||||||
|
class kde_idle_detector
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
wayland::seat_t seat;
|
||||||
|
wayland::display_t d;
|
||||||
|
wayland::org_kde_kwin_idle_t idle;
|
||||||
|
wayland::org_kde_kwin_idle_timeout_t idle_timer;
|
||||||
|
|
||||||
|
uint64_t idle_start = 0;
|
||||||
|
uint64_t idle_finish = 0;
|
||||||
|
bool active = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
kde_idle_detector()
|
||||||
|
{}
|
||||||
|
~kde_idle_detector()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Idle timeout is in msec
|
||||||
|
void start(int idle_timeout)
|
||||||
|
{
|
||||||
|
if (active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto registry = d.get_registry();
|
||||||
|
registry.on_global() = [&] (uint32_t name, const std::string& interface, uint32_t version)
|
||||||
|
{
|
||||||
|
if (interface == wayland::seat_t::interface_name)
|
||||||
|
registry.bind(name, this->seat, version);
|
||||||
|
else
|
||||||
|
if (interface == wayland::org_kde_kwin_idle_t::interface_name)
|
||||||
|
registry.bind(name, this->idle, version);
|
||||||
|
};
|
||||||
|
d.roundtrip();
|
||||||
|
|
||||||
|
|
||||||
|
bool has_keyboard = false, has_pointer = false;
|
||||||
|
seat.on_capabilities() = [&] (const wayland::seat_capability& capability)
|
||||||
|
{
|
||||||
|
has_keyboard = capability & wayland::seat_capability::keyboard;
|
||||||
|
has_pointer = capability & wayland::seat_capability::pointer;
|
||||||
|
};
|
||||||
|
d.roundtrip();
|
||||||
|
|
||||||
|
idle_timer = idle.get_idle_timeout(seat, idle_timeout);
|
||||||
|
idle_timer.on_idle() = [&]()
|
||||||
|
{
|
||||||
|
idle_start = ::time(nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
idle_timer.on_resumed() = [&]()
|
||||||
|
{
|
||||||
|
idle_finish = ::time(nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop()
|
||||||
|
{
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
active = false;
|
||||||
|
idle_timer.release();
|
||||||
|
seat.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return idle time in microseconds
|
||||||
|
int get_idle_time() const
|
||||||
|
{
|
||||||
|
if (idle_start > idle_finish)
|
||||||
|
{
|
||||||
|
return (::time(nullptr) - idle_start) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
kde_idle_detector kde_idle;
|
||||||
|
int get_idle_time_kde_wayland()
|
||||||
|
{
|
||||||
|
// Ensure idle detector runs
|
||||||
|
kde_idle.start(1);
|
||||||
|
|
||||||
|
return kde_idle.get_idle_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static bool Warning_X11InWayland = false;
|
||||||
|
int get_idle_time_dynamically()
|
||||||
|
{
|
||||||
|
const char* wl_display = std::getenv("WAYLAND_DISPLAY");
|
||||||
|
// const char* x11_display = std::getenv("DISPLAY");
|
||||||
|
|
||||||
|
#if defined(USE_WAYLAND)
|
||||||
|
if (wl_display)
|
||||||
|
{
|
||||||
|
const char* desktop_name = std::getenv("XDG_SESSION_DESKTOP");
|
||||||
|
if (!desktop_name)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (strcmp(desktop_name, "KDE") == 0)
|
||||||
|
return get_idle_time_kde_wayland();
|
||||||
|
else
|
||||||
|
if (strcmp(desktop_name, "GNOME") == 0)
|
||||||
|
return get_idle_time_gnome();
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return get_idle_time_x11();
|
||||||
|
#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
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(TARGET_WINDOWS)
|
||||||
|
|
||||||
|
// To handle Windows case later
|
||||||
|
// https://stackoverflow.com/questions/8820615/how-to-check-in-c-if-the-system-is-active
|
||||||
|
|
||||||
|
/*
|
||||||
|
#define WINDOWS_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// do something after 10 minutes of user inactivity
|
||||||
|
static const unsigned int idle_milliseconds = 60*10*1000;
|
||||||
|
// wait at least an hour between two runs
|
||||||
|
static const unsigned int interval = 60*60*1000;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
LASTINPUTINFO last_input;
|
||||||
|
BOOL screensaver_active;
|
||||||
|
|
||||||
|
// main loop to check if user has been idle long enough
|
||||||
|
for (;;) {
|
||||||
|
if ( !GetLastInputInfo(&last_input)
|
||||||
|
|| !SystemParametersInfo(SPI_GETSCREENSAVERACTIVE, 0,
|
||||||
|
&screensaver_active, 0))
|
||||||
|
{
|
||||||
|
std::cerr << "WinAPI failed!" << std::endl;
|
||||||
|
return ERROR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last_input.dwTime < idle_milliseconds && !screensaver_active) {
|
||||||
|
// user hasn't been idle for long enough
|
||||||
|
// AND no screensaver is running
|
||||||
|
Sleep(1000);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// user has been idle at least 10 minutes
|
||||||
|
do_something();
|
||||||
|
// done. Wait before doing the next loop.
|
||||||
|
Sleep(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
#endif
|
||||||
7
app/idle_tracking.h
Normal file
7
app/idle_tracking.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#ifndef __IDLE_TRACKING_H
|
||||||
|
#define __IDLE_TRACKING_H
|
||||||
|
|
||||||
|
// Returns the idle time in milliseconds
|
||||||
|
extern int get_idle_time();
|
||||||
|
extern int get_idle_time_dynamically();
|
||||||
|
#endif
|
||||||
79
app/main.cpp
79
app/main.cpp
@ -6,17 +6,67 @@
|
|||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QFileInfo>
|
#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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
RunGuard guard("adjfaifaif");
|
RunGuard guard("QBreak app - runguard");
|
||||||
if ( !guard.tryToRun() )
|
if ( !guard.tryToRun() )
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
// Needed to enable logging to syslog or journald.
|
||||||
|
qInstallMessageHandler(SyslogMessageHandler);
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
QCoreApplication::setOrganizationName("qbreak.com");
|
QCoreApplication::setOrganizationName("voipobjects.com");
|
||||||
QCoreApplication::setOrganizationDomain("qbreak.com");
|
QCoreApplication::setOrganizationDomain("voipobjects.com");
|
||||||
QCoreApplication::setApplicationName("QBreak");
|
QCoreApplication::setApplicationName("QBreak");
|
||||||
|
|
||||||
QTranslator translator;
|
QTranslator translator;
|
||||||
@ -27,30 +77,19 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
app.setQuitOnLastWindowClosed(false);
|
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
|
// Put itself into app menu
|
||||||
appmenu::install(QFileInfo(argv[0]).absoluteFilePath().toStdString());
|
auto exe_path = QCoreApplication::applicationFilePath();
|
||||||
|
const char* appimage = std::getenv("APPIMAGE");
|
||||||
|
if (appimage != nullptr)
|
||||||
|
exe_path = appimage;
|
||||||
|
|
||||||
|
appmenu::install(exe_path.toStdString());
|
||||||
|
|
||||||
// Main window is full screen window, so start with tray icon only
|
// Main window is full screen window, so start with tray icon only
|
||||||
MainWindow w;
|
MainWindow w;
|
||||||
w.hide();
|
w.hide();
|
||||||
|
|
||||||
if (parser.isSet(test_1))
|
|
||||||
w.test_1();
|
|
||||||
else
|
|
||||||
if (parser.isSet(test_2))
|
|
||||||
w.test_2();
|
|
||||||
|
|
||||||
int retcode = app.exec();
|
int retcode = app.exec();
|
||||||
|
|
||||||
return retcode;
|
return retcode;
|
||||||
|
|||||||
@ -6,25 +6,52 @@
|
|||||||
#include "aboutdlg.h"
|
#include "aboutdlg.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "audio_support.h"
|
#include "audio_support.h"
|
||||||
|
#include "idle_tracking.h"
|
||||||
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDesktopWidget>
|
|
||||||
#include <QSvgGenerator>
|
#include <QSvgGenerator>
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QWindow>
|
#include <QWindow>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QSound>
|
#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));
|
||||||
|
}
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent)
|
MainWindow::MainWindow(QWidget *parent)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
, ui(new Ui::MainWindow)
|
, ui(new Ui::MainWindow)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
init();
|
|
||||||
|
// Defer init call for 100 ms - attempt to resolve the problem with non working setTooltip()
|
||||||
|
dispatchToMainThread([this](){init();}, std::chrono::milliseconds(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
@ -78,18 +105,22 @@ void MainWindow::init()
|
|||||||
// No postpone attempts yet
|
// No postpone attempts yet
|
||||||
mPostponeCount = 0;
|
mPostponeCount = 0;
|
||||||
|
|
||||||
// Timer
|
// Idle is not detected yet
|
||||||
mTimer = new QTimer(this);
|
mLastIdleMilliseconds = 0;
|
||||||
mTimer->setTimerType(Qt::TimerType::CoarseTimer);
|
|
||||||
mTimer->setSingleShot(true);
|
|
||||||
connect(mTimer, SIGNAL(timeout()), this, SLOT(onLongBreakStart()));
|
|
||||||
|
|
||||||
mNotifyTimer = new QTimer(this);
|
// Timer to start break
|
||||||
mNotifyTimer->setTimerType(Qt::TimerType::CoarseTimer);
|
mBreakStartTimer = new QTimer(this);
|
||||||
mNotifyTimer->setSingleShot(true);
|
mBreakStartTimer->setTimerType(Qt::TimerType::CoarseTimer);
|
||||||
connect(mNotifyTimer, SIGNAL(timeout()), this, SLOT(onLongBreakNotify()));
|
mBreakStartTimer->setSingleShot(true);
|
||||||
|
connect(mBreakStartTimer, &QTimer::timeout, this, [this](){shiftTo(AppState::Break);});
|
||||||
|
|
||||||
|
// Timer to run notification about upcoming break
|
||||||
|
mBreakNotifyTimer = new QTimer(this);
|
||||||
|
mBreakNotifyTimer->setTimerType(Qt::TimerType::CoarseTimer);
|
||||||
|
mBreakNotifyTimer->setSingleShot(true);
|
||||||
|
connect(mBreakNotifyTimer, SIGNAL(timeout()), this, SLOT(onLongBreakNotify()));
|
||||||
|
|
||||||
|
// Just update UI once per minute
|
||||||
mUpdateUITimer = new QTimer(this);
|
mUpdateUITimer = new QTimer(this);
|
||||||
mUpdateUITimer->setTimerType(Qt::TimerType::CoarseTimer);
|
mUpdateUITimer->setTimerType(Qt::TimerType::CoarseTimer);
|
||||||
mUpdateUITimer->setSingleShot(false);
|
mUpdateUITimer->setSingleShot(false);
|
||||||
@ -97,40 +128,73 @@ void MainWindow::init()
|
|||||||
connect(mUpdateUITimer, SIGNAL(timeout()), this, SLOT(onUpdateUI()));
|
connect(mUpdateUITimer, SIGNAL(timeout()), this, SLOT(onUpdateUI()));
|
||||||
mUpdateUITimer->start();
|
mUpdateUITimer->start();
|
||||||
|
|
||||||
|
// Timer to draw progress bar during the break
|
||||||
mProgressTimer = new QTimer(this);
|
mProgressTimer = new QTimer(this);
|
||||||
mProgressTimer->setInterval(std::chrono::milliseconds(INTERVAL_UPDATE_PROGRESS));
|
mProgressTimer->setInterval(std::chrono::milliseconds(INTERVAL_UPDATE_PROGRESS));
|
||||||
mProgressTimer->setSingleShot(false);
|
mProgressTimer->setSingleShot(false);
|
||||||
connect(mProgressTimer, SIGNAL(timeout()), this, SLOT(onProgress()));
|
connect(mProgressTimer, SIGNAL(timeout()), this, SLOT(onProgress()));
|
||||||
|
|
||||||
connect(ui->mPostponeButton, SIGNAL(clicked()), this, SLOT(onLongBreakPostpone()));
|
connect(ui->mPostponeButton, SIGNAL(clicked()), this, SLOT(onLongBreakPostpone()));
|
||||||
connect(ui->mSkipButton, SIGNAL(clicked()), this, SLOT(onLongBreakEnd()));
|
connect(ui->mSkipButton, &QPushButton::clicked, this, [this](){shiftTo(AppState::Counting);});
|
||||||
|
|
||||||
// Use the latest config
|
// Use the latest config
|
||||||
applyConfig();
|
applyConfig();
|
||||||
|
|
||||||
// Refresh UI
|
shiftTo(AppState::Counting);
|
||||||
onUpdateUI();
|
}
|
||||||
|
|
||||||
|
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()
|
void MainWindow::loadConfig()
|
||||||
{
|
{
|
||||||
app_settings settings;
|
mAppConfig = app_settings::load();
|
||||||
mAppConfig = 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::applyConfig()
|
void MainWindow::applyConfig()
|
||||||
{
|
{
|
||||||
if (mTimer)
|
if (mBreakStartTimer)
|
||||||
{
|
{
|
||||||
if (mTimer->interval() != mAppConfig.longbreak_interval)
|
if (mBreakStartTimer->interval() != mAppConfig.longbreak_interval)
|
||||||
{
|
{
|
||||||
mTimer->stop();
|
mBreakStartTimer->stop();
|
||||||
mTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval));
|
mBreakStartTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval));
|
||||||
mTimer->start();
|
mBreakStartTimer->start();
|
||||||
|
|
||||||
mNotifyTimer->stop();
|
mBreakNotifyTimer->stop();
|
||||||
mNotifyTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval - 30));
|
mBreakNotifyTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval - 30));
|
||||||
mNotifyTimer->start();
|
mBreakNotifyTimer->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,39 +212,6 @@ void MainWindow::applyConfig()
|
|||||||
autostart::disable();
|
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();
|
|
||||||
onLongBreakStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
void MainWindow::showMe()
|
||||||
{
|
{
|
||||||
QScreen* screen = nullptr;
|
QScreen* screen = nullptr;
|
||||||
@ -201,10 +232,18 @@ void MainWindow::showMe()
|
|||||||
{
|
{
|
||||||
windowHandle()->setScreen(screen);
|
windowHandle()->setScreen(screen);
|
||||||
setGeometry(screen->geometry());
|
setGeometry(screen->geometry());
|
||||||
qDebug() << "Window moved to screen " << screen->name() + " / " + screen->model() + " " + screen->manufacturer();
|
// qDebug() << "Window moved to screen " << screen->name() + " / " + screen->model() + " " + screen->manufacturer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// else
|
||||||
|
// qDebug() << "Screen not found!";
|
||||||
|
|
||||||
|
#if defined(DEBUG)
|
||||||
showFullScreen();
|
showFullScreen();
|
||||||
|
//showMaximized();
|
||||||
|
#else
|
||||||
|
showFullScreen();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::hideMe()
|
void MainWindow::hideMe()
|
||||||
@ -217,7 +256,10 @@ static QString secondsToText(int seconds)
|
|||||||
if (seconds < 60)
|
if (seconds < 60)
|
||||||
return QObject::tr("%1 seconds").arg(seconds);
|
return QObject::tr("%1 seconds").arg(seconds);
|
||||||
else
|
else
|
||||||
return QObject::tr("%1 minutes").arg(seconds / 60);
|
{
|
||||||
|
int minutes = int(float(seconds) / 60 + 0.5f);
|
||||||
|
return QObject::tr("%1 minutes").arg(minutes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::createTrayIcon()
|
void MainWindow::createTrayIcon()
|
||||||
@ -244,30 +286,136 @@ void MainWindow::createTrayIcon()
|
|||||||
|
|
||||||
mTrayIcon->setContextMenu(menu);
|
mTrayIcon->setContextMenu(menu);
|
||||||
mTrayIcon->setIcon(getTrayIcon());
|
mTrayIcon->setIcon(getTrayIcon());
|
||||||
mTrayIcon->setToolTip(AppName);
|
// mTrayIcon->setToolTip(AppName);
|
||||||
|
connect(mTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
|
||||||
|
this, SLOT(onTrayIconActivated(QSystemTrayIcon::ActivationReason)));
|
||||||
|
|
||||||
mTrayIcon->show();
|
mTrayIcon->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int msec2min(int msec)
|
||||||
|
{
|
||||||
|
float min_f = float(msec) / 1000 / 60;
|
||||||
|
return (int)(min_f + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString state2str(AppState state)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case AppState::None:
|
||||||
|
return "None";
|
||||||
|
case AppState::Break:
|
||||||
|
return "Break";
|
||||||
|
case AppState::Idle:
|
||||||
|
return "Idle";
|
||||||
|
case AppState::Counting:
|
||||||
|
return "Counting";
|
||||||
|
}
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MainWindow::shiftTo(AppState newState)
|
||||||
|
{
|
||||||
|
if (newState == mState)
|
||||||
|
return;
|
||||||
|
qDebug() << state2str(mState) << " -> " << state2str(newState);
|
||||||
|
|
||||||
|
switch (newState)
|
||||||
|
{
|
||||||
|
case AppState::None:
|
||||||
|
// Do nothing, app is not started
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppState::Idle:
|
||||||
|
onIdleStart();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppState::Break:
|
||||||
|
// Break is active
|
||||||
|
onLongBreakStart();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppState::Counting:
|
||||||
|
// Working, break is closing
|
||||||
|
if (mState == AppState::Break)
|
||||||
|
onLongBreakEnd();
|
||||||
|
else
|
||||||
|
if (mState == AppState::Idle)
|
||||||
|
onIdleEnd();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
mState = newState;
|
||||||
|
|
||||||
|
// Run deferred in main UI thread
|
||||||
|
dispatchToMainThread([this](){
|
||||||
|
onUpdateUI();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onUpdateUI()
|
void MainWindow::onUpdateUI()
|
||||||
{
|
{
|
||||||
|
qDebug() << "UI timer fired.";
|
||||||
|
|
||||||
|
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();
|
||||||
|
qDebug() << "Idle found: " << idle_milliseconds / 1000 << "s";
|
||||||
|
|
||||||
|
if (idle_milliseconds < mAppConfig.idle_timeout * 1000)
|
||||||
|
{
|
||||||
|
shiftTo(AppState::Counting);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case AppState::Break:
|
||||||
|
// Break is active
|
||||||
if (mTrayIcon)
|
if (mTrayIcon)
|
||||||
{
|
|
||||||
if (mProgressTimer->isActive())
|
|
||||||
{
|
|
||||||
// Break is in effect now
|
|
||||||
mTrayIcon->setToolTip(QString());
|
mTrayIcon->setToolTip(QString());
|
||||||
}
|
break;
|
||||||
else
|
|
||||||
if (mTimer->isActive())
|
case AppState::Counting:
|
||||||
|
// Working, break is closing
|
||||||
|
// Check maybe it is idle ?
|
||||||
|
if (!mIdleStart && mAppConfig.idle_timeout)
|
||||||
{
|
{
|
||||||
auto remaining_milliseconds = mTimer->remainingTime();
|
idle_milliseconds = get_idle_time_dynamically();
|
||||||
if (remaining_milliseconds == 0)
|
qDebug() << "Idle found: " << idle_milliseconds / 1000 << "s";
|
||||||
mTrayIcon->setToolTip(tr("Less than a minute left until the next break."));
|
|
||||||
else
|
if (idle_milliseconds >= mAppConfig.idle_timeout * 1000)
|
||||||
mTrayIcon->setToolTip(tr("There are %1 minutes left until the next break.").arg(remaining_milliseconds / 1000 / 60));
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
ui->mSkipButton->setVisible(mPostponeCount > 0);
|
ui->mSkipButton->setVisible(mPostponeCount > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -276,31 +424,37 @@ void MainWindow::onLongBreakNotify()
|
|||||||
mTrayIcon->showMessage(tr("New break"),
|
mTrayIcon->showMessage(tr("New break"),
|
||||||
tr("New break will start in %1 secs").arg(Default_Notify_Length),
|
tr("New break will start in %1 secs").arg(Default_Notify_Length),
|
||||||
getAppIcon(),
|
getAppIcon(),
|
||||||
INTERVAL_NOTIFICATION);
|
INTERVAL_NOTIFICATION * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onLongBreakStart()
|
void MainWindow::onLongBreakStart()
|
||||||
{
|
{
|
||||||
qDebug() << "Long break starts for " << secondsToText(mAppConfig.longbreak_postpone_interval);
|
mBreakStartTimer->stop();
|
||||||
|
mBreakNotifyTimer->stop();
|
||||||
|
qDebug() << "Stop main and notify timers.";
|
||||||
|
|
||||||
|
// Reset idle counter
|
||||||
|
mIdleStart.reset();
|
||||||
|
|
||||||
|
// Show the button "Postpone"
|
||||||
ui->mPostponeButton->setText(tr("Postpone for ") + secondsToText(mAppConfig.longbreak_postpone_interval));
|
ui->mPostponeButton->setText(tr("Postpone for ") + secondsToText(mAppConfig.longbreak_postpone_interval));
|
||||||
|
|
||||||
|
// Show the screen
|
||||||
showMe();
|
showMe();
|
||||||
|
|
||||||
|
// Save start time
|
||||||
mBreakStartTime = std::chrono::steady_clock::now();
|
mBreakStartTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
// Start progress bar
|
// Start progress bar
|
||||||
mProgressTimer->start();
|
mProgressTimer->start();
|
||||||
|
|
||||||
// Refresh UI
|
// Update title immediate
|
||||||
onUpdateUI();
|
onProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onLongBreakEnd()
|
void MainWindow::onLongBreakEnd()
|
||||||
{
|
{
|
||||||
qDebug() << "Long break ends.";
|
// qDebug() << "Long break ends.";
|
||||||
|
|
||||||
// Reset postpone counter
|
|
||||||
mPostponeCount = 0;
|
|
||||||
|
|
||||||
// Prepare to next triggering
|
// Prepare to next triggering
|
||||||
ui->mProgressBar->setValue(0);
|
ui->mProgressBar->setValue(0);
|
||||||
@ -311,15 +465,19 @@ void MainWindow::onLongBreakEnd()
|
|||||||
mProgressTimer->stop();
|
mProgressTimer->stop();
|
||||||
|
|
||||||
// Start new timer
|
// Start new timer
|
||||||
mTimer->stop();
|
if (!mBreakStartTimer->isActive())
|
||||||
mTimer->start(std::chrono::seconds(mAppConfig.longbreak_interval));
|
{
|
||||||
mNotifyTimer->stop();
|
mBreakStartTimer->start(std::chrono::seconds(mAppConfig.longbreak_interval));
|
||||||
mNotifyTimer->start(std::chrono::seconds(mAppConfig.longbreak_interval - 30));
|
qDebug() << "Start main timer for " << mAppConfig.longbreak_interval << " seconds.";
|
||||||
|
}
|
||||||
|
if (!mBreakNotifyTimer->isActive())
|
||||||
|
{
|
||||||
|
mBreakNotifyTimer->start(std::chrono::seconds(mAppConfig.longbreak_interval - 30));
|
||||||
|
qDebug() << "Start notify timer for " << mAppConfig.longbreak_interval - 30 << " seconds.";
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh UI
|
// Play selected audio. When break is postponed - audio is not played
|
||||||
onUpdateUI();
|
if (0 == mPostponeCount)
|
||||||
|
|
||||||
// Play selecged audio
|
|
||||||
play_audio(mAppConfig.play_audio);
|
play_audio(mAppConfig.play_audio);
|
||||||
|
|
||||||
// Run script
|
// Run script
|
||||||
@ -333,7 +491,7 @@ void MainWindow::onLongBreakEnd()
|
|||||||
|
|
||||||
void MainWindow::onLongBreakPostpone()
|
void MainWindow::onLongBreakPostpone()
|
||||||
{
|
{
|
||||||
qDebug() << "Long break postponed.";
|
// qDebug() << "Long break postponed.";
|
||||||
mPostponeCount++;
|
mPostponeCount++;
|
||||||
|
|
||||||
hideMe();
|
hideMe();
|
||||||
@ -342,13 +500,12 @@ void MainWindow::onLongBreakPostpone()
|
|||||||
ui->mProgressBar->setValue(0);
|
ui->mProgressBar->setValue(0);
|
||||||
|
|
||||||
// Start timer again
|
// Start timer again
|
||||||
mTimer->stop();
|
mBreakStartTimer->stop();
|
||||||
mTimer->start(std::chrono::seconds(mAppConfig.longbreak_postpone_interval));
|
mBreakStartTimer->start(std::chrono::seconds(mAppConfig.longbreak_postpone_interval));
|
||||||
mNotifyTimer->stop();
|
mBreakNotifyTimer->stop();
|
||||||
mNotifyTimer->start(std::chrono::seconds(mAppConfig.longbreak_postpone_interval - 30));
|
mBreakNotifyTimer->start(std::chrono::seconds(mAppConfig.longbreak_postpone_interval - 30));
|
||||||
|
|
||||||
// Refresh UI
|
shiftTo(AppState::Counting);
|
||||||
onUpdateUI();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onProgress()
|
void MainWindow::onProgress()
|
||||||
@ -362,12 +519,16 @@ void MainWindow::onProgress()
|
|||||||
if (remaining < 0)
|
if (remaining < 0)
|
||||||
remaining = 0;
|
remaining = 0;
|
||||||
|
|
||||||
ui->mRemainingLabel->setText(QString("Remaining: ") + secondsToText(remaining));
|
auto text = QString("Remaining: ") + secondsToText(remaining);
|
||||||
|
ui->mRemainingLabel->setText(text);
|
||||||
|
if (mTrayIcon)
|
||||||
|
mTrayIcon->setToolTip(text);
|
||||||
|
|
||||||
if (percents > 100)
|
if (percents > 100)
|
||||||
{
|
{
|
||||||
mProgressTimer->stop();
|
mProgressTimer->stop();
|
||||||
onLongBreakEnd();
|
mPostponeCount = 0; // Reset postpone counter
|
||||||
|
shiftTo(AppState::Counting);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
showMe();
|
showMe();
|
||||||
@ -375,10 +536,58 @@ void MainWindow::onProgress()
|
|||||||
|
|
||||||
void MainWindow::onNextBreak()
|
void MainWindow::onNextBreak()
|
||||||
{
|
{
|
||||||
mTimer->stop();
|
shiftTo(AppState::Break);
|
||||||
mNotifyTimer->stop();
|
}
|
||||||
|
|
||||||
onLongBreakStart();
|
void MainWindow::onIdleStart()
|
||||||
|
{
|
||||||
|
if (mState != AppState::Counting)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Detected idle
|
||||||
|
// Timestamp when idle started
|
||||||
|
mIdleStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// How much working time remains
|
||||||
|
mRemainingWorkInterval = mBreakStartTimer->remainingTime() / 1000;
|
||||||
|
|
||||||
|
// Stop main & notify timers
|
||||||
|
mBreakStartTimer->stop();
|
||||||
|
mBreakNotifyTimer->stop();
|
||||||
|
qDebug() << "Stop main and notify timers. Remaining time is " << mRemainingWorkInterval.value() << "s";
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::onIdleEnd()
|
||||||
|
{
|
||||||
|
if (mState != AppState::Idle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mIdleStart.reset();
|
||||||
|
|
||||||
|
// 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
|
||||||
|
{
|
||||||
|
mBreakStartTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval));
|
||||||
|
if (mRemainingWorkInterval > INTERVAL_NOTIFICATION)
|
||||||
|
mBreakNotifyTimer->setInterval(std::chrono::seconds(mAppConfig.longbreak_interval - INTERVAL_NOTIFICATION));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mBreakStartTimer->isActive())
|
||||||
|
mBreakStartTimer->start();
|
||||||
|
if (!mBreakNotifyTimer->isActive())
|
||||||
|
mBreakNotifyTimer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onSettings()
|
void MainWindow::onSettings()
|
||||||
@ -417,3 +626,20 @@ void MainWindow::onExit()
|
|||||||
this->close();
|
this->close();
|
||||||
QApplication::exit();
|
QApplication::exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onTrayIconActivated(QSystemTrayIcon::ActivationReason reason)
|
||||||
|
{
|
||||||
|
switch(reason)
|
||||||
|
{
|
||||||
|
// Show context menu on single click
|
||||||
|
case QSystemTrayIcon::Trigger:
|
||||||
|
mTrayIcon->contextMenu()->popup(QCursor::pos());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case QSystemTrayIcon::Unknown:
|
||||||
|
case QSystemTrayIcon::Context:
|
||||||
|
case QSystemTrayIcon::DoubleClick:
|
||||||
|
case QSystemTrayIcon::MiddleClick:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -6,9 +6,20 @@
|
|||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "settingsdialog.h"
|
#include "settingsdialog.h"
|
||||||
|
|
||||||
|
// Possible app states
|
||||||
|
enum class AppState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Counting,
|
||||||
|
Idle,
|
||||||
|
Break
|
||||||
|
};
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
namespace Ui { class MainWindow; }
|
namespace Ui { class MainWindow; }
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
@ -29,21 +40,31 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
QTimer* mTimer;
|
QTimer* mBreakStartTimer; // Main timer - triggers when break occurs
|
||||||
QTimer* mNotifyTimer;
|
QTimer* mBreakNotifyTimer; // Timer to show notification from system tray
|
||||||
QTimer* mShowNotifyTimer;
|
QTimer* mUpdateUITimer; // Update UI timer - triggers every minute to update UI and checks for idle
|
||||||
QTimer* mUpdateUITimer;
|
QTimer* mProgressTimer; // Break progress timer - updates an UI
|
||||||
QTimer* mProgressTimer;
|
|
||||||
QSystemTrayIcon* mTrayIcon;
|
QSystemTrayIcon* mTrayIcon;
|
||||||
SettingsDialog* mSettingsDialog;
|
SettingsDialog* mSettingsDialog;
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point mBreakStartTime;
|
std::chrono::steady_clock::time_point mBreakStartTime;
|
||||||
|
|
||||||
|
// How much milliseconds remains for main break
|
||||||
|
std::optional<int> mRemainingWorkInterval;
|
||||||
|
|
||||||
app_settings::config mAppConfig;
|
app_settings::config mAppConfig;
|
||||||
int mPostponeCount;
|
int mPostponeCount = 0;
|
||||||
|
|
||||||
|
// Time when idle was started
|
||||||
|
std::optional<std::chrono::steady_clock::time_point> mIdleStart;
|
||||||
|
|
||||||
|
int mLastIdleMilliseconds = 0;
|
||||||
|
|
||||||
|
AppState mState = AppState::None;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
void loadConfig();
|
void loadConfig();
|
||||||
void applyConfig();
|
void applyConfig();
|
||||||
void schedule();
|
|
||||||
void createTrayIcon();
|
void createTrayIcon();
|
||||||
void showMe();
|
void showMe();
|
||||||
void hideMe();
|
void hideMe();
|
||||||
@ -53,17 +74,24 @@ private:
|
|||||||
QIcon getAppIcon();
|
QIcon getAppIcon();
|
||||||
QIcon getTrayIcon();
|
QIcon getTrayIcon();
|
||||||
|
|
||||||
|
// Function to switch state
|
||||||
|
void shiftTo(AppState state);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onUpdateUI();
|
void onUpdateUI();
|
||||||
void onLongBreakNotify();
|
void onLongBreakNotify();
|
||||||
void onLongBreakStart();
|
void onLongBreakStart();
|
||||||
void onLongBreakPostpone();
|
void onLongBreakPostpone();
|
||||||
void onLongBreakEnd();
|
void onLongBreakEnd();
|
||||||
|
void onIdleStart();
|
||||||
|
void onIdleEnd();
|
||||||
|
|
||||||
void onProgress();
|
void onProgress();
|
||||||
void onNextBreak();
|
void onNextBreak();
|
||||||
void onSettings();
|
void onSettings();
|
||||||
void onAbout();
|
void onAbout();
|
||||||
void onExit();
|
void onExit();
|
||||||
|
|
||||||
|
void onTrayIconActivated(QSystemTrayIcon::ActivationReason reason);
|
||||||
};
|
};
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
QT += core gui svg multimedia
|
QT += core gui svg multimedia widgets dbus
|
||||||
|
|
||||||
|
CONFIG += debug_and_release console
|
||||||
|
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
|
||||||
|
|
||||||
CONFIG += c++17 lrelease embed_translations
|
CONFIG += c++17 lrelease embed_translations
|
||||||
|
|
||||||
@ -16,7 +17,8 @@ SOURCES += \
|
|||||||
settings.cpp \
|
settings.cpp \
|
||||||
settingsdialog.cpp \
|
settingsdialog.cpp \
|
||||||
runguard.cpp \
|
runguard.cpp \
|
||||||
audio_support.cpp
|
audio_support.cpp \
|
||||||
|
idle_tracking.cpp
|
||||||
|
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
@ -27,7 +29,8 @@ HEADERS += \
|
|||||||
settings.h \
|
settings.h \
|
||||||
settingsdialog.h \
|
settingsdialog.h \
|
||||||
runguard.h \
|
runguard.h \
|
||||||
audio_support.h
|
audio_support.h \
|
||||||
|
idle_tracking.h
|
||||||
|
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
@ -39,9 +42,20 @@ RESOURCES = qbreak.qrc
|
|||||||
|
|
||||||
TRANSLATIONS = strings.ts strings_en.ts strings_ru.ts
|
TRANSLATIONS = strings.ts strings_en.ts strings_ru.ts
|
||||||
|
|
||||||
unix:!macx: DEFINES += TARGET_LINUX
|
unix:!macx: DEFINES += TARGET_LINUX # USE_WAYLAND
|
||||||
|
|
||||||
# Default rules for deployment.
|
# Default rules for deployment.
|
||||||
qnx: target.path = /tmp/$${TARGET}/bin
|
qnx: target.path = /tmp/$${TARGET}/bin
|
||||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||||
!isEmpty(target.path): INSTALLS += target
|
!isEmpty(target.path): INSTALLS += target
|
||||||
|
|
||||||
|
# X11 and wayland libraries
|
||||||
|
unix:LIBS += -L/usr/X11R6/lib/ \
|
||||||
|
-lX11 -lXext -lXss -ldl
|
||||||
|
|
||||||
|
Debug: DEFINES += DEBUG
|
||||||
|
Release: DEFINES += QT_NO_DEBUG_OUTPUT
|
||||||
|
|
||||||
|
# When using wayland:
|
||||||
|
# unix:LIBS += -L/usr/local/lib \
|
||||||
|
# -lwayland-client-unstable++ -lwayland-client-extra++ -lwayland-client++
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
<RCC>
|
<RCC>
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>assets/images/app_icon_dark.png</file>
|
|
||||||
<file>assets/images/app_icon_light.png</file>
|
|
||||||
<file>assets/misc/qbreak.desktop</file>
|
<file>assets/misc/qbreak.desktop</file>
|
||||||
<file>assets/images/coffee_cup/icon_16x16.png</file>
|
<file>assets/images/coffee_cup/icon_16x16.png</file>
|
||||||
<file>assets/images/coffee_cup/icon_24x24.png</file>
|
<file>assets/images/coffee_cup/icon_24x24.png</file>
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const QString Key_PreferredMonitor = "Preferred_Monitor";
|
|||||||
const QString Key_Audio_Name = "Audio_Name";
|
const QString Key_Audio_Name = "Audio_Name";
|
||||||
const QString Key_Audio_Path = "Audio_Path";
|
const QString Key_Audio_Path = "Audio_Path";
|
||||||
const QString Key_Script = "Script";
|
const QString Key_Script = "Script";
|
||||||
|
const QString Key_Idle_Timeout = "Idle_Timeout";
|
||||||
|
|
||||||
void app_settings::save(const config &cfg)
|
void app_settings::save(const config &cfg)
|
||||||
{
|
{
|
||||||
@ -28,6 +29,7 @@ void app_settings::save(const config &cfg)
|
|||||||
s.setValue(Key_Audio_Name, cfg.play_audio.name);
|
s.setValue(Key_Audio_Name, cfg.play_audio.name);
|
||||||
s.setValue(Key_Audio_Path, cfg.play_audio.path);
|
s.setValue(Key_Audio_Path, cfg.play_audio.path);
|
||||||
s.setValue(Key_Script, cfg.script_on_break_finish);
|
s.setValue(Key_Script, cfg.script_on_break_finish);
|
||||||
|
s.setValue(Key_Idle_Timeout, cfg.idle_timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
app_settings::config app_settings::load()
|
app_settings::config app_settings::load()
|
||||||
@ -45,6 +47,7 @@ app_settings::config app_settings::load()
|
|||||||
r.play_audio.name = s.value(Key_Audio_Name, Audio_Empty).toString();
|
r.play_audio.name = s.value(Key_Audio_Name, Audio_Empty).toString();
|
||||||
r.play_audio.path = s.value(Key_Audio_Path, QString()).toString();
|
r.play_audio.path = s.value(Key_Audio_Path, QString()).toString();
|
||||||
r.script_on_break_finish = s.value(Key_Script, QString()).toString();
|
r.script_on_break_finish = s.value(Key_Script, QString()).toString();
|
||||||
|
r.idle_timeout = s.value(Key_Idle_Timeout, Default_Idle_Timeout).toInt();
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,13 +19,16 @@ const bool Default_Verbose = false;
|
|||||||
// Default autostart
|
// Default autostart
|
||||||
const bool Default_Autostart = true;
|
const bool Default_Autostart = true;
|
||||||
|
|
||||||
|
// Default idle timeout
|
||||||
|
const int Default_Idle_Timeout = 0;
|
||||||
|
|
||||||
const QString Default_Monitor = "";
|
const QString Default_Monitor = "";
|
||||||
const QString Primary_Monitor = "[Primary]";
|
const QString Primary_Monitor = "[Primary]";
|
||||||
|
|
||||||
// Default behavior is not play any audio on break end.
|
// Default behavior is not play any audio on break end.
|
||||||
const QString Audio_Empty = "None";
|
const QString Audio_Empty = "None";
|
||||||
const QString Audio_Default_1 = "Default 1";
|
const QString Audio_Retro = "Retro";
|
||||||
const QString Audio_Default_2 = "Default 2";
|
const QString Audio_Gagarin = "Gagarin";
|
||||||
const QString Audio_Custom = "Custom...";
|
const QString Audio_Custom = "Custom...";
|
||||||
|
|
||||||
struct audio_item
|
struct audio_item
|
||||||
@ -35,8 +38,8 @@ struct audio_item
|
|||||||
};
|
};
|
||||||
|
|
||||||
const std::map<QString, QString> AudioMap {
|
const std::map<QString, QString> AudioMap {
|
||||||
{Audio_Default_1, ":/assets/sound/alarm-retro.wav"},
|
{Audio_Retro, ":/assets/sound/alarm-retro.wav"},
|
||||||
{Audio_Default_2, ":/assets/sound/alarm-poehali.wav"},
|
{Audio_Gagarin, ":/assets/sound/alarm-poehali.wav"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -67,6 +70,9 @@ public:
|
|||||||
// This value can be path to audio file or empty or [embedded] string
|
// This value can be path to audio file or empty or [embedded] string
|
||||||
selected_audio play_audio;
|
selected_audio play_audio;
|
||||||
QString script_on_break_finish;
|
QString script_on_break_finish;
|
||||||
|
|
||||||
|
// Zero means "idle is not tracked". Value in seconds.
|
||||||
|
int idle_timeout = Default_Idle_Timeout;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void save(const config& cfg);
|
static void save(const config& cfg);
|
||||||
|
|||||||
@ -55,7 +55,7 @@ void SettingsDialog::init()
|
|||||||
ui->mPreferredMonitorCombobox->setCurrentIndex(found_idx);
|
ui->mPreferredMonitorCombobox->setCurrentIndex(found_idx);
|
||||||
|
|
||||||
// Fill audio combo box
|
// Fill audio combo box
|
||||||
auto audios = {Audio_Empty, Audio_Default_1, Audio_Default_2, Audio_Custom};
|
auto audios = {Audio_Empty, Audio_Retro, Audio_Gagarin, Audio_Custom};
|
||||||
ui->mAudioComboBox->addItems(audios);
|
ui->mAudioComboBox->addItems(audios);
|
||||||
mCustomAudioIdx = audios.size() - 1;
|
mCustomAudioIdx = audios.size() - 1;
|
||||||
|
|
||||||
@ -69,6 +69,9 @@ void SettingsDialog::init()
|
|||||||
ui->mScriptEdit->setText(c.script_on_break_finish);
|
ui->mScriptEdit->setText(c.script_on_break_finish);
|
||||||
connect(ui->mAudioComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onAudioIndexChanged(int)));
|
connect(ui->mAudioComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onAudioIndexChanged(int)));
|
||||||
|
|
||||||
|
// Idle timeout
|
||||||
|
ui->mIdleTimeoutSpinbox->setValue(c.idle_timeout / 60);
|
||||||
|
|
||||||
mSkipAudioChangeEvent = false;
|
mSkipAudioChangeEvent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +90,7 @@ void SettingsDialog::accept()
|
|||||||
if (c.play_audio.name == Audio_Custom)
|
if (c.play_audio.name == Audio_Custom)
|
||||||
c.play_audio.path = mCustomAudioPath;
|
c.play_audio.path = mCustomAudioPath;
|
||||||
|
|
||||||
|
c.idle_timeout = ui->mIdleTimeoutSpinbox->value() * 60;
|
||||||
app_settings::save(c);
|
app_settings::save(c);
|
||||||
|
|
||||||
emit accepted();
|
emit accepted();
|
||||||
|
|||||||
@ -131,13 +131,27 @@
|
|||||||
<item row="9" column="0">
|
<item row="9" column="0">
|
||||||
<widget class="QLabel" name="label_8">
|
<widget class="QLabel" name="label_8">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Command to run when break finish</string>
|
<string>Shell command to run when break finish</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="9" column="1">
|
<item row="9" column="1">
|
||||||
<widget class="QLineEdit" name="mScriptEdit"/>
|
<widget class="QLineEdit" name="mScriptEdit"/>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="10" column="0">
|
||||||
|
<widget class="QLabel" name="label_9">
|
||||||
|
<property name="text">
|
||||||
|
<string>Idle timeout in minutes (zero means no idle tracking)</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="1">
|
||||||
|
<widget class="QSpinBox" name="mIdleTimeoutSpinbox">
|
||||||
|
<property name="suffix">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
# I use this script on two different hosts so there are logic to find proper Qt installation
|
# I use this script on two different hosts so there are logic to find proper Qt installation
|
||||||
|
|
||||||
export QT_HOME=/home/$USER/qt/5.12.10/gcc_64
|
export QT_HOME=/home/$USER/tools/qt/5.15.2/gcc_64
|
||||||
if [ ! -d "$QT_HOME" ] ; then
|
if [ ! -d "$QT_HOME" ] ; then
|
||||||
export QT_HOME=/home/$USER/qt5/5.12.12/gcc_64
|
export QT_HOME=/home/$USER/tools/qt/5.15.2/gcc_64
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Build .appimage
|
# Build .appimage
|
||||||
/usr/bin/python3 build_qbreak.py
|
/usr/bin/python3 build_qbreak.py release
|
||||||
|
|
||||||
|
|||||||
12
scripts/build_linux_debug.sh
Executable file
12
scripts/build_linux_debug.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/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/5.15.2/gcc_64
|
||||||
|
if [ ! -d "$QT_HOME" ] ; then
|
||||||
|
export QT_HOME=/home/$USER/qt/5.15.2/gcc_64
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build .appimage
|
||||||
|
/usr/bin/python3 build_qbreak.py debug
|
||||||
|
|
||||||
12
scripts/build_linux_qt6.sh
Executable file
12
scripts/build_linux_qt6.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
12
scripts/build_linux_qt6_debug.sh
Executable file
12
scripts/build_linux_qt6_debug.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
@ -5,6 +5,7 @@ import platform
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import glob
|
import glob
|
||||||
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import build_utils
|
import build_utils
|
||||||
@ -48,8 +49,13 @@ if platform.system() == 'Linux':
|
|||||||
print(f'qmake call failed with code {retcode}')
|
print(f'qmake call failed with code {retcode}')
|
||||||
exit(retcode)
|
exit(retcode)
|
||||||
|
|
||||||
|
# Check the requested type of build - debug or release
|
||||||
|
build_type = 'release'
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
build_type = sys.argv[1]
|
||||||
|
|
||||||
print('Build...')
|
print('Build...')
|
||||||
retcode = os.system('make -j4')
|
retcode = os.system(f'make {build_type} -j4')
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
print(f'make call failed with code {retcode}')
|
print(f'make call failed with code {retcode}')
|
||||||
exit(retcode)
|
exit(retcode)
|
||||||
@ -78,7 +84,7 @@ if platform.system() == 'Linux':
|
|||||||
'-qmake=' + os.environ['QT_HOME'] + '/bin/qmake',
|
'-qmake=' + os.environ['QT_HOME'] + '/bin/qmake',
|
||||||
'-unsupported-allow-new-glibc',
|
'-unsupported-allow-new-glibc',
|
||||||
#'-no-translations',
|
#'-no-translations',
|
||||||
'-extra-plugins=iconengines,platformthemes/libqgtk3.so'
|
'-extra-plugins=iconengines,platformthemes/libqgtk3.so,platforms/libqxcb.so'
|
||||||
]
|
]
|
||||||
|
|
||||||
desktop_path = 'appimage_dir/usr/share/applications/qbreak.desktop'
|
desktop_path = 'appimage_dir/usr/share/applications/qbreak.desktop'
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user