From efd97e4f719b2ff0e98b04e17be541efd6797566 Mon Sep 17 00:00:00 2001 From: Brandon Johnson Date: Fri, 5 Jun 2020 16:12:04 -0600 Subject: [PATCH 01/65] add Lenovo Thinkpad X1 Presenter Mouse --- devices.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/devices.conf b/devices.conf index a46cabb2..126b08fc 100644 --- a/devices.conf +++ b/devices.conf @@ -8,3 +8,4 @@ 0x0c45, 0x8101, usb, AVATTO H100 / August WP200 0x2312, 0x863d, usb, August LP315 0x2571, 0x4109, usb, AVATTO i10 Pro +0x17ef, 0x60d9, usb, Lenovo ThinkPad X1 Presenter Mouse From 24d6c6bc57062f925b6c25d6c688a44434b487a9 Mon Sep 17 00:00:00 2001 From: Jahn Date: Fri, 19 Jun 2020 10:01:24 +0200 Subject: [PATCH 02/65] Bump version for builds without git. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fea2647..a2341127 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,9 +104,9 @@ target_compile_definitions(projecteur PRIVATE # VERSION_TYPE must be either 'release' or 'develop' set_target_properties(projecteur PROPERTIES VERSION_MAJOR 0 - VERSION_MINOR 8 + VERSION_MINOR 9 VERSION_PATCH 0 - VERSION_TYPE release + VERSION_TYPE develop ) add_version_info(projecteur "${CMAKE_CURRENT_SOURCE_DIR}") From 84e5c210997bb0f733a2ab163e9e60fca1c7024e Mon Sep 17 00:00:00 2001 From: Brandon Johnson Date: Mon, 22 Jun 2020 12:41:00 -0600 Subject: [PATCH 03/65] Update devices.conf --- devices.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/devices.conf b/devices.conf index 126b08fc..bd0d0c3b 100644 --- a/devices.conf +++ b/devices.conf @@ -9,3 +9,4 @@ 0x2312, 0x863d, usb, August LP315 0x2571, 0x4109, usb, AVATTO i10 Pro 0x17ef, 0x60d9, usb, Lenovo ThinkPad X1 Presenter Mouse +0x17ef, 0x60db, bt, Lenovo ThinkPad X1 Presenter Mouse From 8c1cf0265e1bbd38ef692e778e323ed9c6f7e3a1 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sun, 12 Jul 2020 08:38:11 +0200 Subject: [PATCH 04/65] Add toggle spotlight action. Add icon for toggle spotlight action. --- icons/icon-font/.fontcustom-manifest.json | 18 ++++++----- .../svg/iconmonstr-power-on-off-11.svg | 1 + icons/projecteur-icons.ttf | Bin 4068 -> 4240 bytes src/actiondelegate.cc | 28 ++++++++++++++++-- src/deviceinput.cc | 9 ++++-- src/deviceinput.h | 24 ++++++++++++--- src/inputmapconfig.cc | 3 ++ src/preferencesdlg.cc | 14 ++++++++- src/projecteur-icons-def.h | 1 + src/spotlight.cc | 4 +++ 10 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 icons/icon-font/svg/iconmonstr-power-on-off-11.svg diff --git a/icons/icon-font/.fontcustom-manifest.json b/icons/icon-font/.fontcustom-manifest.json index 424e217b..92f6e4ac 100644 --- a/icons/icon-font/.fontcustom-manifest.json +++ b/icons/icon-font/.fontcustom-manifest.json @@ -1,14 +1,14 @@ { "checksum": { - "previous": "f2524cd97c9c575c85c05e9f003b8007c64cfe68cce5ae7745c077f4e9c7e8c1", - "current": "f2524cd97c9c575c85c05e9f003b8007c64cfe68cce5ae7745c077f4e9c7e8c1" + "previous": "d4f6bb1053795e614d87b855da33da2b7ee5eefaaf01d5bc4d3976114651c9d5", + "current": "d4f6bb1053795e614d87b855da33da2b7ee5eefaaf01d5bc4d3976114651c9d5" }, "fonts": [ - "output/fonts/projecteur-icons_f2524cd97c9c575c85c05e9f003b8007.ttf", - "output/fonts/projecteur-icons_f2524cd97c9c575c85c05e9f003b8007.svg", - "output/fonts/projecteur-icons_f2524cd97c9c575c85c05e9f003b8007.woff", - "output/fonts/projecteur-icons_f2524cd97c9c575c85c05e9f003b8007.eot", - "output/fonts/projecteur-icons_f2524cd97c9c575c85c05e9f003b8007.woff2" + "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.ttf", + "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.svg", + "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.woff", + "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.eot", + "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.woff2" ], "glyphs": { "iconmonstr-arrow-73": { @@ -63,6 +63,10 @@ "codepoint": 61703, "source": "svg/iconmonstr-plus-5.svg" }, + "iconmonstr-power-on-off-11": { + "codepoint": 61717, + "source": "svg/iconmonstr-power-on-off-11.svg" + }, "iconmonstr-share-8": { "codepoint": 61704, "source": "svg/iconmonstr-share-8.svg" diff --git a/icons/icon-font/svg/iconmonstr-power-on-off-11.svg b/icons/icon-font/svg/iconmonstr-power-on-off-11.svg new file mode 100644 index 00000000..40121689 --- /dev/null +++ b/icons/icon-font/svg/iconmonstr-power-on-off-11.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/projecteur-icons.ttf b/icons/projecteur-icons.ttf index 3b10114927a7fc33b2055fdcf6a522f0da5caaa4..dedbfe8a58dabc7d10a01511de2b375af55e4b2c 100644 GIT binary patch delta 624 zcmYjPOK1~O6g_V;%``NL(RLDQ(l~8uQl*nPu}Rdb1(716B$&2_VyR=yM@%yLQR^zL z2+~!n1FpKUTbEh7$;Pz>2}=Deq@bW^U0CSGcvB;|c-(XD=RMB3Z~MsFU=0j_1xqj> zHaR(Ub?wW*1He|LtScH11f#oi!vISVznRRd#n(aM0`VO>v&n_3goErNV7NkcKBHEO ztrpz`TaI{lCU-ymV(0V*)t3MhpG~Q^9kuTo)!x$yXS1otHCHN}(~_$FQ{PAGi&5U6 zR~L&&Vw2uDMO-SV`P6Eo=>g0S0nT33D%CgJ-(CXNb?VvN*FZWJ%H41051jpl9*)L< zcPD1{{>3Nk9JA}^*#P^Y-(V9=)Yn;D|H^_)th>2c#`7e*zGztPuJJ&t!GSek#39%* z0vvS+4#6ix#=O3Wn~8mbFYFUNpot&EKkQT2aT-(x7un;i4sdw&sYMy5|kr)&hpHRzrLZhg?_!niR75ydYogu^@KIUCJx_8YK6R3s8q{xQM;Ea e%UVI!(rH;y$Yw^XZ_l?s6t*5|tL^yz3jP51w~KrL delta 454 zcmYjO-7CXk6n@Tc=A(ULONz`S`IxoE=E6sb<$|b5T4X;n8ySTeXng|EwnTon|2j}kDT;XtVZ=)*Mwy+eC%lxJXt{dcdOGJ)GF^y|}ql39+QH}(! ztd2CG`2wW!=+a7T`Yw3{=(Svv%4uL7-^cUPl%ewj8j0J0JE_n2=abY$X3uhMoL2e)mf)THItcHM&t3`)EZ7IFefeY=>`jjP?T!6nXgc9q-a5Bjq**ned#|@{C{D;up?r2 diff --git a/src/actiondelegate.cc b/src/actiondelegate.cc index 9d184416..05b84c7a 100644 --- a/src/actiondelegate.cc +++ b/src/actiondelegate.cc @@ -54,6 +54,22 @@ namespace { return QSize(100,16); } } + + namespace togglespotlight { + // --------------------------------------------------------------------------------------------- + void paint(QPainter* p, const QStyleOptionViewItem& option, const ToggleSpotlightAction* /*action*/) + { + const auto& fm = option.fontMetrics; + const int xPos = (option.rect.height()-fm.height()) / 2; + NativeKeySeqEdit::drawText(xPos, *p, option, ActionDelegate::tr("Toggle Spotlight")); + } + + // --------------------------------------------------------------------------------------------- + QSize sizeHint(const QStyleOptionViewItem& /*opt*/, const ToggleSpotlightAction* /*action*/) + { + return QSize(100,16); + } + } } // end anonymous namespace // ------------------------------------------------------------------------------------------------- @@ -77,6 +93,9 @@ void ActionDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option case Action::Type::CyclePresets: cyclepresets::paint(painter, option, static_cast(item.action.get())); break; + case Action::Type::ToggleSpotlight: + togglespotlight::paint(painter, option, static_cast(item.action.get())); + break; } if (option.state & QStyle::State_HasFocus) { @@ -98,6 +117,8 @@ QSize ActionDelegate::sizeHint(const QStyleOptionViewItem& opt, const QModelInde return keysequence::sizeHint(opt, static_cast(item.action.get())); case Action::Type::CyclePresets: return cyclepresets::sizeHint(opt, static_cast(item.action.get())); + case Action::Type::ToggleSpotlight: + return togglespotlight::sizeHint(opt, static_cast(item.action.get())); } return QStyledItemDelegate::sizeHint(opt, index); @@ -113,8 +134,9 @@ QWidget* ActionDelegate::createEditor(QWidget* parent, const Action* action) con connect(editor, &NativeKeySeqEdit::editingFinished, this, &ActionDelegate::commitAndCloseEditor); return editor; } - case Action::Type::CyclePresets: - // None for now... + case Action::Type::CyclePresets: // None for now... + break; + case Action::Type::ToggleSpotlight: // None for now... break; } return nullptr; @@ -233,6 +255,7 @@ void ActionTypeDelegate::paint(QPainter* painter, const QStyleOptionViewItem& op switch(item.action->type()) { case Action::Type::KeySequence: return Font::Icon::keyboard_4; case Action::Type::CyclePresets: return Font::Icon::connection_8; + case Action::Type::ToggleSpotlight: return Font::Icon::power_on_off_11; } return 0; }(); @@ -263,6 +286,7 @@ void ActionTypeDelegate::actionContextMenu(QWidget* parent, InputMapConfigModel* static std::vector items { {Action::Type::KeySequence, Font::Icon::keyboard_4, tr("Key Sequence")}, {Action::Type::CyclePresets, Font::Icon::connection_8, tr("Cycle Presets")}, + {Action::Type::ToggleSpotlight, Font::Icon::power_on_off_11, tr("Toogle Spotlight")}, }; static bool initIcons = []() diff --git a/src/deviceinput.cc b/src/deviceinput.cc index 612f99de..d5d56e59 100644 --- a/src/deviceinput.cc +++ b/src/deviceinput.cc @@ -104,9 +104,9 @@ QDataStream& operator>>(QDataStream& s, MappedAction& mia) { case Action::Type::CyclePresets: mia.action = std::make_shared(); return mia.action->load(s); - default: - mia.action.reset(); - break; + case Action::Type::ToggleSpotlight: + mia.action = std::make_shared(); + return mia.action->load(s); } return s; } @@ -125,6 +125,9 @@ bool MappedAction::operator==(const MappedAction& o) const case Action::Type::CyclePresets: return (*static_cast(action.get())) == (*static_cast(o.action.get())); + case Action::Type::ToggleSpotlight: + return (*static_cast(action.get())) + == (*static_cast(o.action.get())); } return false; diff --git a/src/deviceinput.h b/src/deviceinput.h index c966b69c..ebf32482 100644 --- a/src/deviceinput.h +++ b/src/deviceinput.h @@ -138,7 +138,12 @@ Q_DECLARE_METATYPE(NativeKeySequence) // ------------------------------------------------------------------------------------------------- struct Action { - enum class Type { KeySequence = 1, CyclePresets = 2 }; + enum class Type { + KeySequence = 1, + CyclePresets = 2, + ToggleSpotlight = 3, + }; + virtual Type type() const = 0; virtual QDataStream& save(QDataStream&) const = 0; virtual QDataStream& load(QDataStream&) = 0; @@ -163,11 +168,22 @@ struct KeySequenceAction : public Action struct CyclePresetsAction : public Action { Type type() const override { return Type::CyclePresets; } - QDataStream& save(QDataStream& s) const override { return s << customSelection; } - QDataStream& load(QDataStream& s) override { return s >> customSelection; } + QDataStream& save(QDataStream& s) const override { return s << placeholder; } + QDataStream& load(QDataStream& s) override { return s >> placeholder; } bool empty() const override { return false; } bool operator==(const CyclePresetsAction&) const { return true; } - bool customSelection = false; + bool placeholder = false; +}; + +// ------------------------------------------------------------------------------------------------- +struct ToggleSpotlightAction : public Action +{ + Type type() const override { return Type::ToggleSpotlight; } + QDataStream& save(QDataStream& s) const override { return s << placeholder; } + QDataStream& load(QDataStream& s) override { return s >> placeholder; } + bool empty() const override { return false; } + bool operator==(const ToggleSpotlightAction&) const { return true; } + bool placeholder = false; }; // ------------------------------------------------------------------------------------------------- diff --git a/src/inputmapconfig.cc b/src/inputmapconfig.cc index d40f9fc6..2e38be2d 100644 --- a/src/inputmapconfig.cc +++ b/src/inputmapconfig.cc @@ -201,6 +201,9 @@ void InputMapConfigModel::setItemActionType(const QModelIndex& idx, Action::Type case Action::Type::CyclePresets: item.action = std::make_shared(); break; + case Action::Type::ToggleSpotlight: + item.action = std::make_shared(); + break; } configureInputMapper(); diff --git a/src/preferencesdlg.cc b/src/preferencesdlg.cc index dc3da17a..dbbcb27d 100644 --- a/src/preferencesdlg.cc +++ b/src/preferencesdlg.cc @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -81,6 +82,10 @@ PreferencesDialog::PreferencesDialog(Settings* settings, Spotlight* spotlight, tabWidget->addTab(new DevicesWidget(settings, spotlight, this), tr("Devices")); tabWidget->addTab(createLogTabWidget(), tr("Log")); + const auto overlayCheckBox = new QCheckBox(this); + overlayCheckBox->setChecked(!settings->overlayDisabled()); + tabWidget->tabBar()->setTabButton(0, QTabBar::ButtonPosition::LeftSide, overlayCheckBox); + const auto btnHBox = new QHBoxLayout; btnHBox->addWidget(m_exitBtn); btnHBox->addStretch(1); @@ -90,7 +95,14 @@ PreferencesDialog::PreferencesDialog(Settings* settings, Spotlight* spotlight, mainVBox->addWidget(tabWidget); mainVBox->addLayout(btnHBox); - connect(settings, &Settings::overlayDisabledChanged, this, [settingsWidget](bool disabled){ + connect(overlayCheckBox, &QCheckBox::toggled, this, [settings](bool checked){ + settings->setOverlayDisabled(!checked); + + }); + + connect(settings, &Settings::overlayDisabledChanged, this, + [overlayCheckBox, settingsWidget](bool disabled){ + overlayCheckBox->setChecked(!disabled); settingsWidget->setDisabled(disabled); }); } diff --git a/src/projecteur-icons-def.h b/src/projecteur-icons-def.h index f2049367..33cd8991 100644 --- a/src/projecteur-icons-def.h +++ b/src/projecteur-icons-def.h @@ -19,6 +19,7 @@ namespace Font keyboard_14 = 0xf10e, // svg/iconmonstr-keyboard-14.svg keyboard_4 = 0xf10f, // svg/iconmonstr-keyboard-4.svg plus_5 = 0xf107, // svg/iconmonstr-plus-5.svg + power_on_off_11 = 0xf115, // svg/iconmonstr-power-on-off-11.svg share_8 = 0xf108, // svg/iconmonstr-share-8.svg target_8 = 0xf110, // svg/iconmonstr-target-8.svg time_19 = 0xf109, // svg/iconmonstr-time-19.svg diff --git a/src/spotlight.cc b/src/spotlight.cc index 8074ab71..4b021922 100644 --- a/src/spotlight.cc +++ b/src/spotlight.cc @@ -149,6 +149,10 @@ int Spotlight::connectDevices() m_settings->loadPreset(lastPreset); } } + else if (action->type() == Action::Type::ToggleSpotlight) + { + m_settings->setOverlayDisabled(!m_settings->overlayDisabled()); + } }); connect(m_settings, &Settings::presetLoaded, this, [](const QString& preset){ From 08346828aacedf54bf5040656ccc10bf458607bd Mon Sep 17 00:00:00 2001 From: Stuart Prescott Date: Tue, 25 Feb 2020 15:26:50 +1100 Subject: [PATCH 05/65] Fix typo of unknown Caught by the QA checker 'lintian'. --- src/logging.cc | 2 +- src/main.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logging.cc b/src/logging.cc index d92452a6..c378a191 100644 --- a/src/logging.cc +++ b/src/logging.cc @@ -154,7 +154,7 @@ namespace logging { case level::warning: return "warning"; case level::error: return "error"; case level::custom: return "default/custom"; - case level::unknown: return "unknwon"; + case level::unknown: return "unknown"; } return ""; } diff --git a/src/main.cc b/src/main.cc index d747b66b..bac854ad 100644 --- a/src/main.cc +++ b/src/main.cc @@ -234,7 +234,7 @@ int main(int argc, char *argv[]) const auto busTypeToString = [](DeviceScan::Device::BusType type) -> QString { if (type == DeviceScan::Device::BusType::Usb) return "USB"; if (type == DeviceScan::Device::BusType::Bluetooth) return "Bluetooth"; - return "unknwon"; + return "unknown"; }; for (const auto& device : result.devices) From ef10cdb710c7bbd3d5ee011fc267f05b1baf0b9c Mon Sep 17 00:00:00 2001 From: Stuart Prescott Date: Sat, 8 Aug 2020 19:10:52 +1000 Subject: [PATCH 06/65] Fix typo of separated --- src/deviceinput.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deviceinput.cc b/src/deviceinput.cc index 612f99de..394c3da8 100644 --- a/src/deviceinput.cc +++ b/src/deviceinput.cc @@ -679,7 +679,7 @@ void InputMapper::addEvents(const input_event* input_events, size_t num) } if (input_events[num-1].type != EV_SYN) { - logWarning(input) << tr("Input mapper expects events seperated by SYN event."); + logWarning(input) << tr("Input mapper expects events separated by SYN event."); return; } else if (num == 1) { logWarning(input) << tr("Ignoring single SYN event received."); From 76c9bd977ddfcb43c9ea3f065e6036713269ea58 Mon Sep 17 00:00:00 2001 From: Stuart Prescott Date: Mon, 24 Feb 2020 23:55:54 +1100 Subject: [PATCH 07/65] Calculate version correctly when git not installed Prevent a FTBFS when building without the git package being installed; cmake rules to describe the release leave VERSION_DISTANCE incorrectly unset. --- cmake/modules/GitVersion.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/modules/GitVersion.cmake b/cmake/modules/GitVersion.cmake index 7bf75acd..47ce7af6 100644 --- a/cmake/modules/GitVersion.cmake +++ b/cmake/modules/GitVersion.cmake @@ -57,7 +57,7 @@ function(get_version_info prefix directory) set(${prefix}_VERSION_PATCH 0) set(${prefix}_VERSION_BRANCH unknown) set(${prefix}_VERSION_FLAG unknown) - set(${prefix}_VERSION_DISTANCE 0) + set(${prefix}_VERSION_DISTANCE 0 PARENT_SCOPE) set(${prefix}_VERSION_STRING 0.0.0-unknown) set(${prefix}_VERSION_ISDIRTY 0 PARENT_SCOPE) @@ -267,6 +267,8 @@ function(get_version_info prefix directory) set(ON_MASTER ON) set(${prefix}_VERSION_FLAG "") set(${prefix}_VERSION_FLAG "" PARENT_SCOPE) + set(${prefix}_VERSION_DISTANCE 0) + set(${prefix}_VERSION_DISTANCE 0 PARENT_SCOPE) endif() set(${prefix}_VERSION_BRANCH "not-within-git-repo" PARENT_SCOPE) endif() From 0453c42b9dca4740d58972fbd73550537ab02716 Mon Sep 17 00:00:00 2001 From: Jahn F Date: Mon, 10 Aug 2020 08:18:39 +0200 Subject: [PATCH 08/65] Updated list of contributors. --- src/aboutdlg.cc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aboutdlg.cc b/src/aboutdlg.cc index 685b35cb..7a068fbd 100644 --- a/src/aboutdlg.cc +++ b/src/aboutdlg.cc @@ -60,6 +60,9 @@ namespace { Contributor("Louie Lu", "mlouielu"), Contributor("fmuelle4711", "fmuelle4711"), Contributor("Deniz Bahadir", "Bagira80"), + Contributor("Tomáš Chvátal", "scarabeusiv"), + Contributor("Brandon Johnson", "dbrandonjohnson"), + Contributor("Stuart Prescott", "llimeht"), }; static std::random_device rd; From 9dc2c62996ec8d2fda1881e134e8233ab76d3c4b Mon Sep 17 00:00:00 2001 From: Stuart Prescott Date: Sat, 8 Aug 2020 19:21:28 +1000 Subject: [PATCH 09/65] Add Appstream metainfo file This is a starting point for an Appstream metainfo file. It probably needs to go via templating to set the installation paths. It also need to get installed by cmake: /usr/share/metainfo/projecteur.metainfo.xml is the usual location. The Id field and filename should normally be {tld}.{vendor}.{product} and that would also be reflected in the name of the .desktop file IIRC. https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html --- cmake/templates/projecteur.metainfo.xml | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 cmake/templates/projecteur.metainfo.xml diff --git a/cmake/templates/projecteur.metainfo.xml b/cmake/templates/projecteur.metainfo.xml new file mode 100644 index 00000000..ce7da28f --- /dev/null +++ b/cmake/templates/projecteur.metainfo.xml @@ -0,0 +1,35 @@ + + + projecteur + Expat + Expat + Projecteur + Virtual pointer for the Logitech Spotlight device + https://github.com/jahnf/Projecteur/ + +

+ Projecteur is a virtual laser pointer for use with inertial + pointers such as the Logitech Spotlight. Projecteur can show + a colored dot, a highlighted circle or a zoom effect to act + as a pointer. The location of the pointer moves in response + to moving the handheld pointer device. The effect is much + like that of a traditional laser pointer, except that it is + captured by recording software and works across multiple screens. +

+
+ + usb:v046DpC53Ed* + + + + Highlight virtual pointer effect + https://raw.githubusercontent.com/jahnf/Projecteur/develop/doc/screenshot-spot.png + + + + AudioVideo + Audio + Midi + Sequencer + +
From 13e096a60ed3316a73bb02c2a4ab0bf52e02afeb Mon Sep 17 00:00:00 2001 From: Stuart Prescott Date: Sat, 8 Aug 2020 19:27:48 +1000 Subject: [PATCH 10/65] Draft manual page This manual page is not complete and elements such as the version string in it should perhaps go via CMake templating. It also needs to be installed via CMake. --- cmake/templates/projecteur.1 | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 cmake/templates/projecteur.1 diff --git a/cmake/templates/projecteur.1 b/cmake/templates/projecteur.1 new file mode 100644 index 00000000..80bd409c --- /dev/null +++ b/cmake/templates/projecteur.1 @@ -0,0 +1,55 @@ +.TH PROJECTEUR "1" "February 2020" "Projecteur 0.7" "User Commands" +.SH NAME +Projecteur \- virtual laser pointer for presentations +.SH SYNOPSIS +.B projecteur +[\fI\,option\/\fR] +.SH DESCRIPTION +Projecteur provides a virtual laser pointer on the screen for use when giving +presentations. The laser pointer can be controlled using a Logitech Spotlight +or similar device. Projecteur supports a "laser pointer" like effect that is +a colored dot on the screen, a "highlight" effect that dims the image except +in the highlighted region, and a "zoom" effect that enlarges part of the +display. +.PP +Projecteur can be configured with a dialog box activated from the system +tray in a supported desktop environment. +.PP +.SH Options +.TP +\fB\-h\fR, \fB\-\-help\fR +Show command line usage. +.TP +\fB\-\-help\-all\fR +Show complete command line usage with all properties. +.TP +\fB\-v\fR, \fB\-\-version\fR +Print application version. +.TP +\fB\-\-cfg\fR \fIFILE\fR +Set custom config file. +.TP +\fB\-d\fR, \fB\-\-device\-scan\fR +Print device\-scan results. +.TP +\fB\-l\fR, \fB\-\-log\-level\fR \fILEVEL\fR +Set log level (dbg,inf,wrn,err). +.TP +\fB\-D\fR \fIDEVICE\fR +Additional accepted device; DEVICE = vendorId:productId +e.g., \fB\-D\fR 04b3:310c; e.g. \fB\-D\fR 0x0c45:0x8101 +.TP +\fB\-c\fR \fICOMMAND\fR|\fIPROPERTY\fR +Send command/property to a running instance. See \fBCommands\fP for +details. +.PP +.SH Commands +.TP +spot=[on|off] +Turn spotlight on/off. +.TP +settings=[show|hide] +Show/hide preferences dialog. +.TP +quit +Quit the running instance. From 2ed347c387e73036172efbc7ae4569259cb664ae Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 11 Aug 2020 14:32:08 +0200 Subject: [PATCH 11/65] Set cmake cache property for build type correctly. --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a2341127..ee3e5f3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,10 @@ endif() if( NOT CMAKE_BUILD_TYPE ) message(STATUS "Setting build type to 'Release' as none was specified.") set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() +set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") + project(Projecteur LANGUAGES CXX) add_compile_options(-Wall -Wextra -Werror) From c442ec2864b166ecc0263502fe10a0ef3f33923e Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 11 Aug 2020 16:32:30 +0200 Subject: [PATCH 12/65] Added additional version month and year information. --- cmake/modules/ArchiveExportInfo.cmake | 1 + cmake/modules/ArchiveVersionInfo.cmake.in | 5 +++-- cmake/modules/GitVersion.cmake | 23 +++++++++++++++++++++-- cmake/modules/GitVersion.h.in | 5 +---- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/cmake/modules/ArchiveExportInfo.cmake b/cmake/modules/ArchiveExportInfo.cmake index f5302b5c..de3edbd3 100644 --- a/cmake/modules/ArchiveExportInfo.cmake +++ b/cmake/modules/ArchiveExportInfo.cmake @@ -3,4 +3,5 @@ set(GIT_EXPORT_VERSION_SHORTHASH "$Format:%h$") set(GIT_EXPORT_VERSION_FULLHASH "$Format:%H$") set(GIT_EXPORT_VERSION_BRANCH "$Format:%D$") # needs parsing in cmake... +set(GIT_EXPORT_VERSION_DATE_MONTH_YEAR "$Format:%cd$") set(HAS_GIT_EXPORT_INFO 1) diff --git a/cmake/modules/ArchiveVersionInfo.cmake.in b/cmake/modules/ArchiveVersionInfo.cmake.in index 322d8e02..f83b832c 100644 --- a/cmake/modules/ArchiveVersionInfo.cmake.in +++ b/cmake/modules/ArchiveVersionInfo.cmake.in @@ -1,5 +1,5 @@ -# Auto generated archive version information -# Included in created source archives +# Auto generated archive version information +# Included in created source archives set(@prefix@_VERSION_MAJOR "@VERSION_MAJOR@") set(@prefix@_VERSION_MINOR "@VERSION_MINOR@") @@ -11,4 +11,5 @@ set(@prefix@_VERSION_FULLHASH "@VERSION_FULLHASH@") set(@prefix@_VERSION_STRING "@VERSION_STRING@") set(@prefix@_VERSION_ISDIRTY "@VERSION_ISDIRTY@") set(@prefix@_VERSION_BRANCH "@VERSION_BRANCH@") +set(@prefix@_VERSION_DATE_MONTH_YEAR "@VERSION_DATE_MONTH_YEAR@") set(@prefix@_VERSION_SUCCESS 1) diff --git a/cmake/modules/GitVersion.cmake b/cmake/modules/GitVersion.cmake index 47ce7af6..6fb1ac6e 100644 --- a/cmake/modules/GitVersion.cmake +++ b/cmake/modules/GitVersion.cmake @@ -60,6 +60,7 @@ function(get_version_info prefix directory) set(${prefix}_VERSION_DISTANCE 0 PARENT_SCOPE) set(${prefix}_VERSION_STRING 0.0.0-unknown) set(${prefix}_VERSION_ISDIRTY 0 PARENT_SCOPE) + set(${prefix}_VERSION_DATE_MONTH_YEAR "Unknown date" PARENT_SCOPE) if("${${prefix}_OR_VERSION_MAJOR}" STREQUAL "") set(${prefix}_OR_VERSION_MAJOR 0) @@ -119,6 +120,21 @@ function(get_version_info prefix directory) endif() endif() + # Get committer date + set(ENV_LC_TIME ENV{LC_TIME}) + set(ENV{LC_TIME} C) # we want to enforce C locale for date formatting + execute_process(COMMAND ${GIT_EXECUTABLE} show -s --format=%cd "--date=format:%B %Y" + RESULT_VARIABLE result + OUTPUT_VARIABLE GIT_DATE_MONTH_YEAR + ERROR_VARIABLE error_out + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${directory} + ) + set(ENV{LC_TIME} ENV_LC_TIME) # Reset environment variable to previous value + if(result EQUAL 0) + set(${prefix}_VERSION_DATE_MONTH_YEAR "${GIT_DATE_MONTH_YEAR}" PARENT_SCOPE) + endif() + # Check the branch we are on execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD RESULT_VARIABLE result @@ -129,7 +145,7 @@ function(get_version_info prefix directory) ) if(result EQUAL 0) - if("${GIT_BRANCH}" STREQUAL "HEAD" + if("${GIT_BRANCH}" STREQUAL "HEAD" AND NOT "$ENV{TRAVIS_BRANCH}" STREQUAL "") set(GIT_BRANCH "$ENV{TRAVIS_BRANCH}") endif() @@ -367,6 +383,7 @@ function(add_version_info_custom_prefix target prefix directory) set(${prefix}_VERSION_SHORTHASH "${GIT_EXPORT_VERSION_SHORTHASH}") set(${prefix}_VERSION_FULLHASH "${GIT_EXPORT_VERSION_FULLHASH}") set(${prefix}_VERSION_BRANCH "${GIT_EXPORT_VERSION_BRANCH}") + set(${prefix}_VERSION_VERSION_DATE_MONTH_YEAR "${GIT_EXPORT_VERSION_DATE_MONTH_YEAR}") if("${${prefix}_VERSION_BRANCH}" MATCHES ".*[ \t]+[->]+[\t ]+(.*)([,]?.*)") set(${prefix}_VERSION_BRANCH "${CMAKE_MATCH_1}") elseif("${${prefix}_VERSION_BRANCH}" MATCHES ".*,[ \t](.*)") @@ -430,7 +447,8 @@ function(add_version_info_custom_prefix target prefix directory) set(VERSION_STRING ${${prefix}_VERSION_STRING}) set(VERSION_ISDIRTY ${${prefix}_VERSION_ISDIRTY}) set(VERSION_BRANCH ${${prefix}_VERSION_BRANCH}) - set_target_properties(${target} PROPERTIES + set(VERSION_DATE_MONTH_YEAR ${${prefix}_VERSION_DATE_MONTH_YEAR}) + set_target_properties(${target} PROPERTIES VERSION_MAJOR "${VERSION_MAJOR}" VERSION_MINOR "${VERSION_MINOR}" VERSION_PATCH "${VERSION_PATCH}" @@ -441,6 +459,7 @@ function(add_version_info_custom_prefix target prefix directory) VERSION_STRING "${VERSION_STRING}" VERSION_ISDIRTY "${VERSION_ISDIRTY}" VERSION_BRANCH "${VERSION_BRANCH}" + VERSION_DATE_MONTH_YEAR "${VERSION_DATE_MONTH_YEAR}" ) set(TARGET ${prefix}) diff --git a/cmake/modules/GitVersion.h.in b/cmake/modules/GitVersion.h.in index ed42d0a9..16b7431d 100644 --- a/cmake/modules/GitVersion.h.in +++ b/cmake/modules/GitVersion.h.in @@ -1,5 +1,4 @@ -#ifndef @TARGET@_GITVERSION_H -#define @TARGET@_GITVERSION_H +#pragma once namespace @TARGET@ { const char* version_string(); @@ -13,5 +12,3 @@ namespace @TARGET@ { bool version_isdirty(); const char* version_branch(); } - -#endif From c6f5e52271bb24274f751a5b16003c0f4bd381c1 Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 11 Aug 2020 16:56:52 +0200 Subject: [PATCH 13/65] Install projecteur man page. --- CMakeLists.txt | 17 ++++++++++++----- cmake/modules/ArchiveExportInfo.cmake | 2 +- cmake/modules/GitVersion.cmake | 1 + cmake/templates/projecteur.1 | 4 ++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee3e5f3c..b0ff5e47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,13 +196,20 @@ set_property(TARGET projecteur APPEND PROPERTY SOURCES "${CMAKE_CURRENT_BINARY_D configure_file("55-projecteur.rules.in" "55-projecteur.rules" @ONLY) install(FILES "${OUTDIR}/55-projecteur.rules" DESTINATION ${CMAKE_INSTALL_UDEVRULESDIR}/) -install(FILES icons/projecteur-tray.svg DESTINATION /usr/share/icons/hicolor/48x48/apps/ RENAME projecteur.svg) -install(FILES icons/projecteur-tray.svg DESTINATION /usr/share/icons/hicolor/64x64/apps/ RENAME projecteur.svg) -install(FILES icons/projecteur-tray.svg DESTINATION /usr/share/icons/hicolor/128x128/apps/ RENAME projecteur.svg) -install(FILES icons/projecteur-tray.svg DESTINATION /usr/share/icons/hicolor/256x256/apps/ RENAME projecteur.svg) +install(FILES icons/projecteur-tray.svg DESTINATION share/icons/hicolor/48x48/apps/ RENAME projecteur.svg) +install(FILES icons/projecteur-tray.svg DESTINATION share/icons/hicolor/64x64/apps/ RENAME projecteur.svg) +install(FILES icons/projecteur-tray.svg DESTINATION share/icons/hicolor/128x128/apps/ RENAME projecteur.svg) +install(FILES icons/projecteur-tray.svg DESTINATION share/icons/hicolor/256x256/apps/ RENAME projecteur.svg) + +# Set variables for file configurations +get_target_property(VERSION_STRING projecteur VERSION_STRING) +get_target_property(VERSION_DATE_MONTH_YEAR projecteur VERSION_DATE_MONTH_YEAR) configure_file("${TMPLDIR}/Projecteur.desktop.in" "projecteur.desktop" @ONLY) -install(FILES "${OUTDIR}/projecteur.desktop" DESTINATION /usr/share/applications/) +install(FILES "${OUTDIR}/projecteur.desktop" DESTINATION share/applications/) + +configure_file("${TMPLDIR}/projecteur.1" "projecteur.1" @ONLY) +install(FILES "${OUTDIR}/projecteur.1" DESTINATION share/man/man1/) configure_file("${TMPLDIR}/preinst.in" "pkg/scripts/preinst" @ONLY) configure_file("${TMPLDIR}/postinst.in" "pkg/scripts/postinst" @ONLY) diff --git a/cmake/modules/ArchiveExportInfo.cmake b/cmake/modules/ArchiveExportInfo.cmake index de3edbd3..e138c451 100644 --- a/cmake/modules/ArchiveExportInfo.cmake +++ b/cmake/modules/ArchiveExportInfo.cmake @@ -3,5 +3,5 @@ set(GIT_EXPORT_VERSION_SHORTHASH "$Format:%h$") set(GIT_EXPORT_VERSION_FULLHASH "$Format:%H$") set(GIT_EXPORT_VERSION_BRANCH "$Format:%D$") # needs parsing in cmake... -set(GIT_EXPORT_VERSION_DATE_MONTH_YEAR "$Format:%cd$") +set(GIT_EXPORT_VERSION_DATE_MONTH_YEAR "$Format:%cs$") set(HAS_GIT_EXPORT_INFO 1) diff --git a/cmake/modules/GitVersion.cmake b/cmake/modules/GitVersion.cmake index 6fb1ac6e..9145553f 100644 --- a/cmake/modules/GitVersion.cmake +++ b/cmake/modules/GitVersion.cmake @@ -383,6 +383,7 @@ function(add_version_info_custom_prefix target prefix directory) set(${prefix}_VERSION_SHORTHASH "${GIT_EXPORT_VERSION_SHORTHASH}") set(${prefix}_VERSION_FULLHASH "${GIT_EXPORT_VERSION_FULLHASH}") set(${prefix}_VERSION_BRANCH "${GIT_EXPORT_VERSION_BRANCH}") + set(${prefix}_VERSION_DISTANCE 0) set(${prefix}_VERSION_VERSION_DATE_MONTH_YEAR "${GIT_EXPORT_VERSION_DATE_MONTH_YEAR}") if("${${prefix}_VERSION_BRANCH}" MATCHES ".*[ \t]+[->]+[\t ]+(.*)([,]?.*)") set(${prefix}_VERSION_BRANCH "${CMAKE_MATCH_1}") diff --git a/cmake/templates/projecteur.1 b/cmake/templates/projecteur.1 index 80bd409c..dbbf7351 100644 --- a/cmake/templates/projecteur.1 +++ b/cmake/templates/projecteur.1 @@ -1,9 +1,9 @@ -.TH PROJECTEUR "1" "February 2020" "Projecteur 0.7" "User Commands" +.TH PROJECTEUR "1" "@VERSION_DATE_MONTH_YEAR@" "Projecteur @VERSION_STRING@" "User Commands" .SH NAME Projecteur \- virtual laser pointer for presentations .SH SYNOPSIS .B projecteur -[\fI\,option\/\fR] +[\fI\,OPTION\/\fR]... .SH DESCRIPTION Projecteur provides a virtual laser pointer on the screen for use when giving presentations. The laser pointer can be controlled using a Logitech Spotlight From cfab390c423568d71707721f8b17d93a6c64be42 Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 11 Aug 2020 16:59:28 +0200 Subject: [PATCH 14/65] Adjust metainfo categories. --- cmake/templates/projecteur.metainfo.xml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmake/templates/projecteur.metainfo.xml b/cmake/templates/projecteur.metainfo.xml index ce7da28f..9ea4636b 100644 --- a/cmake/templates/projecteur.metainfo.xml +++ b/cmake/templates/projecteur.metainfo.xml @@ -27,9 +27,7 @@ - AudioVideo - Audio - Midi - Sequencer + Office + Presentation From 05d69aad9ba708c7969dbb0ad85d9bb6f3ba484b Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 11 Aug 2020 17:04:50 +0200 Subject: [PATCH 15/65] Configure and install appstream metadata --- CMakeLists.txt | 6 ++++-- cmake/templates/projecteur.metainfo.xml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b0ff5e47..fd4be5fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,6 +204,7 @@ install(FILES icons/projecteur-tray.svg DESTINATION share/icons/hicolor/256x256/ # Set variables for file configurations get_target_property(VERSION_STRING projecteur VERSION_STRING) get_target_property(VERSION_DATE_MONTH_YEAR projecteur VERSION_DATE_MONTH_YEAR) +set(HOMEPAGE "https://github.com/jahnf/Projecteur") configure_file("${TMPLDIR}/Projecteur.desktop.in" "projecteur.desktop" @ONLY) install(FILES "${OUTDIR}/projecteur.desktop" DESTINATION share/applications/) @@ -211,11 +212,12 @@ install(FILES "${OUTDIR}/projecteur.desktop" DESTINATION share/applications/) configure_file("${TMPLDIR}/projecteur.1" "projecteur.1" @ONLY) install(FILES "${OUTDIR}/projecteur.1" DESTINATION share/man/man1/) +configure_file("${TMPLDIR}/projecteur.metainfo.xml" "projecteur.metainfo.xml" @ONLY) +install(FILES "${OUTDIR}/projecteur.metainfo.xml" DESTINATION share/metainfo/) + configure_file("${TMPLDIR}/preinst.in" "pkg/scripts/preinst" @ONLY) configure_file("${TMPLDIR}/postinst.in" "pkg/scripts/postinst" @ONLY) -set(HOMEPAGE "https://github.com/jahnf/Projecteur") - # --- Linux packaging --- include(LinuxPackaging) diff --git a/cmake/templates/projecteur.metainfo.xml b/cmake/templates/projecteur.metainfo.xml index 9ea4636b..119ccd84 100644 --- a/cmake/templates/projecteur.metainfo.xml +++ b/cmake/templates/projecteur.metainfo.xml @@ -5,7 +5,7 @@ Expat Projecteur Virtual pointer for the Logitech Spotlight device - https://github.com/jahnf/Projecteur/ + @HOMEPAGE@

Projecteur is a virtual laser pointer for use with inertial From 01ad097758540c7290571ddd2d8a309244f4ab90 Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 11 Aug 2020 18:14:31 +0200 Subject: [PATCH 16/65] Generate fallback date when git is not available. --- cmake/modules/GitVersion.cmake | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmake/modules/GitVersion.cmake b/cmake/modules/GitVersion.cmake index 9145553f..2c9dd1ea 100644 --- a/cmake/modules/GitVersion.cmake +++ b/cmake/modules/GitVersion.cmake @@ -57,10 +57,11 @@ function(get_version_info prefix directory) set(${prefix}_VERSION_PATCH 0) set(${prefix}_VERSION_BRANCH unknown) set(${prefix}_VERSION_FLAG unknown) + set(${prefix}_VERSION_DISTANCE 0) set(${prefix}_VERSION_DISTANCE 0 PARENT_SCOPE) set(${prefix}_VERSION_STRING 0.0.0-unknown) set(${prefix}_VERSION_ISDIRTY 0 PARENT_SCOPE) - set(${prefix}_VERSION_DATE_MONTH_YEAR "Unknown date" PARENT_SCOPE) + set(${prefix}_VERSION_DATE_MONTH_YEAR "" PARENT_SCOPE) if("${${prefix}_OR_VERSION_MAJOR}" STREQUAL "") set(${prefix}_OR_VERSION_MAJOR 0) @@ -383,8 +384,7 @@ function(add_version_info_custom_prefix target prefix directory) set(${prefix}_VERSION_SHORTHASH "${GIT_EXPORT_VERSION_SHORTHASH}") set(${prefix}_VERSION_FULLHASH "${GIT_EXPORT_VERSION_FULLHASH}") set(${prefix}_VERSION_BRANCH "${GIT_EXPORT_VERSION_BRANCH}") - set(${prefix}_VERSION_DISTANCE 0) - set(${prefix}_VERSION_VERSION_DATE_MONTH_YEAR "${GIT_EXPORT_VERSION_DATE_MONTH_YEAR}") + set(${prefix}_VERSION_DATE_MONTH_YEAR "${GIT_EXPORT_VERSION_DATE_MONTH_YEAR}") if("${${prefix}_VERSION_BRANCH}" MATCHES ".*[ \t]+[->]+[\t ]+(.*)([,]?.*)") set(${prefix}_VERSION_BRANCH "${CMAKE_MATCH_1}") elseif("${${prefix}_VERSION_BRANCH}" MATCHES ".*,[ \t](.*)") @@ -449,6 +449,12 @@ function(add_version_info_custom_prefix target prefix directory) set(VERSION_ISDIRTY ${${prefix}_VERSION_ISDIRTY}) set(VERSION_BRANCH ${${prefix}_VERSION_BRANCH}) set(VERSION_DATE_MONTH_YEAR ${${prefix}_VERSION_DATE_MONTH_YEAR}) + + # Fallback + if("${VERSION_DATE_MONTH_YEAR}" STREQUAL "") + string(TIMESTAMP VERSION_DATE_MONTH_YEAR "%b %Y") + endif() + set_target_properties(${target} PROPERTIES VERSION_MAJOR "${VERSION_MAJOR}" VERSION_MINOR "${VERSION_MINOR}" From 6c68697a53312fa80aaf38cb8bbd4444adb17cf8 Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 11 Aug 2020 18:42:55 +0200 Subject: [PATCH 17/65] Gzip man page. --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fd4be5fe..a8ad1dbe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,8 +209,16 @@ set(HOMEPAGE "https://github.com/jahnf/Projecteur") configure_file("${TMPLDIR}/Projecteur.desktop.in" "projecteur.desktop" @ONLY) install(FILES "${OUTDIR}/projecteur.desktop" DESTINATION share/applications/) +# Configure man page and gzip it. configure_file("${TMPLDIR}/projecteur.1" "projecteur.1" @ONLY) -install(FILES "${OUTDIR}/projecteur.1" DESTINATION share/man/man1/) +find_program(GZIP_EXECUTABLE gzip) +add_custom_command( + OUTPUT ${OUTDIR}/projecteur.1.gz + COMMAND ${GZIP_EXECUTABLE} -9f "${OUTDIR}/projecteur.1" + WORKING_DIRECTORY ${OUTDIR} +) +add_custom_target(gzip-manpage ALL DEPENDS "${OUTDIR}/projecteur.1.gz") +install(FILES "${OUTDIR}/projecteur.1.gz" DESTINATION share/man/man1/) configure_file("${TMPLDIR}/projecteur.metainfo.xml" "projecteur.metainfo.xml" @ONLY) install(FILES "${OUTDIR}/projecteur.metainfo.xml" DESTINATION share/metainfo/) From bed4968d24d532c84246e6fc6eaa5c20846eef94 Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 12 Aug 2020 17:51:46 +0200 Subject: [PATCH 18/65] Add CMake option to disable package targets and fix compiler warning/ error when building without QDBus. #98 --- CMakeLists.txt | 34 +++++++++++++++++++--------------- src/linuxdesktop.cc | 1 + 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a2341127..92535504 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,21 +211,25 @@ set(HOMEPAGE "https://github.com/jahnf/Projecteur") # --- Linux packaging --- include(LinuxPackaging) -# Add 'source-archive' target -add_source_archive_target(projecteur) - -# Add 'dist-package' target: Creates a deb/rpm/tgz package depending on the current Linux distribution -add_dist_package_target( - PROJECT "${CMAKE_PROJECT_NAME}" - TARGET projecteur - DESCRIPTION_BRIEF "Linux/X11 application for the Logitech Spotlight device." - DESCRIPTION_FULL "Linux/X11 application for the Logitech Spotlight device.\nHomepage: ${HOMEPAGE}" - CONTACT "Jahn Fuchs " - HOMEPAGE "${HOMEPAGE}" - DEBIAN_SECTION "utils" - PREINST_SCRIPT "${OUTDIR}/pkg/scripts/preinst" - POSTINST_SCRIPT "${OUTDIR}/pkg/scripts/postinst" -) +option(PACKAGE_TARGETS "Create packaging build targets" ON) + +if(PACKAGE_TARGETS) + # Add 'source-archive' target + add_source_archive_target(projecteur) + + # Add 'dist-package' target: Creates a deb/rpm/tgz package depending on the current Linux distribution + add_dist_package_target( + PROJECT "${CMAKE_PROJECT_NAME}" + TARGET projecteur + DESCRIPTION_BRIEF "Linux/X11 application for the Logitech Spotlight device." + DESCRIPTION_FULL "Linux/X11 application for the Logitech Spotlight device.\nHomepage: ${HOMEPAGE}" + CONTACT "Jahn Fuchs " + HOMEPAGE "${HOMEPAGE}" + DEBIAN_SECTION "utils" + PREINST_SCRIPT "${OUTDIR}/pkg/scripts/preinst" + POSTINST_SCRIPT "${OUTDIR}/pkg/scripts/postinst" + ) +endif() option(ENABLE_IWYU "Enable Include-What-You-Use" OFF) find_program(iwyu_path NAMES include-what-you-use iwyu) diff --git a/src/linuxdesktop.cc b/src/linuxdesktop.cc index 4fc64bd0..f33d661e 100644 --- a/src/linuxdesktop.cc +++ b/src/linuxdesktop.cc @@ -139,6 +139,7 @@ QPixmap LinuxDesktop::grabScreenWayland(QScreen* screen) const } return pm.isNull() ? pm : pm.copy(screen->geometry()); #else + Q_UNUSED(screen); logWarning(desktop) << tr("Projecteur was compiled without Qt DBus. Currently zoom on Wayland is " "only supported via DBus on KDE and GNOME."); return QPixmap(); From 50ab73f72967b8e52cb6add402108bfb94b23ebd Mon Sep 17 00:00:00 2001 From: Jahn Date: Thu, 13 Aug 2020 07:12:28 +0200 Subject: [PATCH 19/65] Added all command line options to manual page. --- cmake/templates/projecteur.1 | 68 +++++++++++++++++++++++++++++++++--- src/main.cc | 2 +- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/cmake/templates/projecteur.1 b/cmake/templates/projecteur.1 index dbbf7351..0054b278 100644 --- a/cmake/templates/projecteur.1 +++ b/cmake/templates/projecteur.1 @@ -26,6 +26,9 @@ Show complete command line usage with all properties. \fB\-v\fR, \fB\-\-version\fR Print application version. .TP +\fB\-f\fR +Print detailed application version information. +.TP \fB\-\-cfg\fR \fIFILE\fR Set custom config file. .TP @@ -33,15 +36,27 @@ Set custom config file. Print device\-scan results. .TP \fB\-l\fR, \fB\-\-log\-level\fR \fILEVEL\fR -Set log level (dbg,inf,wrn,err). +Set log level, where LEVEL is one of \fBdbg\fR, \fBinf\fR, \fBwrn\fR, \fBerr\fR .TP \fB\-D\fR \fIDEVICE\fR Additional accepted device; DEVICE = vendorId:productId -e.g., \fB\-D\fR 04b3:310c; e.g. \fB\-D\fR 0x0c45:0x8101 +e.g., \fB\-D\fR 04b3:310c; e.g. \fB\-D\fR 0x0c45:0x8101; +This option can be used multiple times and works in connection with the +\fB\-\-device\-scan\fP option. .TP \fB\-c\fR \fICOMMAND\fR|\fIPROPERTY\fR -Send command/property to a running instance. See \fBCommands\fP for -details. +Send command/property to a running instance. See \fBCommands\fP and +\fBProperties\fP for details. This option can be use multiple times. +.TP +\fB\-\-disable-uinput\fR +Disable uinput support. +.TP +\fB\-\-show-dialog\fR +Show preferences dialog on application start. +.TP +\fB\-m\fR, \fB\-\-minimize-only\fR +Only allow minimizing the dialog. Useful for desktop environments that do not +have a system tray. .PP .SH Commands .TP @@ -51,5 +66,50 @@ Turn spotlight on/off. settings=[show|hide] Show/hide preferences dialog. .TP +preset=NAME +Set a preset. +.TP quit Quit the running instance. +.PP +.SH Properties +.TP +spot.size=[Integer] (5 ... 100) +.TP +spot.rotation=[Double] (0 ... 360) +.TP +spot.shape=[Value] (Circle, Square, Star, Ngon) +.TP +spot.shape.square.radius=[Integer] (0 ... 100) +.TP +spot.shape.star.points=[Integer] (3 ... 100) +.TP +spot.shape.star.innerradius=[Integer] (5 ... 100) +.TP +spot.shape.ngon.sides=[Integer] (3 ... 100) +.TP +shade=[Bool] (false, true) +.TP +shade.opacity=[Double] (0 ... 1) +.TP +shade.color=[Color] (HTML-color; #RRGGBB) +.TP +dot=[Bool] (false, true) +.TP +dot.size=[Integer] (3 ... 100) +.TP +dot.color=[Color] (HTML-color; #RRGGBB) +.TP +dot.opacity=[Double] (0 ... 1) +.TP +border=[Bool] (false, true) +.TP +border.size=[Integer] (0 ... 100) +.TP +border.color=[Color] (HTML-color; #RRGGBB) +.TP +border.opacity=[Double] (0 ... 1) +.TP +zoom=[Bool] (false, true) +.TP +zoom.factor=[Double] (1.5 ... 20) diff --git a/src/main.cc b/src/main.cc index bac854ad..ca02b3b4 100644 --- a/src/main.cc +++ b/src/main.cc @@ -94,7 +94,7 @@ int main(int argc, char *argv[]) { print() << QCoreApplication::applicationName() << " " << projecteur::version_string() << std::endl; - print() << "Usage: projecteur [option]" << std::endl; + print() << "Usage: projecteur [OPTION]..." << std::endl; print() << ""; print() << " -h, --help " << helpOption.description(); print() << " --help-all " << fullHelpOption.description(); From 639e50c6430b7c649190fac35a68587bcc3aa08b Mon Sep 17 00:00:00 2001 From: Lumnicence <67649224+Lumnicence@users.noreply.github.com> Date: Sun, 6 Sep 2020 07:35:55 -0400 Subject: [PATCH 20/65] fixed one typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d0ce77d7..7bd5bb58 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ See **[Download](#download)** section for binary packages. I saw the Logitech Spotlight device in action at a conference and liked it immediately. Unfortunately as in a lot of cases, software is only provided for Windows and Mac. The device itself works just fine on Linux, but the cool spotlight feature is -done by additional software. +only available using additional software. So here it is: a Linux application for the Logitech Spotlight. From c7c94bd62f49b481266234c36da00e9721d6e101 Mon Sep 17 00:00:00 2001 From: Lumnicence <67649224+Lumnicence@users.noreply.github.com> Date: Sun, 6 Sep 2020 08:26:10 -0400 Subject: [PATCH 21/65] Fixes to the README file to make it more clear --- README.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7bd5bb58..e8ea9590 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,10 @@ So here it is: a Linux application for the Logitech Spotlight. ## Features * Configurable desktop spotlight - * _shade color_, _opacity_, _cursor_, _border_, _center dot_ and different _shapes_. - * Zoom (magnifier) functionality. + * _shade color_, _opacity_, _cursor_, _border_, _center dot_ and different _shapes_ + * Zoom (magnifier) functionality * Multiple screen support -* Support of devices besides the Logitech Spotlight (see [Device Support](#device-support)) +* Support of devices besyond the Logitech Spotlight (see [Device Support](#device-support)) * Button mapping: * Map any button on the device to (almost) any keyboard combination. * Switch between (cycle through) custom spotlight presets. @@ -65,11 +65,11 @@ application yourself, make sure you have the correct udev rules installed With a connection via the USB Dongle Receiver or via Bluetooth, the Logitech Spotlight device will be detected by Linux as a HID device with mouse and keyboard events. -As mouse events the device sends relative cursor movements and left button presses. +As mouse events, the device sends relative cursor movements and left button presses. Acting as a keyboard, the device basically just sends left and right arrow key press -events when forward or back on the device is pressed. +events when forward or back is pressed on the device. -The mouse move events of device is what we are mainly interested in. Since the device is +The mouse move events of the device is what we are mainly interested in. Since the device is already detected as a mouse input device and able to move the cursor, we simply detect if the Spotlight device is sending mouse move events. If it is sending mouse events, we will 'turn on' the desktop spot (virtual laser). @@ -138,10 +138,10 @@ file in this repository: `55-projecteur.rules.in` * Most recent systems (using systemd) will automatically pick up the rule. If not, run `sudo udevadm control --reload-rules` and `sudo udevadm trigger` to load the rules without a reboot. -* After that the input devices from the Logitech USB Receiver (but also the Bluetooth device) +* After that, the input devices from the Logitech USB Receiver (but also the Bluetooth device) in /dev/input should be readable/writable by you. (See also about [device detection](#device-shows-as-not-connected)) -* When building against the Qt version that comes with your distribution's packages +* When building against the Qt version that comes with your distribution's packages, you might need to install some additional QML module packages. For example this is the case for Ubuntu, where you need to install the packages `qml-module-qtgraphicaleffects`, `qml-module-qtquick-window2`, `qml-modules-qtquick2` and @@ -193,7 +193,7 @@ Besides the _Logitech Spotlight_, the following devices are currently supported #### Compile Time Besides the Logitech Spotlight, similar devices can be used and are supported. -Additional devices can be added to `devices.conf`. At CMake configuration time +Additional devices can be added to `devices.conf`. At CMake configuration time, the project will be configured to support these devices and also create entries for them in the generated udev-rule file. @@ -212,7 +212,7 @@ sure the device is accessible (via udev rules). #### Opaque Spotlight / No Transparency -To be able to show transparent windows a **compositing manager** is necessary. If there is no +To be able to show transparent windows, a **compositing manager** is necessary. If there is no compositing manager running you will see the spotlight overlay as an opaque window. * On **KDE** it might be necessary to turn on Desktop effects to allow transparent windows. @@ -239,11 +239,11 @@ GNOME extension to have a system tray that can show the _Projecteur_ tray icon #### Zoom is not updated while spotlight is shown -That is due to the fact how the zoom currently works. A screenshot is taken shortly before the +Zoom does not update while spotlight is shown due to how the zoom currently works. A screenshot is taken shortly before the overlay window is shown, and then a magnified section is shown wherever the mouse/spotlight is. If the zoom would be updated while the overlay window is shown, the overlay window it self would -show up in the magnified section. That is a general problem, that also other magnifier tools face, -although they can get around the problem by showing the magnified content rectangle always in the +show up in the magnified section. That is a general problem that other magnifier tools also face, +although they get around the problem by showing the magnified content rectangle always in the same position on the screen. #### Wayland @@ -261,7 +261,7 @@ Using Wayland-EGL On Wayland the Zoom feature is currently only implemented on KDE and GNOME. This is done with the help of their respective DBus interfaces for screen capturing. On other environments with -Wayland, the zoom feature is currently not supported. +Wayland, the zoom feature is not currently supported. #### Device shows as not connected @@ -274,7 +274,7 @@ If the device shows as not connected, there are some things you can do: * Manually on the shell: Check if the device is detected by the Linux system: Run `cat /proc/bus/input/devices | grep -A 5 "Vendor=046d"` \ This should show one or multiple spotlight devices (among other Logitech devices) - * Check you the corresponding `/dev/input/event??` device file is readable by you. \ + * Check that the corresponding `/dev/input/event??` device file is readable by you. \ Example: `test -r /dev/input/event19 && echo "SUCCESS" || echo "NOT readable"` * Make sure you don't have conflicting udev rules installed, e.g. first you installed the udev rule yourself and later you used the automatically built Linux packages to From 99ad9b2c725553234cd24c3224e2eee2739bf77a Mon Sep 17 00:00:00 2001 From: Lumnicence <67649224+Lumnicence@users.noreply.github.com> Date: Mon, 7 Sep 2020 09:41:47 -0400 Subject: [PATCH 22/65] tiny typo --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e8ea9590..351c8c5c 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ So here it is: a Linux application for the Logitech Spotlight. * _shade color_, _opacity_, _cursor_, _border_, _center dot_ and different _shapes_ * Zoom (magnifier) functionality * Multiple screen support -* Support of devices besyond the Logitech Spotlight (see [Device Support](#device-support)) +* Support of devices beyond the Logitech Spotlight (see [Device Support](#device-support)) * Button mapping: * Map any button on the device to (almost) any keyboard combination. * Switch between (cycle through) custom spotlight presets. @@ -69,7 +69,7 @@ As mouse events, the device sends relative cursor movements and left button pres Acting as a keyboard, the device basically just sends left and right arrow key press events when forward or back is pressed on the device. -The mouse move events of the device is what we are mainly interested in. Since the device is +The mouse move events of the device are what we are mainly interested in. Since the device is already detected as a mouse input device and able to move the cursor, we simply detect if the Spotlight device is sending mouse move events. If it is sending mouse events, we will 'turn on' the desktop spot (virtual laser). @@ -205,7 +205,7 @@ command line option. Example: `projecteur -D 04b3:310c` This will enable devices within _Projecteur_ and the application will try to -connect to that device if it is detected. It is up to the user though to make +connect to that device if it is detected. It is, however, up to the user to make sure the device is accessible (via udev rules). ### Troubleshooting @@ -269,7 +269,7 @@ If the device shows as not connected, there are some things you can do: * Check for devices with _Projecteur_'s command line option `-d` or `--device-scan` option. This will show you a list of all supported and detected devices and also if - they are readable/writable. If a detected device is not readable/writable it is an indicator, + they are readable/writable. If a detected device is not readable/writable, it is an indicator that there is something wrong with the installed _udev_ rules. * Manually on the shell: Check if the device is detected by the Linux system: Run `cat /proc/bus/input/devices | grep -A 5 "Vendor=046d"` \ From ff9af0b598ba8621e5a248c9ef72bf929fd98b65 Mon Sep 17 00:00:00 2001 From: Jahn Date: Mon, 7 Sep 2020 16:32:05 +0200 Subject: [PATCH 23/65] Add documentation on global shortcuts #100 --- .gitignore | 1 + README.md | 23 ++++++++++++++++++++++- src/aboutdlg.cc | 4 ++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a76d8c6e..c85722f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ CMakeLists.txt.user* .vscode .idea +*.code-workspace build build/* icons/icon-font/output/ diff --git a/README.md b/README.md index 351c8c5c..9005cb2b 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,28 @@ Usage: projecteur [option] quit Quit the running instance. ``` -All the properties that can be set via the command line, are listed with the `--help-all` option. +A complete list the properties that can be set via the command line, can be listed with the +`--help-all` option or can also be found on the man pagers with newer versions of +_Projecteur_ (`man projecteur`). + +#### Scriptability + +_Projecteur_ allows you to set almost all aspects of the spotlight via the command line +for a running instance. + +Example: +```bash +# Set showing the border to true +projecteur -c border=true +# Set the border color to red +projecteur -c border.color=#ff0000 +``` + +While _Projecteur_ does not provide global keyboard shortcuts, the command line options +can but utilized for that. For instance, if you like to use _Projecteur_ as a tool while sharing +your screen in a video call without additional presenter hardware , you can assign global +shortcuts in your window manager (e.g. GNOME) to run the commands +`projecteur -c spot=on` and `projecteur -c spot=off`. ### Device Support diff --git a/src/aboutdlg.cc b/src/aboutdlg.cc index 7a068fbd..b7cf63cf 100644 --- a/src/aboutdlg.cc +++ b/src/aboutdlg.cc @@ -63,10 +63,10 @@ namespace { Contributor("Tomáš Chvátal", "scarabeusiv"), Contributor("Brandon Johnson", "dbrandonjohnson"), Contributor("Stuart Prescott", "llimeht"), + Contributor("Crista Renouard", "Lumnicence"), }; - static std::random_device rd; - static std::mt19937 g(rd()); + static std::mt19937 g(std::random_device{}()); std::shuffle(contributors.begin(), contributors.end(), g); QStringList contributorsHtml; From c46413974c2aac32eb28e7e528f28b727e709274 Mon Sep 17 00:00:00 2001 From: Jahn Date: Mon, 7 Sep 2020 16:36:43 +0200 Subject: [PATCH 24/65] Update README.md #100 --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9005cb2b..e3771a05 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ So here it is: a Linux application for the Logitech Spotlight. * [Pre-requisites](#pre-requisites) * [Application Menu](#application-menu) * [Command Line Interface](#command-line-interface) + * [Scriptability](#scriptability) * [Device Support](#device-support) * [Troubleshooting](#troubleshooting) * [License](#license) @@ -184,7 +185,7 @@ A complete list the properties that can be set via the command line, can be list `--help-all` option or can also be found on the man pagers with newer versions of _Projecteur_ (`man projecteur`). -#### Scriptability +### Scriptability _Projecteur_ allows you to set almost all aspects of the spotlight via the command line for a running instance. @@ -197,11 +198,11 @@ projecteur -c border=true projecteur -c border.color=#ff0000 ``` -While _Projecteur_ does not provide global keyboard shortcuts, the command line options +While _Projecteur_ does not provide global keyboard shortcuts, command line options can but utilized for that. For instance, if you like to use _Projecteur_ as a tool while sharing -your screen in a video call without additional presenter hardware , you can assign global -shortcuts in your window manager (e.g. GNOME) to run the commands -`projecteur -c spot=on` and `projecteur -c spot=off`. +your screen in a video call without additional presenter hardware, you can assign global +shortcuts in your window manager (e.g. GNOME) to run the commands `projecteur -c spot=on` +and `projecteur -c spot=off`, and therefore turning the spot on and off with a keyboard shortcut. ### Device Support @@ -260,8 +261,9 @@ GNOME extension to have a system tray that can show the _Projecteur_ tray icon #### Zoom is not updated while spotlight is shown -Zoom does not update while spotlight is shown due to how the zoom currently works. A screenshot is taken shortly before the -overlay window is shown, and then a magnified section is shown wherever the mouse/spotlight is. +Zoom does not update while spotlight is shown due to how the zoom currently works. A screenshot is +taken shortly before the overlay window is shown, and then a magnified section is shown wherever +the mouse/spotlight is. If the zoom would be updated while the overlay window is shown, the overlay window it self would show up in the magnified section. That is a general problem that other magnifier tools also face, although they get around the problem by showing the magnified content rectangle always in the From 0693e28168c9de958d1d91c1781dec31409abf6a Mon Sep 17 00:00:00 2001 From: Jahn Date: Fri, 11 Sep 2020 16:20:34 +0200 Subject: [PATCH 25/65] Add Ubuntu 20.10 to to CI builds. --- .github/workflows/ci-build.yml | 1 + .travis.yml | 12 ++++++------ docker/Dockerfile.ubuntu-18.04 | 13 +++++-------- docker/Dockerfile.ubuntu-20.04 | 5 +++-- docker/Dockerfile.ubuntu-20.10 | 22 ++++++++++++++++++++++ docker/README.md | 12 ++++++++++++ 6 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 docker/Dockerfile.ubuntu-20.10 create mode 100644 docker/README.md diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 4ee2855d..0318b287 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -22,6 +22,7 @@ jobs: - debian-buster - ubuntu-18.04 - ubuntu-20.04 + - ubuntu-20.10 - opensuse-15.0 - opensuse-15.1 - centos-8 diff --git a/.travis.yml b/.travis.yml index 96c1ba99..c5ac134f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,12 +7,12 @@ matrix: # - os: linux # dist: xenial # env: DOCKER_IMG=jahnf/projecteur:debian-stretch - - os: linux - dist: bionic - env: DOCKER_IMG=jahnf/projecteur:debian-buster - - os: linux - dist: xenial - env: DOCKER_IMG=jahnf/projecteur:ubuntu-18.04 +# - os: linux +# dist: bionic +# env: DOCKER_IMG=jahnf/projecteur:debian-buster +# - os: linux +# dist: xenial +# env: DOCKER_IMG=jahnf/projecteur:ubuntu-18.04 - os: linux dist: bionic env: DOCKER_IMG=jahnf/projecteur:ubuntu-20.04 diff --git a/docker/Dockerfile.ubuntu-18.04 b/docker/Dockerfile.ubuntu-18.04 index 123f92d9..e3901096 100644 --- a/docker/Dockerfile.ubuntu-18.04 +++ b/docker/Dockerfile.ubuntu-18.04 @@ -13,14 +13,11 @@ RUN apt-get install -y --no-install-recommends \ cmake \ udev \ git \ - pkg-config - -RUN apt-get install -y --no-install-recommends \ + pkg-config \ qtdeclarative5-dev \ qttools5-dev-tools \ - qt5-default - -RUN apt-get install -y --no-install-recommends \ + qttools5-dev \ + qt5-default \ libqt5x11extras5-dev \ - libusb-1.0-0-dev - + libusb-1.0-0-dev \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/Dockerfile.ubuntu-20.04 b/docker/Dockerfile.ubuntu-20.04 index d5d93d9d..90c22633 100644 --- a/docker/Dockerfile.ubuntu-20.04 +++ b/docker/Dockerfile.ubuntu-20.04 @@ -15,7 +15,8 @@ RUN DEBIAN_FRONTEND="noninteractive" \ pkg-config \ qtdeclarative5-dev \ qttools5-dev-tools \ + qttools5-dev \ qt5-default \ libqt5x11extras5-dev \ - libusb-1.0-0-dev - + libusb-1.0-0-dev \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/Dockerfile.ubuntu-20.10 b/docker/Dockerfile.ubuntu-20.10 new file mode 100644 index 00000000..d38ec0fe --- /dev/null +++ b/docker/Dockerfile.ubuntu-20.10 @@ -0,0 +1,22 @@ +# Container for building the Projecteur package +# Images available at: https://hub.docker.com/r/jahnf/projecteur/tags + +FROM ubuntu:20.10 + +RUN apt-get update && mkdir /build +RUN DEBIAN_FRONTEND="noninteractive" \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + g++ \ + make \ + cmake \ + udev \ + git \ + pkg-config \ + qtdeclarative5-dev \ + qttools5-dev-tools \ + qttools5-dev \ + qt5-default \ + libqt5x11extras5-dev \ + libusb-1.0-0-dev \ + && rm -rf /var/lib/apt/lists/* diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..34e3d0aa --- /dev/null +++ b/docker/README.md @@ -0,0 +1,12 @@ +# Projecteur Dockerfiles + +Docker configuration files for build containers used in _Projecteur_ CI builds. + +Example for creating an image: +``` +docker build -f Dockerfile.ubuntu-20.10 --tag jahnf/projecteur:ubuntu-20.10 . +``` + +Images used in the CI build can be found on docker hub: +https://hub.docker.com/r/jahnf/projecteur + From 968d92c43cdbf25c2f12b87c464979a511b64c8b Mon Sep 17 00:00:00 2001 From: Jahn Date: Fri, 23 Oct 2020 12:20:51 +0200 Subject: [PATCH 26/65] Enable toggle of the spotlight via command line #104 + and fix minor bug when switching between screens --- qml/main.qml | 2 +- src/devicescan.cc | 2 +- src/main.cc | 2 +- src/projecteurapp.cc | 47 ++++++++++++++++++++++++++++++++++---------- src/projecteurapp.h | 1 + src/spotlight.cc | 15 ++++++++++---- src/spotlight.h | 1 + 7 files changed, 53 insertions(+), 17 deletions(-) diff --git a/qml/main.qml b/qml/main.qml index 2d547e81..afbb5ddc 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -61,7 +61,7 @@ Window { cursorShape: Settings.cursor anchors.fill: parent hoverEnabled: true - onClicked: { mainWindow.hide() } + onClicked: { ProjecteurApp.spotlightWindowClicked() } onExited: { ProjecteurApp.cursorExitedWindow() } } } diff --git a/src/devicescan.cc b/src/devicescan.cc index 1c0459a6..cb46aa85 100644 --- a/src/devicescan.cc +++ b/src/devicescan.cc @@ -136,7 +136,7 @@ namespace { switch (busType) { case BUS_USB: spotlightDevice.busType = DeviceScan::Device::BusType::Usb; break; - case BUS_BLUETOOTH: spotlightDevice.busType = DeviceScan::Device::BusType::Bluetooth; break; + case BUS_BLUETOOTH: spotlightDevice.busType = DeviceScan::Device::BusType::Bluetooth; break; } spotlightDevice.id.vendorId = ids.size() > 1 ? ids[1].toUShort(nullptr, 16) : 0; spotlightDevice.id.productId = ids.size() > 2 ? ids[2].toUShort(nullptr, 16) : 0; diff --git a/src/main.cc b/src/main.cc index ca02b3b4..0f1613a3 100644 --- a/src/main.cc +++ b/src/main.cc @@ -110,7 +110,7 @@ int main(int argc, char *argv[]) } print() << " -c COMMAND|PROPERTY " << commandOption.description() << std::endl; print() << ""; - print() << " spot=[on|off] " << Main::tr("Turn spotlight on/off."); + print() << " spot=[on|off|toggle] " << Main::tr("Turn spotlight on/off or toggle."); print() << " settings=[show|hide] " << Main::tr("Show/hide preferences dialog."); if (parser.isSet(fullHelpOption)) { print() << " preset=NAME " << Main::tr("Set a preset."); diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index bb52f9c2..d679fb35 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -63,7 +63,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio : PreferencesDialog::Mode::ClosableDialog)); connect(&*m_dialog, &PreferencesDialog::testButtonClicked, this, [this](){ - emit m_spotlight->spotActiveChanged(true); + m_spotlight->setSpotActive(true); }); const QString desktopEnv = m_linuxDesktop->type() == LinuxDesktop::Type::KDE ? "KDE" : @@ -123,7 +123,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio const auto actionQuit = m_trayMenu->addAction(tr("&Quit")); connect(actionQuit, &QAction::triggered, this, [this, engine](){ engine->deleteLater(); // see: https://bugreports.qt.io/browse/QTBUG-81247 - this->quit(); + this->quit(); }); m_trayIcon->setContextMenu(&*m_trayMenu); @@ -223,7 +223,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio }); // Handling if the screen in the settings was changed - connect(m_settings, &Settings::screenChanged, this, [this, window](int screenIdx) + connect(m_settings, &Settings::screenChanged, this, [this, window, xcbOnWayland](int screenIdx) { if (screenIdx >= screens().size()) return; @@ -231,16 +231,33 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio const auto screen = screens()[screenIdx]; const bool wasVisible = window->isVisible(); - window->setFlags(window->flags() | Qt::SplashScreen | Qt::WindowStaysOnTopHint); + m_overlayVisible = false; + emit overlayVisibleChanged(false); + + window->setFlags(window->flags() | Qt::WindowTransparentForInput); + window->setFlags(window->flags() & ~Qt::WindowStaysOnTopHint); window->hide(); window->setGeometry(QRect(screen->geometry().topLeft(), QSize(300,200))); window->setScreen(screen); window->setGeometry(screen->geometry()); + if (xcbOnWayland && !wasVisible) + { + if (m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog + && m_dialog->isMinimized()) { // keep Window minimized... + //Workaround for QTBUG-76354 (https://bugreports.qt.io/browse/QTBUG-76354) + m_dialog->showNormal(); + m_dialog->setWindowState(Qt::WindowMinimized); + } + } + if (wasVisible) { QTimer::singleShot(0, this, [this](){ - emit m_spotlight->spotActiveChanged(true); + if (m_spotlight->spotActive()) + emit m_spotlight->spotActiveChanged(true); + else + m_spotlight->setSpotActive(true); }); } }); @@ -294,6 +311,11 @@ ProjecteurApplication::~ProjecteurApplication() if (m_localServer) m_localServer->close(); } +void ProjecteurApplication::spotlightWindowClicked() +{ + m_spotlight->setSpotActive(false); +} + void ProjecteurApplication::cursorExitedWindow() { setScreenForCursorPos(); @@ -357,13 +379,18 @@ void ProjecteurApplication::readCommand(QLocalSocket* clientConnection) } else if (cmdKey == "spot") { - const bool active = (cmdValue == "on" || cmdValue == "1" || cmdValue == "true"); - logDebug(cmdserver) << tr("Received command spot = %1").arg(active); - emit m_spotlight->spotActiveChanged(active); + if (cmdValue.toLower() == "toggle") { + m_spotlight->setSpotActive(!m_spotlight->spotActive()); + } + else { + const bool active = (cmdValue.toLower() == "on" || cmdValue == "1" || cmdValue.toLower() == "true"); + logDebug(cmdserver) << tr("Received command spot = %1").arg(active); + m_spotlight->setSpotActive(active); + } } else if (cmdKey == "settings" || cmdKey == "preferences") { - const bool show = !(cmdValue == "hide" || cmdValue == "0"); + const bool show = !(cmdValue.toLower() == "hide" || cmdValue == "0"); logDebug(cmdserver) << tr("Received command settings = %1").arg(show); showPreferences(show); } @@ -404,7 +431,7 @@ void ProjecteurApplication::showPreferences(bool show) else { if (m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog) m_dialog->showMinimized(); - else + else m_dialog->hide(); } } diff --git a/src/projecteurapp.h b/src/projecteurapp.h index 6d7e2a2b..4554139c 100644 --- a/src/projecteurapp.h +++ b/src/projecteurapp.h @@ -42,6 +42,7 @@ class ProjecteurApplication : public QApplication public slots: void cursorExitedWindow(); + void spotlightWindowClicked(); private slots: void readCommand(QLocalSocket* client); diff --git a/src/spotlight.cc b/src/spotlight.cc index 4b021922..a8f376c3 100644 --- a/src/spotlight.cc +++ b/src/spotlight.cc @@ -34,8 +34,7 @@ Spotlight::Spotlight(QObject* parent, Options options, Settings* settings) m_activeTimer->setInterval(600); connect(m_activeTimer, &QTimer::timeout, this, [this](){ - m_spotActive = false; - emit spotActiveChanged(false); + setSpotActive(false); }); if (m_options.enableUInput) { @@ -82,6 +81,15 @@ uint32_t Spotlight::connectedDeviceCount() const return count; } +// ------------------------------------------------------------------------------------------------- +void Spotlight::setSpotActive(bool active) +{ + if (m_spotActive == active) return; + m_spotActive = active; + if (!m_spotActive) m_activeTimer->stop(); + emit spotActiveChanged(m_spotActive); +} + // ------------------------------------------------------------------------------------------------- std::shared_ptr Spotlight::deviceConnection(const DeviceId& deviceId) { @@ -253,8 +261,7 @@ void Spotlight::onEventDataAvailable(int fd, SubEventConnection& connection) if (isMouseMoveEvent) { // Skip input mapping for mouse move events completely if (!m_activeTimer->isActive()) { - m_spotActive = true; - emit spotActiveChanged(true); + setSpotActive(true); } m_activeTimer->start(); if (m_virtualDevice) m_virtualDevice->emitEvents(buf.data(), buf.pos()); diff --git a/src/spotlight.h b/src/spotlight.h index 390de9a9..2174e11b 100644 --- a/src/spotlight.h +++ b/src/spotlight.h @@ -29,6 +29,7 @@ class Spotlight : public QObject virtual ~Spotlight(); bool spotActive() const { return m_spotActive; } + void setSpotActive(bool active); struct ConnectedDeviceInfo { DeviceId id; From dfb82abf62ba900eb2b5ba786cff90dac2f5a80c Mon Sep 17 00:00:00 2001 From: Jahn Date: Fri, 23 Oct 2020 12:26:46 +0200 Subject: [PATCH 27/65] Update README and man file #104 --- README.md | 5 +++-- cmake/templates/projecteur.1 | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e3771a05..629f334b 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ Usage: projecteur [option] -c COMMAND|PROPERTY Send command/property to a running instance. - spot=[on|off] Turn spotlight on/off. + spot=[on|off|toggle] Turn spotlight on/off or toggle. settings=[show|hide] Show/hide preferences dialog. quit Quit the running instance. ``` @@ -202,7 +202,8 @@ While _Projecteur_ does not provide global keyboard shortcuts, command line opti can but utilized for that. For instance, if you like to use _Projecteur_ as a tool while sharing your screen in a video call without additional presenter hardware, you can assign global shortcuts in your window manager (e.g. GNOME) to run the commands `projecteur -c spot=on` -and `projecteur -c spot=off`, and therefore turning the spot on and off with a keyboard shortcut. +and `projecteur -c spot=off` or `projecteur -c spot=toggle`, and therefore +turning the spot on and off with a keyboard shortcut. ### Device Support diff --git a/cmake/templates/projecteur.1 b/cmake/templates/projecteur.1 index 0054b278..39c65336 100644 --- a/cmake/templates/projecteur.1 +++ b/cmake/templates/projecteur.1 @@ -60,8 +60,8 @@ have a system tray. .PP .SH Commands .TP -spot=[on|off] -Turn spotlight on/off. +spot=[on|off|toggle] +Turn spotlight on/off or toggle. .TP settings=[show|hide] Show/hide preferences dialog. From 2af3ef08f5a58ec0404e3b3d06424a215f697a1f Mon Sep 17 00:00:00 2001 From: Jahn F Date: Tue, 27 Oct 2020 16:00:21 +0100 Subject: [PATCH 28/65] Switch back to github docker registry. --- .github/workflows/ci-build.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 0318b287..48014ba5 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -31,7 +31,7 @@ jobs: runs-on: ${{ matrix.os }} env: - DOCKER_IMG: jahnf/projecteur + DOCKER_IMG: docker.pkg.github.com/jahnf/projecteur/projecteur DOCKER_TAG: ${{ matrix.docker_tag }} MAKEFLAGS: -j2 CLOUDSMITH_USER: jahnf @@ -52,9 +52,16 @@ jobs: export BRANCH=${GITHUB_REF/refs\/heads\//} echo Deteted branch: ${BRANCH} echo "::set-env name=BRANCH::${BRANCH}" + + - name: Login to github docker registry + run: echo ${DOCKER_TOKEN} | docker login docker.pkg.github.com -u ${{ secrets.DOCKER_USER }} --password-stdin + env: + DOCKER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Pull ${{ matrix.docker_tag }} docker image - run: docker pull ${DOCKER_IMG}:${{ matrix.docker_tag }} + run: | + docker pull ${DOCKER_IMG}:${{ matrix.docker_tag }} + docker logout docker.pkg.github.com - name: docker create build container run: | docker run --name build --env MAKEFLAGS=${MAKEFLAGS} \ From e0bc38345dddd8138662c2ec8034f3d29ee0fcad Mon Sep 17 00:00:00 2001 From: Jahn F Date: Tue, 27 Oct 2020 17:53:56 +0100 Subject: [PATCH 29/65] Replace deprecated set-env #107 --- .github/workflows/ci-build.yml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 48014ba5..9cdad603 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -43,15 +43,15 @@ jobs: # ---------- Add ~/.local/bin to PATH ---------- - run: | export LOCAL_BIN=~/.local/bin - echo "::set-env name=PATH::${PATH}:${LOCAL_BIN}" + echo "${LOCAL_BIN}" >> $GITHUB_PATH # =================================================================================== # ---------- Checkout and build inside docker container ---------- - uses: actions/checkout@v1 - run: | export BRANCH=${GITHUB_REF/refs\/heads\//} - echo Deteted branch: ${BRANCH} - echo "::set-env name=BRANCH::${BRANCH}" + echo Detected branch: ${BRANCH} + echo "BRANCH=${BRANCH}" >> $GITHUB_ENV - name: Login to github docker registry run: echo ${DOCKER_TOKEN} | docker login docker.pkg.github.com -u ${{ secrets.DOCKER_USER }} --password-stdin @@ -88,7 +88,7 @@ jobs: - name: Set version environment variable run: | projecteur_version=`cat version-string` - echo "::set-env name=projecteur_version::${projecteur_version}" + echo "projecteur_version=${projecteur_version}" >> $GITHUB_ENV - name: Move source package if: startsWith(matrix.docker_tag, 'archlinux') @@ -97,12 +97,12 @@ jobs: - name: Get source package filename for artifact uploads run: | src_pkg_artifact=`ls -1 source-pkg/* | head -n 1` - echo "::set-env name=src_pkg_artifact::${src_pkg_artifact}" + echo "src_pkg_artifact=${src_pkg_artifact}" >> $GITHUB_ENV - name: Get binary package filename for artifact uploads run: | dist_pkg_artifact=`ls -1 dist-pkg/* | head -n 1` - echo "::set-env name=dist_pkg_artifact::${dist_pkg_artifact}" + echo "dist_pkg_artifact=${dist_pkg_artifact}" >> $GITHUB_ENV # =================================================================================== # ---------- Upload artifacts to github ---------- @@ -123,26 +123,26 @@ jobs: # ---------- Set environment variables depending on branch ---------- - name: Set environment variable defaults run: | - echo "::set-env name=upload_bin_pkg::${{ false }}" - echo "::set-env name=upload_src_pkg::${{ false }}" - echo "::set-env name=cloudsmith_upload_repo::projecteur-develop" - echo "::set-env name=REPO_UPLOAD::${{ false }}" + echo "upload_bin_pkg=${{ false }}" >> $GITHUB_ENV + echo "upload_src_pkg=${{ false }}" >> $GITHUB_ENV + echo "cloudsmith_upload_repo=projecteur-develop" >> $GITHUB_ENV + echo "REPO_UPLOAD=${{ false }}" >> $GITHUB_ENV - name: Check for binary-pkg upload conditions if: env.BRANCH == 'develop' || env.BRANCH == 'master' run: | - echo "::set-env name=upload_bin_pkg::${{ true }}" - echo "::set-env name=bintray_upload_pkg::projecteur-${{ env.BRANCH }}" + echo "upload_bin_pkg=${{ true }}" >> $GITHUB_ENV + echo "bintray_upload_pkg=${{ env.BRANCH }}" >> $GITHUB_ENV pip install --upgrade wheel pip install --upgrade cloudsmith-cli - name: Check for source-pkg upload conditions if: env.upload_bin_pkg == 'true' && startsWith(matrix.docker_tag, 'archlinux') run: | - echo "::set-env name=upload_src_pkg::${{ true }}" + echo "upload_src_pkg=${{ true }}" >> $GITHUB_ENV - if: env.BRANCH == 'master' - run: echo "::set-env name=cloudsmith_upload_repo::projecteur-stable" + run: echo "cloudsmith_upload_repo=projecteur-stable" >> $GITHUB_ENV # =================================================================================== # ---------- Upload artifacts to cloudsmith ---------- @@ -179,11 +179,11 @@ jobs: export DISTRO=${distromap[${{ matrix.docker_tag }}]} echo PKGTYPE=$PKG_TYPE echo DISTRO=$DISTRO - echo "::set-env name=PKG_TYPE::${PKG_TYPE}" - echo "::set-env name=DISTRO::${DISTRO}" + echo "PKG_TYPE=${PKG_TYPE}" >> $GITHUB_ENV + echo "DISTRO=${DISTRO}" >> $GITHUB_ENV if [ -z ${DISTRO} ] || [ -z ${PKG_TYPE} ]; then \ export REPO_UPLOAD=false; else export REPO_UPLOAD=true; fi; - echo "::set-env name=REPO_UPLOAD::${REPO_UPLOAD}" + echo "REPO_UPLOAD=${REPO_UPLOAD}" >> $GITHUB_ENV - name: Linux repo upload on cloudsmith for ${{ env.DISTRO }} if: env.REPO_UPLOAD == 'true' From 7a71bb508dcf4896dc5fbd28249c2d6f42e12c32 Mon Sep 17 00:00:00 2001 From: Jahn F Date: Tue, 27 Oct 2020 18:05:05 +0100 Subject: [PATCH 30/65] Delete .travis.yml Travis-ci is very slow now for a long time. Removing the travis build, since github actions are fast and are working fine. --- .travis.yml | 49 ------------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c5ac134f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -language: cpp -matrix: - include: - - os: linux - dist: bionic - env: DOCKER_IMG=jahnf/projecteur:archlinux -# - os: linux -# dist: xenial -# env: DOCKER_IMG=jahnf/projecteur:debian-stretch -# - os: linux -# dist: bionic -# env: DOCKER_IMG=jahnf/projecteur:debian-buster -# - os: linux -# dist: xenial -# env: DOCKER_IMG=jahnf/projecteur:ubuntu-18.04 - - os: linux - dist: bionic - env: DOCKER_IMG=jahnf/projecteur:ubuntu-20.04 -# - os: linux -# dist: xenial -# env: DOCKER_IMG=jahnf/projecteur:opensuse-15.0 - - os: linux - dist: xenial - env: DOCKER_IMG=jahnf/projecteur:opensuse-15.1 -# - os: linux -# dist: bionic -# env: DOCKER_IMG=jahnf/projecteur:fedora-30 -# - os: linux -# dist: bionic -# env: DOCKER_IMG=jahnf/projecteur:fedora-31 - - os: linux - dist: bionic - env: DOCKER_IMG=jahnf/projecteur:fedora-32 - - os: linux - dist: bionic - env: DOCKER_IMG=jahnf/projecteur:centos-8 -script: -- git fetch --tags -- "[ -f $(git rev-parse --git-dir)/shallow ] && git fetch --unshallow || echo No need to unshallow..." -- docker pull ${DOCKER_IMG} -- docker run --name build --env TRAVIS_BRANCH=${TRAVIS_BRANCH} -d -v `pwd`:/source:ro -ti ${DOCKER_IMG} -- docker exec -it build /bin/bash -c "mkdir -p /build/dist-pkg && cd /build && cmake /source" -- docker exec -it build /bin/bash -c "cd /build && cmake --build ." -- docker exec -it build /bin/bash -c "cd /build && cmake --build . --target dist-package" -- docker exec -it build /bin/bash -c "cd /build && ./projecteur -f" -- "[ \"${BUILD_SOURCE_PKG}\" = 1 ] && docker exec -it build /bin/bash -c \"cd /build && cmake --build . --target source-archive\" || true" -- docker cp build:/build/dist-pkg . -- docker cp build:/build/travis-ci-bintray-deploy.json . -- ls -alh dist-pkg From 2fb129ec2b2e2c7c41051ce6eabf79a6ad6b0746 Mon Sep 17 00:00:00 2001 From: Jahn F Date: Tue, 27 Oct 2020 20:01:00 +0100 Subject: [PATCH 31/65] Update ci-build.yml --- .github/workflows/ci-build.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 9cdad603..161e6223 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -126,13 +126,13 @@ jobs: echo "upload_bin_pkg=${{ false }}" >> $GITHUB_ENV echo "upload_src_pkg=${{ false }}" >> $GITHUB_ENV echo "cloudsmith_upload_repo=projecteur-develop" >> $GITHUB_ENV + echo "bintray_upload_repo=projecteur-develop" >> $GITHUB_ENV echo "REPO_UPLOAD=${{ false }}" >> $GITHUB_ENV - name: Check for binary-pkg upload conditions if: env.BRANCH == 'develop' || env.BRANCH == 'master' run: | echo "upload_bin_pkg=${{ true }}" >> $GITHUB_ENV - echo "bintray_upload_pkg=${{ env.BRANCH }}" >> $GITHUB_ENV pip install --upgrade wheel pip install --upgrade cloudsmith-cli @@ -142,7 +142,9 @@ jobs: echo "upload_src_pkg=${{ true }}" >> $GITHUB_ENV - if: env.BRANCH == 'master' - run: echo "cloudsmith_upload_repo=projecteur-stable" >> $GITHUB_ENV + run: | + echo "cloudsmith_upload_repo=projecteur-stable" >> $GITHUB_ENV + echo "bintray_upload_repo=projecteur-master" >> $GITHUB_ENV # =================================================================================== # ---------- Upload artifacts to cloudsmith ---------- @@ -206,7 +208,7 @@ jobs: api_key: ${{ secrets.BINTRAY_API_KEY }} repository_user: jahnf repository: Projecteur - package: ${{ env.bintray_upload_pkg }} + package: ${{ env.bintray_upload_repo }} version: ${{ env.projecteur_version }} upload_path: packages/branches/${{ env.BRANCH }}/${{ env.projecteur_version }} calculate_metadata: false @@ -221,7 +223,7 @@ jobs: api_key: ${{ secrets.BINTRAY_API_KEY }} repository_user: jahnf repository: Projecteur - package: ${{ env.bintray_upload_pkg }} + package: ${{ env.bintray_upload_repo }} version: ${{ env.projecteur_version }} upload_path: packages/branches/${{ env.BRANCH }}/${{ env.projecteur_version }} calculate_metadata: false From 4b56e86e6f3bdad6845f6471557bf8ac7f76836e Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 28 Oct 2020 07:54:09 +0100 Subject: [PATCH 32/65] Add fedora 33 CI build. --- .github/workflows/ci-build.yml | 13 +++++++------ docker/Dockerfile.fedora-33 | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 docker/Dockerfile.fedora-33 diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 161e6223..b3ce8eda 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -18,6 +18,7 @@ jobs: - fedora-30 - fedora-31 - fedora-32 + - fedora-33 - debian-stretch - debian-buster - ubuntu-18.04 @@ -52,15 +53,15 @@ jobs: export BRANCH=${GITHUB_REF/refs\/heads\//} echo Detected branch: ${BRANCH} echo "BRANCH=${BRANCH}" >> $GITHUB_ENV - - - name: Login to github docker registry - run: echo ${DOCKER_TOKEN} | docker login docker.pkg.github.com -u ${{ secrets.DOCKER_USER }} --password-stdin - env: - DOCKER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to github docker registry + run: echo ${DOCKER_TOKEN} | docker login docker.pkg.github.com -u ${{ secrets.DOCKER_USER }} --password-stdin + env: + DOCKER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Pull ${{ matrix.docker_tag }} docker image run: | - docker pull ${DOCKER_IMG}:${{ matrix.docker_tag }} + docker pull ${DOCKER_IMG}:${{ matrix.docker_tag }} docker logout docker.pkg.github.com - name: docker create build container run: | diff --git a/docker/Dockerfile.fedora-33 b/docker/Dockerfile.fedora-33 new file mode 100644 index 00000000..733c1859 --- /dev/null +++ b/docker/Dockerfile.fedora-33 @@ -0,0 +1,20 @@ +# Container for building the Projecteur package +# Images available at: https://hub.docker.com/r/jahnf/projecteur/tags + +FROM fedora:33 + +RUN mkdir /build +RUN dnf -y install --setopt=install_weak_deps=False --best \ + cmake \ + udev \ + gcc-c++ \ + tar \ + make \ + git \ + qt5-qtdeclarative-devel \ + pkg-config \ + rpm-build \ + qt5-linguist \ + qt5-qtx11extras-devel \ + libusbx-devel + From 87c2641b60b969f0c872751628ad6fc80e9befd5 Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 28 Oct 2020 08:05:14 +0100 Subject: [PATCH 33/65] Add fedora-33 package upload to cloudsmith --- .github/workflows/ci-build.yml | 2 +- doc/LinuxRepositories.md | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index b3ce8eda..cb638926 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -178,7 +178,7 @@ jobs: ["debian-bullseye"]="debian/bullseye" ["ubuntu-18.04"]="ubuntu/bionic" \ ["ubuntu-20.04"]="ubuntu/focal" ["opensuse-15.1"]="opensuse/15.1" \ ["centos-8"]="el/8" ["fedora-30"]="fedora/30" ["fedora-31"]="fedora/31" \ - ["fedora-32"]="fedora/32" ) + ["fedora-32"]="fedora/32" ["fedora-33"]="fedora/33" ) export DISTRO=${distromap[${{ matrix.docker_tag }}]} echo PKGTYPE=$PKG_TYPE echo DISTRO=$DISTRO diff --git a/doc/LinuxRepositories.md b/doc/LinuxRepositories.md index cb3a6500..06bf1810 100644 --- a/doc/LinuxRepositories.md +++ b/doc/LinuxRepositories.md @@ -103,6 +103,16 @@ dnf config-manager --add-repo '/tmp/jahnf-projecteur-develop.repo' dnf -q makecache -y --disablerepo='*' --enablerepo='jahnf-projecteur-develop' --enablerepo='jahnf-projecteur-develop-source' ``` +#### Fedora 33 + +``` +dnf install yum-utils pygpgme +rpm --import 'https://dl.cloudsmith.io/public/jahnf/projecteur-develop/cfg/gpg/gpg.544E6934C0570750.key' +curl -1sLf 'https://dl.cloudsmith.io/public/jahnf/projecteur-develop/cfg/setup/config.rpm.txt?distro=fedora&codename=33' > /tmp/jahnf-projecteur-develop.repo +dnf config-manager --add-repo '/tmp/jahnf-projecteur-develop.repo' +dnf -q makecache -y --disablerepo='*' --enablerepo='jahnf-projecteur-develop' --enablerepo='jahnf-projecteur-develop-source' +``` + #### CentOS 8 ``` From a0356b5fabf2dac4218afb9b3a19a34d53451458 Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 28 Oct 2020 08:27:32 +0100 Subject: [PATCH 34/65] Add openSUSE 15.2 ci build. --- .github/workflows/ci-build.yml | 1 + docker/Dockerfile.opensuse-15.2 | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 docker/Dockerfile.opensuse-15.2 diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index cb638926..6d765ac5 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -26,6 +26,7 @@ jobs: - ubuntu-20.10 - opensuse-15.0 - opensuse-15.1 + - opensuse-15.2 - centos-8 os: - ubuntu-latest diff --git a/docker/Dockerfile.opensuse-15.2 b/docker/Dockerfile.opensuse-15.2 new file mode 100644 index 00000000..77f53bda --- /dev/null +++ b/docker/Dockerfile.opensuse-15.2 @@ -0,0 +1,20 @@ +# Container for building the Projecteur package +# Images available at: https://hub.docker.com/r/jahnf/projecteur/tags + +FROM opensuse/leap:15.2 + +RUN zypper --non-interactive in --no-recommends \ + pkg-config \ + udev \ + gcc-c++ \ + tar \ + make \ + cmake \ + git \ + wget \ + libqt5-qtdeclarative-devel \ + rpmbuild \ + libqt5-linguist \ + libqt5-qtx11extras-devel \ + libusb-1_0-devel \ + libQt5DBus-devel From 38c78240d85a99c4d895d955b48db337c2d662c3 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sun, 1 Nov 2020 16:42:44 +0100 Subject: [PATCH 35/65] Add bash completion file. --- CMakeLists.txt | 5 + cmake/templates/projecteur.bash-completion | 111 +++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 cmake/templates/projecteur.bash-completion diff --git a/CMakeLists.txt b/CMakeLists.txt index 7501d703..d1642298 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -223,6 +223,11 @@ install(FILES "${OUTDIR}/projecteur.1.gz" DESTINATION share/man/man1/) configure_file("${TMPLDIR}/projecteur.metainfo.xml" "projecteur.metainfo.xml" @ONLY) install(FILES "${OUTDIR}/projecteur.metainfo.xml" DESTINATION share/metainfo/) +configure_file("${TMPLDIR}/projecteur.bash-completion" "projecteur.bash-completion" @ONLY) +install(FILES "${OUTDIR}/projecteur.bash-completion" + DESTINATION share/bash-completion/completions/ + RENAME projecteur) + configure_file("${TMPLDIR}/preinst.in" "pkg/scripts/preinst" @ONLY) configure_file("${TMPLDIR}/postinst.in" "pkg/scripts/postinst" @ONLY) diff --git a/cmake/templates/projecteur.bash-completion b/cmake/templates/projecteur.bash-completion new file mode 100644 index 00000000..70212245 --- /dev/null +++ b/cmake/templates/projecteur.bash-completion @@ -0,0 +1,111 @@ +# projecteur(1) completion -*- shell-script -*- + +_projecteur() +{ + COMPREPLY=() + local cur=${COMP_WORDS[COMP_CWORD]} + local prev=${COMP_WORDS[COMP_CWORD-1]} + local prev_prev=${COMP_WORDS[COMP_CWORD-2]} + local first_level=0 + + # Handling of '=' + if [ "${prev}" = "=" ]; then + prev="${prev_prev}" + prev_prev="=" + fi + + local options="-h --help --help-all --version -v --cfg --device-scan -m --minimize-only" + options="${options} --log-level -l --show-dialog --disable-uinput -D -c" + + case "$prev" in + "-c") + # Auto completion for commands and properties + local commands="quit spot= settings= preset=" + commands="${commands} spot.size= spot.rotation= spot.shape= spot.shape.square.radius=" + commands="${commands} spot.shape.star.points= spot.shape.star.innerradius= spot.shape.ngon.sides=" + commands="${commands} shade= shade.opacity= shade.color= dot= dot.size= dot.color= dot.opacity=" + commands="${commands} border= border.size= border.color= border.opacity= zoom= zoom.factor=" + + local fl=$(printf '%.1s' "$cur") + [ ! "$fl" = "q" ] && compopt -o nospace + COMPREPLY=( $(compgen -W "${commands}" -- $cur) ) + ;; + "spot") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "on off toggle" -- $cur) ) + fi + ;; + "settings") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "show hide" -- $cur) ) + fi + ;; + "spot.shape") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "circle square star ngon" -- $cur) ) + fi + ;; + "shade") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "false true" -- $cur) ) + fi + ;; + "dot") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "false true" -- $cur) ) + fi + ;; + "border") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "false true" -- $cur) ) + fi + ;; + "zoom") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "false true" -- $cur) ) + fi + ;; + "-D") + # TODO: Auto completion for devices (vendorId:productId) + COMPREPLY=( $(compgen -W "0123:4567" -- $cur) ) + ;; + "-l") + COMPREPLY=( $(compgen -W "dbg inf wrn err" -- $cur) ) + ;; + "--log-level") + COMPREPLY=( $(compgen -W "dbg inf wrn err" -- $cur) ) + ;; + "--cfg") + # Auto completion for files + local IFS=$'\n' + local LASTCHAR=' ' + compopt -o nospace + COMPREPLY=( $(compgen -f -- ${cur}) ) + + if [ ${#COMPREPLY[@]} = 1 ]; then + [ -d "$COMPREPLY" ] && LASTCHAR=/ + COMPREPLY=$(printf %q%s "$COMPREPLY" "$LASTCHAR") + else + for ((i=0; i < ${#COMPREPLY[@]}; i++)); do + [ -d "${COMPREPLY[$i]}" ] && COMPREPLY[$i]=${COMPREPLY[$i]}/ + done + fi + ;; + *) + first_level=1 + ;; + esac + + if [ $first_level -eq 1 ]; then + COMPREPLY=( $(compgen -W "${options}" -- $cur) ) + fi +} + +complete -F _projecteur projecteur From 3593e82b7ccc1e0da90b76c48c1a3e5ead7bb53e Mon Sep 17 00:00:00 2001 From: Jahn Date: Mon, 2 Nov 2020 11:49:09 +0100 Subject: [PATCH 36/65] Add opensuse-15.2 package upload to cloudsmith --- .github/workflows/ci-build.yml | 3 ++- doc/LinuxRepositories.md | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 6d765ac5..ebce6a3e 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -178,7 +178,8 @@ jobs: declare -A distromap=( ["debian-stretch"]="debian/stretch" ["debian-buster"]="debian/buster" \ ["debian-bullseye"]="debian/bullseye" ["ubuntu-18.04"]="ubuntu/bionic" \ ["ubuntu-20.04"]="ubuntu/focal" ["opensuse-15.1"]="opensuse/15.1" \ - ["centos-8"]="el/8" ["fedora-30"]="fedora/30" ["fedora-31"]="fedora/31" \ + ["opensuse-15.2"]="opensuse/15.2" ["centos-8"]="el/8" \ + ["fedora-30"]="fedora/30" ["fedora-31"]="fedora/31" \ ["fedora-32"]="fedora/32" ["fedora-33"]="fedora/33" ) export DISTRO=${distromap[${{ matrix.docker_tag }}]} echo PKGTYPE=$PKG_TYPE diff --git a/doc/LinuxRepositories.md b/doc/LinuxRepositories.md index 06bf1810..b0f1b158 100644 --- a/doc/LinuxRepositories.md +++ b/doc/LinuxRepositories.md @@ -83,6 +83,14 @@ zypper ar -f '/tmp/jahnf-projecteur-develop.repo' zypper --gpg-auto-import-keys refresh jahnf-projecteur-develop jahnf-projecteur-develop-source ``` +#### OpenSuse 15.2 + +``` +curl -1sLf 'https://dl.cloudsmith.io/public/jahnf/projecteur-develop/cfg/setup/config.rpm.txt?distro=opensuse&codename=15.2' > /tmp/jahnf-projecteur-develop.repo +zypper ar -f '/tmp/jahnf-projecteur-develop.repo' +zypper --gpg-auto-import-keys refresh jahnf-projecteur-develop jahnf-projecteur-develop-source +``` + #### Fedora 31 ``` From 0d25bf8b39439ecf5647109027d1b934a91bd13c Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 4 Nov 2020 17:28:11 +0100 Subject: [PATCH 37/65] Restructure for multi-screen overlay #80 --- qml/main.qml | 6 +++- src/preferencesdlg.cc | 11 +++++++ src/preferencesdlg.h | 1 + src/projecteurapp.cc | 71 +++++++++++++++++++++++++++++++++---------- src/projecteurapp.h | 7 +++++ src/settings.cc | 11 +++++++ src/settings.h | 8 +++-- 7 files changed, 96 insertions(+), 19 deletions(-) diff --git a/qml/main.qml b/qml/main.qml index afbb5ddc..b58e2e83 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -7,6 +7,8 @@ import Projecteur.Utils 1.0 as Utils Window { id: mainWindow + property var screenId: -1 + width: 300; height: 200 flags: Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.SplashScreen @@ -63,6 +65,7 @@ Window { hoverEnabled: true onClicked: { ProjecteurApp.spotlightWindowClicked() } onExited: { ProjecteurApp.cursorExitedWindow() } + onEntered: { ProjecteurApp.cursorEntered(screenId) } } } @@ -77,6 +80,7 @@ Window { color: Settings.shadeColor visible: false enabled: false + } Loader { @@ -191,4 +195,4 @@ Window { enabled: false } } -} +} // Window diff --git a/src/preferencesdlg.cc b/src/preferencesdlg.cc index dbbcb27d..003df835 100644 --- a/src/preferencesdlg.cc +++ b/src/preferencesdlg.cc @@ -116,6 +116,7 @@ QWidget* PreferencesDialog::createSettingsTabWidget(Settings* settings) spotScreenVBoxLeft->addWidget(createShapeGroupBox(settings)); spotScreenVBoxLeft->addWidget(createZoomGroupBox(settings)); spotScreenVBoxLeft->addWidget(createCursorGroupBox(settings)); + spotScreenVBoxLeft->addWidget(createMultiScreenWidget(settings)); const auto spotScreenVBoxRight = new QVBoxLayout(); spotScreenVBoxRight->addWidget(createSpotGroupBox(settings)); spotScreenVBoxRight->addWidget(createDotGroupBox(settings)); @@ -652,6 +653,16 @@ QGroupBox* PreferencesDialog::createCursorGroupBox(Settings* settings) return cursorGroup; } +// ------------------------------------------------------------------------------------------------- +QWidget* PreferencesDialog::createMultiScreenWidget(Settings* settings) +{ + const auto cb = new QCheckBox(tr("Enable multi-screen overlay"), this); + cb->setChecked(settings->multiScreenOverlayEnabled()); + connect(cb, &QCheckBox::toggled, settings, &Settings::setMultiScreenOverlayEnabled); + cb->setDisabled(true); // not yet implemented + return cb; +} + // ------------------------------------------------------------------------------------------------- QWidget* PreferencesDialog::createLogTabWidget() { diff --git a/src/preferencesdlg.h b/src/preferencesdlg.h index ca7aee55..9e4cec8f 100644 --- a/src/preferencesdlg.h +++ b/src/preferencesdlg.h @@ -60,6 +60,7 @@ class PreferencesDialog : public QDialog QGroupBox* createDotGroupBox(Settings* settings); QGroupBox* createBorderGroupBox(Settings* settings); QGroupBox* createCursorGroupBox(Settings* settings); + QWidget* createMultiScreenWidget(Settings* settings); QGroupBox* createZoomGroupBox(Settings* settings); QWidget* createPresetSelector(Settings* settings); #if HAS_Qt5_X11Extras diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index d679fb35..7f7b4fd5 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include #include #include @@ -34,16 +36,21 @@ namespace { } } +// ------------------------------------------------------------------------------------------------- ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Options& options) : QApplication(argc, argv) , m_trayIcon(new QSystemTrayIcon()) , m_trayMenu(new QMenu()) , m_localServer(new QLocalServer(this)) , m_linuxDesktop(new LinuxDesktop(this)) + , m_xcbOnWayland(QGuiApplication::platformName() == "xcb" && m_linuxDesktop->isWayland()) { if (screens().size() < 1) { - QMessageBox::critical(nullptr, tr("No Screens detected"), tr("screens().size() returned a size < 1.\nExiting.")); + const auto title = tr("No Screens detected"); + const auto text = tr("screens().size() returned a size < 1. Exiting."); + logError(mainapp) << title << ";" << text; + QMessageBox::critical(nullptr, title, text); QTimer::singleShot(0, this, [this](){ this->exit(2); }); return; } @@ -74,8 +81,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio << tr("Desktop Environment: %1;").arg(desktopEnv) << tr("Wayland: %1").arg(m_linuxDesktop->isWayland() ? "true" : "false"); - const bool xcbOnWayland = QGuiApplication::platformName() == "xcb" && m_linuxDesktop->isWayland(); - if (xcbOnWayland) { + if (m_xcbOnWayland) { logWarning(mainapp) << tr("Qt 'xcb' platform and Wayland session detected."); } @@ -86,14 +92,26 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio QTimer::singleShot(0, this, [this](){ m_dialog->show(); m_dialog->showMinimized(); }); } + // Create qml engine and register context properties const auto desktopImageProvider = new PixmapProvider(this); - const auto engine = new QQmlApplicationEngine(this); - engine->rootContext()->setContextProperty("Settings", m_settings); - engine->rootContext()->setContextProperty("PreferencesDialog", &*m_dialog); - engine->rootContext()->setContextProperty("DesktopImage", desktopImageProvider); - engine->rootContext()->setContextProperty("ProjecteurApp", this); - engine->load(QUrl(QStringLiteral("qrc:/main.qml"))); - const auto window = topLevelWindows().first(); + m_qmlEngine = new QQmlApplicationEngine(this); + m_qmlEngine->rootContext()->setContextProperty("Settings", m_settings); + m_qmlEngine->rootContext()->setContextProperty("PreferencesDialog", &*m_dialog); + m_qmlEngine->rootContext()->setContextProperty("DesktopImage", desktopImageProvider); + m_qmlEngine->rootContext()->setContextProperty("ProjecteurApp", this); + + // Create qml overlay window component + m_windowQmlComponent = new QQmlComponent(m_qmlEngine, QUrl(QStringLiteral("qrc:/main.qml")), m_qmlEngine); + if (m_windowQmlComponent->status() != QQmlComponent::Status::Ready) { + const auto title = tr("Overlay window error."); + const auto text = tr("Qml component has status '%1'. Exiting.").arg(m_windowQmlComponent->status()); + logError(mainapp) << title << ";" << text; + QMessageBox::critical(nullptr, title, text); + QTimer::singleShot(0, this, [this](){ this->exit(2); }); + return; + } + + const auto window = createOverlayWindow(); const auto actionPref = m_trayMenu->addAction(tr("&Preferences...")); connect(actionPref, &QAction::triggered, this, [this](){ @@ -121,8 +139,8 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio m_trayMenu->addSeparator(); const auto actionQuit = m_trayMenu->addAction(tr("&Quit")); - connect(actionQuit, &QAction::triggered, this, [this, engine](){ - engine->deleteLater(); // see: https://bugreports.qt.io/browse/QTBUG-81247 + connect(actionQuit, &QAction::triggered, this, [this](){ + m_qmlEngine->deleteLater(); // see: https://bugreports.qt.io/browse/QTBUG-81247 this->quit(); }); m_trayIcon->setContextMenu(&*m_trayMenu); @@ -164,7 +182,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio // Handling of spotlight window when mouse move events from spotlight device are detected connect(m_spotlight, &Spotlight::spotActiveChanged, this, - [window, desktopImageProvider, xcbOnWayland, this](bool active) + [window, desktopImageProvider, this](bool active) { if (active && !m_settings->overlayDisabled()) { @@ -201,7 +219,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio // Workaround for 'xcb' on Wayland session (default on Ubuntu) // .. the window in that case is not transparent for inputs and cannot be clicked through. // --> hide the window, although animations will not be visible - if (xcbOnWayland) + if (m_xcbOnWayland) { window->hide(); if (m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog @@ -223,7 +241,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio }); // Handling if the screen in the settings was changed - connect(m_settings, &Settings::screenChanged, this, [this, window, xcbOnWayland](int screenIdx) + connect(m_settings, &Settings::screenChanged, this, [this, window](int screenIdx) { if (screenIdx >= screens().size()) return; @@ -242,7 +260,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio window->setScreen(screen); window->setGeometry(screen->geometry()); - if (xcbOnWayland && !wasVisible) + if (m_xcbOnWayland && !wasVisible) { if (m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog && m_dialog->isMinimized()) { // keep Window minimized... @@ -306,21 +324,39 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio } } +// ------------------------------------------------------------------------------------------------- ProjecteurApplication::~ProjecteurApplication() { if (m_localServer) m_localServer->close(); } +// ------------------------------------------------------------------------------------------------- +QWindow* ProjecteurApplication::createOverlayWindow() +{ + QObject *object = m_windowQmlComponent->create(); + object->setParent(m_qmlEngine); + return qobject_cast(object); +} + +// ------------------------------------------------------------------------------------------------- void ProjecteurApplication::spotlightWindowClicked() { m_spotlight->setSpotActive(false); } +// ------------------------------------------------------------------------------------------------- void ProjecteurApplication::cursorExitedWindow() { setScreenForCursorPos(); } +// ------------------------------------------------------------------------------------------------- +void ProjecteurApplication::cursorEntered(quint64 /*screen*/) +{ + // TODO +} + +// ------------------------------------------------------------------------------------------------- void ProjecteurApplication::setScreenForCursorPos() { #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) @@ -339,6 +375,7 @@ void ProjecteurApplication::setScreenForCursorPos() #endif } +// ------------------------------------------------------------------------------------------------- void ProjecteurApplication::readCommand(QLocalSocket* clientConnection) { auto it = m_commandConnections.find(clientConnection); @@ -419,6 +456,7 @@ void ProjecteurApplication::readCommand(QLocalSocket* clientConnection) commandSize = 0; } +// ------------------------------------------------------------------------------------------------- void ProjecteurApplication::showPreferences(bool show) { if (show) @@ -436,6 +474,7 @@ void ProjecteurApplication::showPreferences(bool show) } } +// ================================================================================================= ProjecteurCommandClientApp::ProjecteurCommandClientApp(const QStringList& ipcCommands, int &argc, char **argv) : QCoreApplication(argc, argv) { diff --git a/src/projecteurapp.h b/src/projecteurapp.h index 4554139c..5f2406b3 100644 --- a/src/projecteurapp.h +++ b/src/projecteurapp.h @@ -13,6 +13,8 @@ class PreferencesDialog; class QLocalServer; class QLocalSocket; class QMenu; +class QQmlApplicationEngine; +class QQmlComponent; class QSystemTrayIcon; class Settings; class Settings; @@ -42,6 +44,7 @@ class ProjecteurApplication : public QApplication public slots: void cursorExitedWindow(); + void cursorEntered(quint64 screen); void spotlightWindowClicked(); private slots: @@ -50,6 +53,7 @@ private slots: private: void showPreferences(bool show = true); void setScreenForCursorPos(); + QWindow* createOverlayWindow(); private: std::unique_ptr m_trayIcon; @@ -60,8 +64,11 @@ private slots: Spotlight* m_spotlight = nullptr; Settings* m_settings = nullptr; LinuxDesktop* m_linuxDesktop = nullptr; + QQmlApplicationEngine* m_qmlEngine = nullptr; + QQmlComponent* m_windowQmlComponent = nullptr; std::map m_commandConnections; bool m_overlayVisible = false; + const bool m_xcbOnWayland = false; }; class ProjecteurCommandClientApp : public QCoreApplication diff --git a/src/settings.cc b/src/settings.cc index 12864339..4f1858d9 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -162,6 +162,8 @@ void Settings::initializeStringProperties() { auto& map = m_stringPropertyMap; // -- spot settings + map.emplace_back( "spot.overlay", StringProperty{ StringProperty::Bool, {false, true}, + [this](const QString& value){ setOverlayDisabled(!toBool(value)); } } ); map.emplace_back( "spot.size", StringProperty{ StringProperty::Integer, {::settings::ranges::spotSize.min, ::settings::ranges::spotSize.max}, [this](const QString& value){ setSpotSize(value.toInt()); } } ); @@ -756,6 +758,15 @@ void Settings::setZoomFactor(double factor) } } +// ------------------------------------------------------------------------------------------------- +void Settings::setMultiScreenOverlayEnabled(bool enabled) +{ + if (m_multiScreenOverlayEnabled == enabled) return; + m_multiScreenOverlayEnabled = enabled; + logDebug(lcSettings) << "multi-screen-overlay = " << m_multiScreenOverlayEnabled; + emit multiScreenOverlayEnabledChanged(m_multiScreenOverlayEnabled); +} + // ------------------------------------------------------------------------------------------------- void Settings::setOverlayDisabled(bool disabled) { diff --git a/src/settings.h b/src/settings.h index 2cbc8f37..d7dee98c 100644 --- a/src/settings.h +++ b/src/settings.h @@ -39,6 +39,8 @@ class Settings : public QObject Q_PROPERTY(double borderOpacity READ borderOpacity WRITE setBorderOpacity NOTIFY borderOpacityChanged) Q_PROPERTY(bool zoomEnabled READ zoomEnabled WRITE setZoomEnabled NOTIFY zoomEnabledChanged) Q_PROPERTY(double zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged) + Q_PROPERTY(bool multiScreenOverlayEnabled READ multiScreenOverlayEnabled + WRITE setMultiScreenOverlayEnabled NOTIFY multiScreenOverlayEnabledChanged) public: explicit Settings(QObject* parent = nullptr); @@ -84,6 +86,8 @@ class Settings : public QObject void setZoomEnabled(bool enabled); double zoomFactor() const { return m_zoomFactor; } void setZoomFactor(double factor); + bool multiScreenOverlayEnabled() const { return m_multiScreenOverlayEnabled; } + void setMultiScreenOverlayEnabled(bool enabled); bool overlayDisabled() const { return m_overlayDisabled; } void setOverlayDisabled(bool disabled); @@ -190,7 +194,7 @@ class Settings : public QObject void borderOpacityChanged(double opacity); void zoomEnabledChanged(bool enabled); void zoomFactorChanged(double zoomFactor); - void dblClickDurationChanged(int duration); + void multiScreenOverlayEnabledChanged(bool enabled); void overlayDisabledChanged(bool disabled); void presetLoaded(const QString& preset); @@ -217,11 +221,11 @@ class Settings : public QObject double m_borderOpacity = 0.8; bool m_zoomEnabled = false; double m_zoomFactor = 2.0; - int m_dblClickDuration = 300; bool m_showSpotShade = true; bool m_showCenterDot = false; bool m_spotRotationAllowed = false; bool m_showBorder=false; + bool m_multiScreenOverlayEnabled = false; bool m_overlayDisabled = false; std::vector> m_stringPropertyMap; From 60c0bd64459a44ba09b2bbb6b7147ab92dda16ad Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 4 Nov 2020 17:30:38 +0100 Subject: [PATCH 38/65] Refactor some window and screen handling functionality #80 --- src/projecteurapp.cc | 120 +++++++++++++++++++++++-------------------- src/projecteurapp.h | 4 ++ src/settings.cc | 12 ----- src/settings.h | 5 -- 4 files changed, 68 insertions(+), 73 deletions(-) diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index 7f7b4fd5..ad2caf6c 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -111,7 +111,8 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio return; } - const auto window = createOverlayWindow(); + m_overlayWindows.push_back(createOverlayWindow()); + const auto window = m_overlayWindows.front(); const auto actionPref = m_trayMenu->addAction(tr("&Preferences...")); connect(actionPref, &QAction::triggered, this, [this](){ @@ -240,46 +241,6 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio } }); - // Handling if the screen in the settings was changed - connect(m_settings, &Settings::screenChanged, this, [this, window](int screenIdx) - { - if (screenIdx >= screens().size()) - return; - - const auto screen = screens()[screenIdx]; - const bool wasVisible = window->isVisible(); - - m_overlayVisible = false; - emit overlayVisibleChanged(false); - - window->setFlags(window->flags() | Qt::WindowTransparentForInput); - window->setFlags(window->flags() & ~Qt::WindowStaysOnTopHint); - window->hide(); - - window->setGeometry(QRect(screen->geometry().topLeft(), QSize(300,200))); - window->setScreen(screen); - window->setGeometry(screen->geometry()); - - if (m_xcbOnWayland && !wasVisible) - { - if (m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog - && m_dialog->isMinimized()) { // keep Window minimized... - //Workaround for QTBUG-76354 (https://bugreports.qt.io/browse/QTBUG-76354) - m_dialog->showNormal(); - m_dialog->setWindowState(Qt::WindowMinimized); - } - } - - if (wasVisible) { - QTimer::singleShot(0, this, [this](){ - if (m_spotlight->spotActive()) - emit m_spotlight->spotActiveChanged(true); - else - m_spotlight->setSpotActive(true); - }); - } - }); - // Open local server for local IPC commands, e.g. from other command line instances QLocalServer::removeServer(localServerName()); if (m_localServer->listen(localServerName())) @@ -333,9 +294,9 @@ ProjecteurApplication::~ProjecteurApplication() // ------------------------------------------------------------------------------------------------- QWindow* ProjecteurApplication::createOverlayWindow() { - QObject *object = m_windowQmlComponent->create(); - object->setParent(m_qmlEngine); - return qobject_cast(object); + QObject *object = m_windowQmlComponent->create(); + object->setParent(m_qmlEngine); + return qobject_cast(object); } // ------------------------------------------------------------------------------------------------- @@ -347,7 +308,7 @@ void ProjecteurApplication::spotlightWindowClicked() // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::cursorExitedWindow() { - setScreenForCursorPos(); + if (m_spotlight->spotActive()) { setScreenForCursorPos(); } } // ------------------------------------------------------------------------------------------------- @@ -357,24 +318,71 @@ void ProjecteurApplication::cursorEntered(quint64 /*screen*/) } // ------------------------------------------------------------------------------------------------- -void ProjecteurApplication::setScreenForCursorPos() +void ProjecteurApplication::updateOverlayWindow(QWindow* window, QScreen* screen) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) - int screenNumber = 0; - const auto screen_cursor = this->screenAt(QCursor::pos()); + if (screen == nullptr) + return; + + if (window->screen() == screen && screen->geometry() == window->geometry()) { + return; + } - for (const auto& screen : screens()) { - if (screen_cursor == screen) { - m_settings->setScreen(screenNumber); - break; + window->setProperty("screenId", quint64(screen)); + + const bool wasVisible = window->isVisible(); + const bool wasSpotActive = m_spotlight->spotActive(); + + m_overlayVisible = false; + emit overlayVisibleChanged(false); + + window->setFlags(window->flags() | Qt::WindowTransparentForInput); + window->setFlags(window->flags() & ~Qt::WindowStaysOnTopHint); + window->hide(); + + window->setGeometry(QRect(screen->geometry().topLeft(), QSize(300,200))); + window->setScreen(screen); + window->setGeometry(screen->geometry()); + + if (m_xcbOnWayland && !wasVisible) + { + if (m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog + && m_dialog->isMinimized()) { // keep Window minimized... + //Workaround for QTBUG-76354 (https://bugreports.qt.io/browse/QTBUG-76354) + m_dialog->showNormal(); + m_dialog->setWindowState(Qt::WindowMinimized); } - ++screenNumber; } + + if (wasVisible && wasSpotActive) { + QTimer::singleShot(0, this, [this](){ + if (m_spotlight->spotActive()) + emit m_spotlight->spotActiveChanged(true); + else + m_spotlight->setSpotActive(true); + }); + } +} + +// ------------------------------------------------------------------------------------------------- +void ProjecteurApplication::setScreenForCursorPos() +{ + updateOverlayWindow(m_overlayWindows.first(), screenAtCursorPos()); +} + +// ------------------------------------------------------------------------------------------------- +QScreen* ProjecteurApplication::screenAtCursorPos() const +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)) + return this->screenAt(QCursor::pos()); #else - m_settings->setScreen(this->desktop()->screenNumber(QCursor::pos())); + const int screenNumber = this->desktop()->screenNumber(QCursor::pos()); + const auto screenList = screens(); + if (screenNumber >= 0 && screenNumber < screenList.size()) { + return screenList[screenNumber]; + } + return nullptr; #endif } - // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::readCommand(QLocalSocket* clientConnection) { diff --git a/src/projecteurapp.h b/src/projecteurapp.h index 5f2406b3..69735b18 100644 --- a/src/projecteurapp.h +++ b/src/projecteurapp.h @@ -53,7 +53,9 @@ private slots: private: void showPreferences(bool show = true); void setScreenForCursorPos(); + QScreen* screenAtCursorPos() const; QWindow* createOverlayWindow(); + void updateOverlayWindow(QWindow* window, QScreen* screen); private: std::unique_ptr m_trayIcon; @@ -69,6 +71,8 @@ private slots: std::map m_commandConnections; bool m_overlayVisible = false; const bool m_xcbOnWayland = false; + + QList m_overlayWindows; }; class ProjecteurCommandClientApp : public QCoreApplication diff --git a/src/settings.cc b/src/settings.cc index 4f1858d9..2eaa5e78 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -27,7 +27,6 @@ namespace { constexpr char dotOpacity[] = "dotOpacity"; constexpr char shadeColor[] = "shadeColor"; constexpr char shadeOpacity[] = "shadeOpacity"; - constexpr char screen[] = "screen"; constexpr char cursor[] = "cursor"; constexpr char spotShape[] = "spotShape"; constexpr char spotRotation[] ="spotRotation"; @@ -586,17 +585,6 @@ void Settings::setShadeOpacity(double opacity) } } -// ------------------------------------------------------------------------------------------------- -void Settings::setScreen(int screen) -{ - if (screen == m_screen) - return; - - m_screen = qMin(qMax(0, screen), 100); - m_settings->setValue(::settings::screen, m_screen); - emit screenChanged(m_screen); -} - // ------------------------------------------------------------------------------------------------- void Settings::setCursor(Qt::CursorShape cursor) { diff --git a/src/settings.h b/src/settings.h index d7dee98c..eed54caa 100644 --- a/src/settings.h +++ b/src/settings.h @@ -27,7 +27,6 @@ class Settings : public QObject Q_PROPERTY(double dotOpacity READ dotOpacity WRITE setDotOpacity NOTIFY dotOpacityChanged) Q_PROPERTY(QColor shadeColor READ shadeColor WRITE setShadeColor NOTIFY shadeColorChanged) Q_PROPERTY(double shadeOpacity READ shadeOpacity WRITE setShadeOpacity NOTIFY shadeOpacityChanged) - Q_PROPERTY(int screen READ screen WRITE setScreen NOTIFY screenChanged) Q_PROPERTY(Qt::CursorShape cursor READ cursor WRITE setCursor NOTIFY cursorChanged) Q_PROPERTY(QString spotShape READ spotShape WRITE setSpotShape NOTIFY spotShapeChanged) Q_PROPERTY(double spotRotation READ spotRotation WRITE setSpotRotation NOTIFY spotRotationChanged) @@ -65,8 +64,6 @@ class Settings : public QObject void setShadeColor(const QColor& color); double shadeOpacity() const { return m_shadeOpacity; } void setShadeOpacity(double opacity); - int screen() const { return m_screen; } - void setScreen(int screen); Qt::CursorShape cursor() const { return m_cursor; } void setCursor(Qt::CursorShape cursor); QString spotShape() const { return m_spotShape; } @@ -183,7 +180,6 @@ class Settings : public QObject void dotOpacityChanged(double opacity); void shadeColorChanged(const QColor& color); void shadeOpacityChanged(double opcacity); - void screenChanged(int screen); void cursorChanged(Qt::CursorShape cursor); void spotShapeChanged(const QString& spotShapeQmlComponent); void spotRotationChanged(double rotation); @@ -212,7 +208,6 @@ class Settings : public QObject double m_dotOpacity = 0.8; QColor m_shadeColor; double m_shadeOpacity = 0.3; - int m_screen = -1; // inital invalid value, see #26 Qt::CursorShape m_cursor = Qt::BlankCursor; QString m_spotShape; double m_spotRotation = 0.0; From 02c9a5d867224649d2d74719a02af10bfbfed5a6 Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 4 Nov 2020 17:34:43 +0100 Subject: [PATCH 39/65] Add early version of multi-screen overlay functionality #80 --- src/preferencesdlg.cc | 1 - src/projecteurapp.cc | 139 ++++++++++++++++++++++++++++++------------ src/projecteurapp.h | 2 + src/settings.cc | 8 +++ 4 files changed, 110 insertions(+), 40 deletions(-) diff --git a/src/preferencesdlg.cc b/src/preferencesdlg.cc index 003df835..3d6857c7 100644 --- a/src/preferencesdlg.cc +++ b/src/preferencesdlg.cc @@ -659,7 +659,6 @@ QWidget* PreferencesDialog::createMultiScreenWidget(Settings* settings) const auto cb = new QCheckBox(tr("Enable multi-screen overlay"), this); cb->setChecked(settings->multiScreenOverlayEnabled()); connect(cb, &QCheckBox::toggled, settings, &Settings::setMultiScreenOverlayEnabled); - cb->setDisabled(true); // not yet implemented return cb; } diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index ad2caf6c..f2870613 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -111,8 +111,8 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio return; } - m_overlayWindows.push_back(createOverlayWindow()); - const auto window = m_overlayWindows.front(); + setupScreenOverlays(); + connect(m_settings, &Settings::multiScreenOverlayEnabledChanged, this, [this](){ setupScreenOverlays(); }); const auto actionPref = m_trayMenu->addAction(tr("&Preferences...")); connect(actionPref, &QAction::triggered, this, [this](){ @@ -178,64 +178,68 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio actionQuit->trigger(); }); - window->setFlags(window->flags() | Qt::WindowTransparentForInput | Qt::Tool); - connect(this, &ProjecteurApplication::aboutToQuit, window, [window](){ if (window) window->close(); }); + connect(this, &ProjecteurApplication::aboutToQuit, this, [this](){ + for (const auto window : m_overlayWindows) { window->close(); } + m_overlayWindows.clear(); + }); // Handling of spotlight window when mouse move events from spotlight device are detected connect(m_spotlight, &Spotlight::spotActiveChanged, this, - [window, desktopImageProvider, this](bool active) + [desktopImageProvider, this](bool active) { if (active && !m_settings->overlayDisabled()) { - setScreenForCursorPos(); - - window->setFlags(window->flags() | Qt::WindowStaysOnTopHint); - window->setFlags(window->flags() & ~Qt::SplashScreen); - window->setFlags(window->flags() | Qt::ToolTip); - window->setFlags(window->flags() & ~Qt::WindowTransparentForInput); + if (!m_settings->multiScreenOverlayEnabled()) setScreenForCursorPos(); - if (window->screen()) + for (const auto window : m_overlayWindows) { - if (m_settings->zoomEnabled()) { - desktopImageProvider->setPixmap(m_linuxDesktop->grabScreen(window->screen())); - } + window->setFlags(window->flags() | Qt::WindowStaysOnTopHint); + window->setFlags(window->flags() & ~Qt::SplashScreen); + window->setFlags(window->flags() | Qt::ToolTip); + window->setFlags(window->flags() & ~Qt::WindowTransparentForInput); + + if (window->screen()) + { + if (m_settings->zoomEnabled()) { + desktopImageProvider->setPixmap(m_linuxDesktop->grabScreen(window->screen())); + } - const auto screenGeometry = window->screen()->geometry(); - if (window->geometry() != screenGeometry) { - window->setGeometry(screenGeometry); + const auto screenGeometry = window->screen()->geometry(); + if (window->geometry() != screenGeometry) { + window->setGeometry(screenGeometry); + } + window->setPosition(screenGeometry.topLeft()); } - window->setPosition(screenGeometry.topLeft()); + window->showFullScreen(); + window->raise(); } - window->showFullScreen(); m_overlayVisible = true; emit overlayVisibleChanged(true); - window->raise(); } else { m_overlayVisible = false; emit overlayVisibleChanged(false); - window->setFlags(window->flags() | Qt::WindowTransparentForInput); - window->setFlags(window->flags() & ~Qt::WindowStaysOnTopHint); - // Workaround for 'xcb' on Wayland session (default on Ubuntu) - // .. the window in that case is not transparent for inputs and cannot be clicked through. - // --> hide the window, although animations will not be visible - if (m_xcbOnWayland) + for (const auto window : m_overlayWindows) { - window->hide(); - if (m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog - && m_dialog->isMinimized()) { // keep Window minimized... - //Workaround for QTBUG-76354 (https://bugreports.qt.io/browse/QTBUG-76354) - m_dialog->showNormal(); - m_dialog->setWindowState(Qt::WindowMinimized); - } + window->setFlags(window->flags() | Qt::WindowTransparentForInput); + window->setFlags(window->flags() & ~Qt::WindowStaysOnTopHint); + // Workaround for 'xcb' on Wayland session (default on Ubuntu) + // .. the window in that case is not transparent for inputs and cannot be clicked through. + // --> hide the window, although animations will not be visible + if (m_xcbOnWayland) window->hide(); + } + if (m_xcbOnWayland && m_dialog->mode() == PreferencesDialog::Mode::MinimizeOnlyDialog + && m_dialog->isMinimized()) { // keep Window minimized... + //Workaround for QTBUG-76354 (https://bugreports.qt.io/browse/QTBUG-76354) + m_dialog->showNormal(); + m_dialog->setWindowState(Qt::WindowMinimized); } } }); - connect(window, &QWindow::visibleChanged, this, [this](bool v){ - logDebug(mainapp) << tr("Spotlight window visible = ") << v; - if (!v && m_dialog->isVisible()) { + connect(m_spotlight, &Spotlight::spotActiveChanged, this, [this](bool active){ + if (!active && m_dialog->isVisible()) { m_dialog->raise(); m_dialog->activateWindow(); } @@ -296,7 +300,9 @@ QWindow* ProjecteurApplication::createOverlayWindow() { QObject *object = m_windowQmlComponent->create(); object->setParent(m_qmlEngine); - return qobject_cast(object); + const auto window = qobject_cast(object); + window->setFlags(window->flags() | Qt::WindowTransparentForInput | Qt::Tool); + return window; } // ------------------------------------------------------------------------------------------------- @@ -308,7 +314,7 @@ void ProjecteurApplication::spotlightWindowClicked() // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::cursorExitedWindow() { - if (m_spotlight->spotActive()) { setScreenForCursorPos(); } + if (m_spotlight->spotActive() && !m_settings->multiScreenOverlayEnabled()) { setScreenForCursorPos(); } } // ------------------------------------------------------------------------------------------------- @@ -383,6 +389,61 @@ QScreen* ProjecteurApplication::screenAtCursorPos() const return nullptr; #endif } + +// ------------------------------------------------------------------------------------------------- +void ProjecteurApplication::setupScreenOverlays() +{ + // Adapt number of overlay windows depending on multiScreenOverlayEnabled() and + // the number of screens + const auto currentScreens = screens(); + if (currentScreens.size() == 0) + { + m_screenWindowMap.clear(); + for (const auto window : m_overlayWindows) { window->deleteLater(); } + m_overlayWindows.clear(); + return; + } + + m_screenWindowMap.clear(); + const int numOverlayWindows = m_settings->multiScreenOverlayEnabled() ? currentScreens.size() : 1; + const bool wasSpotActive = m_spotlight->spotActive(); + + while (m_overlayWindows.size() > numOverlayWindows) { + m_overlayWindows.back()->deleteLater(); + m_overlayWindows.pop_back(); + } + + while (m_overlayWindows.size() < numOverlayWindows) { + m_overlayWindows.push_back(createOverlayWindow()); + } + + // Default behavior - only one overlay window that is moved across sreens + if (!m_settings->multiScreenOverlayEnabled()) + { + for (const auto screen : currentScreens) { + m_screenWindowMap[screen] = m_overlayWindows.front(); + } + } + else + { + auto wit = m_overlayWindows.cbegin(); + for (const auto screen : currentScreens) { + m_screenWindowMap[screen] = (*wit); + updateOverlayWindow(*wit, screen); + ++wit; + } + } + + if (wasSpotActive) { + QTimer::singleShot(0, this, [this](){ + if (m_spotlight->spotActive()) + emit m_spotlight->spotActiveChanged(true); + else + m_spotlight->setSpotActive(true); + }); + } +} + // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::readCommand(QLocalSocket* clientConnection) { diff --git a/src/projecteurapp.h b/src/projecteurapp.h index 69735b18..87b56e08 100644 --- a/src/projecteurapp.h +++ b/src/projecteurapp.h @@ -56,6 +56,7 @@ private slots: QScreen* screenAtCursorPos() const; QWindow* createOverlayWindow(); void updateOverlayWindow(QWindow* window, QScreen* screen); + void setupScreenOverlays(); private: std::unique_ptr m_trayIcon; @@ -73,6 +74,7 @@ private slots: const bool m_xcbOnWayland = false; QList m_overlayWindows; + std::map m_screenWindowMap; }; class ProjecteurCommandClientApp : public QCoreApplication diff --git a/src/settings.cc b/src/settings.cc index 2eaa5e78..bf6a45d6 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -36,6 +36,7 @@ namespace { constexpr char borderOpacity[] = "borderOpacity"; constexpr char zoomEnabled[] = "enableZoom"; constexpr char zoomFactor[] = "zoomFactor"; + constexpr char multiScreenOverlay[] = "multiScreenOverlay"; // -- device specific constexpr char inputSequenceInterval[] = "inputSequenceInterval"; @@ -59,6 +60,7 @@ namespace { constexpr double borderOpacity = 0.8; constexpr bool zoomEnabled = false; constexpr double zoomFactor = 2.0; + constexpr bool multiScreenOverlay = false; // -- device specific defaults constexpr int inputSequenceInterval = 250; @@ -163,6 +165,8 @@ void Settings::initializeStringProperties() // -- spot settings map.emplace_back( "spot.overlay", StringProperty{ StringProperty::Bool, {false, true}, [this](const QString& value){ setOverlayDisabled(!toBool(value)); } } ); + map.emplace_back( "spot.multi-screen", StringProperty{ StringProperty::Bool, {false, true}, + [this](const QString& value){ setMultiScreenOverlayEnabled(toBool(value)); } } ); map.emplace_back( "spot.size", StringProperty{ StringProperty::Integer, {::settings::ranges::spotSize.min, ::settings::ranges::spotSize.max}, [this](const QString& value){ setSpotSize(value.toInt()); } } ); @@ -292,6 +296,7 @@ void Settings::setDefaults() setBorderOpacity(settings::defaultValue::borderOpacity); setZoomEnabled(settings::defaultValue::zoomEnabled); setZoomFactor(settings::defaultValue::zoomFactor); + setMultiScreenOverlayEnabled(settings::defaultValue::multiScreenOverlay); shapeSettingsSetDefaults(); } @@ -458,6 +463,7 @@ void Settings::load(const QString& preset) setBorderOpacity(m_settings->value(s+::settings::borderOpacity, settings::defaultValue::borderOpacity).toDouble()); setZoomEnabled(m_settings->value(s+::settings::zoomEnabled, settings::defaultValue::zoomEnabled).toBool()); setZoomFactor(m_settings->value(s+::settings::zoomFactor, settings::defaultValue::zoomFactor).toDouble()); + setMultiScreenOverlayEnabled(m_settings->value(s+::settings::multiScreenOverlay, settings::defaultValue::multiScreenOverlay).toBool()); shapeSettingsLoad(preset); } @@ -483,6 +489,7 @@ void Settings::savePreset(const QString& preset) m_settings->setValue(section+::settings::borderOpacity, m_borderOpacity); m_settings->setValue(section+::settings::zoomEnabled, m_zoomEnabled); m_settings->setValue(section+::settings::zoomFactor, m_zoomFactor); + m_settings->setValue(section+::settings::multiScreenOverlay, m_multiScreenOverlayEnabled); shapeSettingsSavePreset(preset); m_presetModel->addPreset(preset); @@ -751,6 +758,7 @@ void Settings::setMultiScreenOverlayEnabled(bool enabled) { if (m_multiScreenOverlayEnabled == enabled) return; m_multiScreenOverlayEnabled = enabled; + m_settings->setValue(::settings::multiScreenOverlay, m_multiScreenOverlayEnabled); logDebug(lcSettings) << "multi-screen-overlay = " << m_multiScreenOverlayEnabled; emit multiScreenOverlayEnabledChanged(m_multiScreenOverlayEnabled); } From b680459a54c22e88140b98fdadfa7ae75eaaa63a Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 4 Nov 2020 19:29:20 +0100 Subject: [PATCH 40/65] Fix multi-sreen setting behavior in preferences dialog #80 --- src/preferencesdlg.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/preferencesdlg.cc b/src/preferencesdlg.cc index 3d6857c7..04988673 100644 --- a/src/preferencesdlg.cc +++ b/src/preferencesdlg.cc @@ -659,6 +659,8 @@ QWidget* PreferencesDialog::createMultiScreenWidget(Settings* settings) const auto cb = new QCheckBox(tr("Enable multi-screen overlay"), this); cb->setChecked(settings->multiScreenOverlayEnabled()); connect(cb, &QCheckBox::toggled, settings, &Settings::setMultiScreenOverlayEnabled); + connect(settings, &Settings::multiScreenOverlayEnabledChanged, cb, &QCheckBox::setChecked); + connect(settings, &Settings::multiScreenOverlayEnabledChanged, this, &PreferencesDialog::resetPresetCombo); return cb; } From 26283d57f9c016cb625590229d28a5be5f68a22a Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 10 Nov 2020 09:05:02 +0100 Subject: [PATCH 41/65] Only show spotlight on current screen for cursor position #80 --- qml/main.qml | 6 +++++- src/projecteurapp.cc | 35 +++++++++++++++++++++++++++++++++-- src/projecteurapp.h | 5 +++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/qml/main.qml b/qml/main.qml index b58e2e83..d6ab719a 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -8,6 +8,7 @@ import Projecteur.Utils 1.0 as Utils Window { id: mainWindow property var screenId: -1 + readonly property bool showSpot: ProjecteurApp.currentSpotScreen === screenId width: 300; height: 200 @@ -72,8 +73,11 @@ Window { Rectangle { property int spotSize: (mainWindow.height / 100.0) * Settings.spotSize id: centerRect + readonly property int dynamicHeight: + mainWindow.showSpot ? spotSize > 50 + ? Math.min(spotSize, mainWindow.height) : 50 : 0; opacity: Settings.shadeOpacity - height: spotSize > 50 ? Math.min(spotSize, mainWindow.height) : 50; + height: dynamicHeight width: height x: ma.mouseX - width/2 y: ma.mouseY - height/2 diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index f2870613..ba14e446 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -111,8 +111,25 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio return; } + // Setup screen overlay windows setupScreenOverlays(); + + // React to multi-screen and overlay disabled changes in settings. connect(m_settings, &Settings::multiScreenOverlayEnabledChanged, this, [this](){ setupScreenOverlays(); }); + connect(m_settings, &Settings::overlayDisabledChanged, this, [this](bool disabled){ + if (disabled) { + if (m_spotlight->spotActive()) m_spotlight->setSpotActive(false); + else emit m_spotlight->spotActiveChanged(false); + } + else { + QTimer::singleShot(0, this, [this](){ + if (m_spotlight->spotActive()) + emit m_spotlight->spotActiveChanged(true); + else + m_spotlight->setSpotActive(true); + }); + } + }); const auto actionPref = m_trayMenu->addAction(tr("&Preferences...")); connect(actionPref, &QAction::triggered, this, [this](){ @@ -318,9 +335,9 @@ void ProjecteurApplication::cursorExitedWindow() } // ------------------------------------------------------------------------------------------------- -void ProjecteurApplication::cursorEntered(quint64 /*screen*/) +void ProjecteurApplication::cursorEntered(quint64 screen) { - // TODO + setCurrentSpotScreen(screen); } // ------------------------------------------------------------------------------------------------- @@ -444,6 +461,20 @@ void ProjecteurApplication::setupScreenOverlays() } } +// ------------------------------------------------------------------------------------------------- +quint64 ProjecteurApplication::currentSpotScreen() const +{ + return m_currentSpotScreen; +} + +// ------------------------------------------------------------------------------------------------- +void ProjecteurApplication::setCurrentSpotScreen(quint64 screen) +{ + if (m_currentSpotScreen == screen) return; + m_currentSpotScreen = screen; + emit currentSpotScreenChanged(m_currentSpotScreen); +} + // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::readCommand(QLocalSocket* clientConnection) { diff --git a/src/projecteurapp.h b/src/projecteurapp.h index 87b56e08..3a16d2a4 100644 --- a/src/projecteurapp.h +++ b/src/projecteurapp.h @@ -23,6 +23,7 @@ class ProjecteurApplication : public QApplication { Q_OBJECT Q_PROPERTY(bool overlayVisible READ overlayVisible NOTIFY overlayVisibleChanged) + Q_PROPERTY(quint64 currentSpotScreen READ currentSpotScreen NOTIFY currentSpotScreenChanged) public: struct Options { @@ -41,6 +42,7 @@ class ProjecteurApplication : public QApplication signals: void overlayVisibleChanged(bool visible); + void currentSpotScreenChanged(quint64 screen); public slots: void cursorExitedWindow(); @@ -57,6 +59,8 @@ private slots: QWindow* createOverlayWindow(); void updateOverlayWindow(QWindow* window, QScreen* screen); void setupScreenOverlays(); + quint64 currentSpotScreen() const; + void setCurrentSpotScreen(quint64 screen); private: std::unique_ptr m_trayIcon; @@ -75,6 +79,7 @@ private slots: QList m_overlayWindows; std::map m_screenWindowMap; + quint64 m_currentSpotScreen = 0; }; class ProjecteurCommandClientApp : public QCoreApplication From b70324444f40a4ba6571967d4c96557f16f65888 Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 10 Nov 2020 18:27:37 +0100 Subject: [PATCH 42/65] Handle visual moving of spot from one screen to another #80 --- qml/main.qml | 27 ++++++++++++++++++--------- src/projecteurapp.cc | 20 ++++++++++++++++++++ src/projecteurapp.h | 6 ++++++ src/settings.h | 2 +- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/qml/main.qml b/qml/main.qml index d6ab719a..3ce56303 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -8,7 +8,7 @@ import Projecteur.Utils 1.0 as Utils Window { id: mainWindow property var screenId: -1 - readonly property bool showSpot: ProjecteurApp.currentSpotScreen === screenId + readonly property bool spotOnCurrentWindow: ProjecteurApp.currentSpotScreen === screenId width: 300; height: 200 @@ -49,7 +49,7 @@ Window { } OpacityMask { - visible: Settings.zoomEnabled + visible: Settings.zoomEnabled && mainWindow.spotOnCurrentWindow cached: true anchors.fill: centerRect source: desktopItem @@ -61,30 +61,39 @@ Window { anchors.fill: parent MouseArea { id: ma + + readonly property bool calculateMapping: Settings.multiScreenOverlayEnabled && !mainWindow.spotOnCurrentWindow + readonly property point globalPos: calculateMapping ? ProjecteurApp.currentCursorPos : Qt.point(0,0) + readonly property point mappedPos: calculateMapping ? mainWindow.contentItem.mapFromGlobal(globalPos.x, globalPos.y) : globalPos + readonly property int posX: spotOnCurrentWindow ? mouseX : mappedPos.x + readonly property int posY: spotOnCurrentWindow ? mouseY : mappedPos.y + cursorShape: Settings.cursor anchors.fill: parent hoverEnabled: true onClicked: { ProjecteurApp.spotlightWindowClicked() } onExited: { ProjecteurApp.cursorExitedWindow() } onEntered: { ProjecteurApp.cursorEntered(screenId) } + onPositionChanged: { + if (Settings.multiScreenOverlayEnabled) { + ProjecteurApp.cursorPositionChanged( + mainWindow.contentItem.mapToGlobal(mouse.x, mouse.y)) + } + } } } Rectangle { property int spotSize: (mainWindow.height / 100.0) * Settings.spotSize id: centerRect - readonly property int dynamicHeight: - mainWindow.showSpot ? spotSize > 50 - ? Math.min(spotSize, mainWindow.height) : 50 : 0; opacity: Settings.shadeOpacity - height: dynamicHeight + height: spotSize > 50 ? Math.min(spotSize, mainWindow.height) : 50 width: height - x: ma.mouseX - width/2 - y: ma.mouseY - height/2 + x: ma.posX - width/2 + y: ma.posY - height/2 color: Settings.shadeColor visible: false enabled: false - } Loader { diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index ba14e446..83a8c607 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -340,6 +340,12 @@ void ProjecteurApplication::cursorEntered(quint64 screen) setCurrentSpotScreen(screen); } +// ------------------------------------------------------------------------------------------------- +void ProjecteurApplication::cursorPositionChanged(const QPoint& pos) +{ + setCurrentCursorPos(pos); +} + // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::updateOverlayWindow(QWindow* window, QScreen* screen) { @@ -475,6 +481,20 @@ void ProjecteurApplication::setCurrentSpotScreen(quint64 screen) emit currentSpotScreenChanged(m_currentSpotScreen); } +// ------------------------------------------------------------------------------------------------- +QPoint ProjecteurApplication::currentCursorPos() const +{ + return m_currentCursorPos; +} + +// ------------------------------------------------------------------------------------------------- +void ProjecteurApplication::setCurrentCursorPos(const QPoint& pos) +{ + if (pos == m_currentCursorPos) return; + m_currentCursorPos = pos; + emit currentCursorPosChanged(m_currentCursorPos); +} + // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::readCommand(QLocalSocket* clientConnection) { diff --git a/src/projecteurapp.h b/src/projecteurapp.h index 3a16d2a4..8f2dc8dd 100644 --- a/src/projecteurapp.h +++ b/src/projecteurapp.h @@ -24,6 +24,7 @@ class ProjecteurApplication : public QApplication Q_OBJECT Q_PROPERTY(bool overlayVisible READ overlayVisible NOTIFY overlayVisibleChanged) Q_PROPERTY(quint64 currentSpotScreen READ currentSpotScreen NOTIFY currentSpotScreenChanged) + Q_PROPERTY(QPoint currentCursorPos READ currentCursorPos NOTIFY currentCursorPosChanged) public: struct Options { @@ -43,11 +44,13 @@ class ProjecteurApplication : public QApplication signals: void overlayVisibleChanged(bool visible); void currentSpotScreenChanged(quint64 screen); + void currentCursorPosChanged(const QPoint& pos); public slots: void cursorExitedWindow(); void cursorEntered(quint64 screen); void spotlightWindowClicked(); + void cursorPositionChanged(const QPoint& pos); private slots: void readCommand(QLocalSocket* client); @@ -61,6 +64,8 @@ private slots: void setupScreenOverlays(); quint64 currentSpotScreen() const; void setCurrentSpotScreen(quint64 screen); + QPoint currentCursorPos() const; + void setCurrentCursorPos(const QPoint& pos); private: std::unique_ptr m_trayIcon; @@ -80,6 +85,7 @@ private slots: QList m_overlayWindows; std::map m_screenWindowMap; quint64 m_currentSpotScreen = 0; + QPoint m_currentCursorPos; }; class ProjecteurCommandClientApp : public QCoreApplication diff --git a/src/settings.h b/src/settings.h index eed54caa..a394faec 100644 --- a/src/settings.h +++ b/src/settings.h @@ -39,7 +39,7 @@ class Settings : public QObject Q_PROPERTY(bool zoomEnabled READ zoomEnabled WRITE setZoomEnabled NOTIFY zoomEnabledChanged) Q_PROPERTY(double zoomFactor READ zoomFactor WRITE setZoomFactor NOTIFY zoomFactorChanged) Q_PROPERTY(bool multiScreenOverlayEnabled READ multiScreenOverlayEnabled - WRITE setMultiScreenOverlayEnabled NOTIFY multiScreenOverlayEnabledChanged) + WRITE setMultiScreenOverlayEnabled NOTIFY multiScreenOverlayEnabledChanged) public: explicit Settings(QObject* parent = nullptr); From 7ac7876319ea90e2b4c9da7260a3e34d9a3031f1 Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 10 Nov 2020 18:46:10 +0100 Subject: [PATCH 43/65] Handle adding/removing screens and screen geometry changes #80 --- src/projecteurapp.cc | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index 83a8c607..77e0eb73 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -131,11 +131,17 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio } }); + // Re-setup screen overlay(s) when a screen is added or removed + connect(this, &ProjecteurApplication::screenAdded, this, [this](){ setupScreenOverlays(); }); + connect(this, &ProjecteurApplication::screenRemoved, this, [this](){ setupScreenOverlays(); }); + + // add and connect 'Preferences' tray menu action const auto actionPref = m_trayMenu->addAction(tr("&Preferences...")); connect(actionPref, &QAction::triggered, this, [this](){ this->showPreferences(true); }); + // add and and connect 'About' tray menu action const auto actionAbout = m_trayMenu->addAction(tr("&About")); connect(actionAbout, &QAction::triggered, this, [this]() { @@ -416,18 +422,36 @@ QScreen* ProjecteurApplication::screenAtCursorPos() const // ------------------------------------------------------------------------------------------------- void ProjecteurApplication::setupScreenOverlays() { - // Adapt number of overlay windows depending on multiScreenOverlayEnabled() and - // the number of screens + m_screenWindowMap.clear(); + const auto currentScreens = screens(); if (currentScreens.size() == 0) { - m_screenWindowMap.clear(); for (const auto window : m_overlayWindows) { window->deleteLater(); } m_overlayWindows.clear(); return; } - m_screenWindowMap.clear(); + // disconnect any connected screen signals previously connected to `this` + // and connect to geometryChanged signal to update overlay windows on screen geometry changes + for (const auto screen : currentScreens) { + disconnect(screen, nullptr, this, nullptr); + connect(screen, &QScreen::geometryChanged, this, [this, screen]() + { + if (m_settings->multiScreenOverlayEnabled()) + { + const auto it = m_screenWindowMap.find(screen); + if (it == m_screenWindowMap.cend()) return; + updateOverlayWindow(it->second, it->first); + } + else { + setScreenForCursorPos(); + } + }); + } + + // Adapt number of overlay windows depending on multiScreenOverlayEnabled() and + // the number of screens const int numOverlayWindows = m_settings->multiScreenOverlayEnabled() ? currentScreens.size() : 1; const bool wasSpotActive = m_spotlight->spotActive(); @@ -448,7 +472,7 @@ void ProjecteurApplication::setupScreenOverlays() } } else - { + { // multi-screen overlays enabled: assign overlay windows to screens auto wit = m_overlayWindows.cbegin(); for (const auto screen : currentScreens) { m_screenWindowMap[screen] = (*wit); @@ -457,6 +481,8 @@ void ProjecteurApplication::setupScreenOverlays() } } + // If the spotlight was active was active when calling the setup function, + // make sure it will be activated again. if (wasSpotActive) { QTimer::singleShot(0, this, [this](){ if (m_spotlight->spotActive()) From 2d2c4270e93506752c3499c4cbf42a64141e7363 Mon Sep 17 00:00:00 2001 From: Jahn Date: Tue, 10 Nov 2020 18:47:29 +0100 Subject: [PATCH 44/65] Update bash-completion and minor cleanup. #80 --- cmake/templates/projecteur.bash-completion | 13 +++++++++++++ src/actiondelegate.cc | 2 -- src/colorselector.cc | 2 +- src/deviceinput.h | 1 + src/devicescan.h | 1 - src/preferencesdlg.cc | 12 ++++++------ src/spotlight.cc | 1 - 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/cmake/templates/projecteur.bash-completion b/cmake/templates/projecteur.bash-completion index 70212245..111e87b2 100644 --- a/cmake/templates/projecteur.bash-completion +++ b/cmake/templates/projecteur.bash-completion @@ -22,6 +22,7 @@ _projecteur() # Auto completion for commands and properties local commands="quit spot= settings= preset=" commands="${commands} spot.size= spot.rotation= spot.shape= spot.shape.square.radius=" + commands="${commands} spot.multi-screen= spot.overlay=" commands="${commands} spot.shape.star.points= spot.shape.star.innerradius= spot.shape.ngon.sides=" commands="${commands} shade= shade.opacity= shade.color= dot= dot.size= dot.color= dot.opacity=" commands="${commands} border= border.size= border.color= border.opacity= zoom= zoom.factor=" @@ -36,6 +37,18 @@ _projecteur() COMPREPLY=( $(compgen -W "on off toggle" -- $cur) ) fi ;; + "spot.overlay") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "false true" -- $cur) ) + fi + ;; + "spot.multi-screen") + if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then + [ "${cur}" = "=" ] && cur="" + COMPREPLY=( $(compgen -W "false true" -- $cur) ) + fi + ;; "settings") if [ "${prev_prev}" = "=" ] || [ "${cur}" = "=" ]; then [ "${cur}" = "=" ] && cur="" diff --git a/src/actiondelegate.cc b/src/actiondelegate.cc index 05b84c7a..d8c029db 100644 --- a/src/actiondelegate.cc +++ b/src/actiondelegate.cc @@ -1,10 +1,8 @@ // This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md #include "actiondelegate.h" -#include "deviceinput.h" #include "inputmapconfig.h" #include "inputseqedit.h" -#include "logging.h" #include "nativekeyseqedit.h" #include "projecteur-icons-def.h" diff --git a/src/colorselector.cc b/src/colorselector.cc index 9c01854f..8789ab62 100644 --- a/src/colorselector.cc +++ b/src/colorselector.cc @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include diff --git a/src/deviceinput.h b/src/deviceinput.h index ebf32482..bce9c44f 100644 --- a/src/deviceinput.h +++ b/src/deviceinput.h @@ -2,6 +2,7 @@ #pragma once #include +#include #include #include diff --git a/src/devicescan.h b/src/devicescan.h index 4287e4b3..ea1b0c6c 100644 --- a/src/devicescan.h +++ b/src/devicescan.h @@ -6,7 +6,6 @@ #include #include -#include #include // ------------------------------------------------------------------------------------------------- diff --git a/src/preferencesdlg.cc b/src/preferencesdlg.cc index 04988673..d422dbbb 100644 --- a/src/preferencesdlg.cc +++ b/src/preferencesdlg.cc @@ -656,12 +656,12 @@ QGroupBox* PreferencesDialog::createCursorGroupBox(Settings* settings) // ------------------------------------------------------------------------------------------------- QWidget* PreferencesDialog::createMultiScreenWidget(Settings* settings) { - const auto cb = new QCheckBox(tr("Enable multi-screen overlay"), this); - cb->setChecked(settings->multiScreenOverlayEnabled()); - connect(cb, &QCheckBox::toggled, settings, &Settings::setMultiScreenOverlayEnabled); - connect(settings, &Settings::multiScreenOverlayEnabledChanged, cb, &QCheckBox::setChecked); - connect(settings, &Settings::multiScreenOverlayEnabledChanged, this, &PreferencesDialog::resetPresetCombo); - return cb; + const auto cb = new QCheckBox(tr("Enable multi-screen overlay"), this); + cb->setChecked(settings->multiScreenOverlayEnabled()); + connect(cb, &QCheckBox::toggled, settings, &Settings::setMultiScreenOverlayEnabled); + connect(settings, &Settings::multiScreenOverlayEnabledChanged, cb, &QCheckBox::setChecked); + connect(settings, &Settings::multiScreenOverlayEnabledChanged, this, &PreferencesDialog::resetPresetCombo); + return cb; } // ------------------------------------------------------------------------------------------------- diff --git a/src/spotlight.cc b/src/spotlight.cc index a8f376c3..ed4c2785 100644 --- a/src/spotlight.cc +++ b/src/spotlight.cc @@ -15,7 +15,6 @@ #include #include #include -#include DECLARE_LOGGING_CATEGORY(device) From 4143e6c9172722a89fc1fd93de623a86195d56a1 Mon Sep 17 00:00:00 2001 From: Jahn Date: Wed, 11 Nov 2020 18:36:18 +0100 Subject: [PATCH 45/65] Fix zoom on multi screen overlay #80 --- qml/main.qml | 2 +- src/imageitem.cc | 10 ---------- src/imageitem.h | 20 -------------------- src/projecteurapp.cc | 6 ++---- 4 files changed, 3 insertions(+), 35 deletions(-) diff --git a/qml/main.qml b/qml/main.qml index 3ce56303..78a2e084 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -9,6 +9,7 @@ Window { id: mainWindow property var screenId: -1 readonly property bool spotOnCurrentWindow: ProjecteurApp.currentSpotScreen === screenId + property alias desktopPixmap: desktopImage.pixmap width: 300; height: 200 @@ -37,7 +38,6 @@ Window { Utils.Image { id: desktopImage - pixmap: DesktopImage.pixmap smooth: rotation == 0 ? false : true rotation: -rotationItem.rotation readonly property real xOffset: Math.floor(parent.width/2.0 + ((rotationItem.width-mainWindow.width)/2)) diff --git a/src/imageitem.cc b/src/imageitem.cc index b05f5ca3..3c185a72 100644 --- a/src/imageitem.cc +++ b/src/imageitem.cc @@ -10,16 +10,6 @@ namespace { }(); } -PixmapProvider::PixmapProvider(QObject* parent) - : QObject(parent) -{} - -void PixmapProvider::setPixmap(QPixmap pm) -{ - m_pixmap = pm; - emit pixmapChanged(); -} - ProjecteurImage::ProjecteurImage(QQuickItem *parent) : QQuickPaintedItem(parent) { diff --git a/src/imageitem.h b/src/imageitem.h index d525ff51..104cf951 100644 --- a/src/imageitem.h +++ b/src/imageitem.h @@ -4,26 +4,6 @@ #include #include -class PixmapProvider : public QObject -{ - Q_OBJECT - Q_PROPERTY(QPixmap pixmap READ pixmap NOTIFY pixmapChanged) - -public: - explicit PixmapProvider(QObject* parent = nullptr); - virtual ~PixmapProvider() override = default; - - QPixmap pixmap() const { return m_pixmap; } - void setPixmap(QPixmap pm); - -signals: - void pixmapChanged(); - -private: - QPixmap m_pixmap; -}; - - class ProjecteurImage : public QQuickPaintedItem { Q_OBJECT diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index 77e0eb73..45d1e2a2 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -93,11 +93,9 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio } // Create qml engine and register context properties - const auto desktopImageProvider = new PixmapProvider(this); m_qmlEngine = new QQmlApplicationEngine(this); m_qmlEngine->rootContext()->setContextProperty("Settings", m_settings); m_qmlEngine->rootContext()->setContextProperty("PreferencesDialog", &*m_dialog); - m_qmlEngine->rootContext()->setContextProperty("DesktopImage", desktopImageProvider); m_qmlEngine->rootContext()->setContextProperty("ProjecteurApp", this); // Create qml overlay window component @@ -208,7 +206,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio // Handling of spotlight window when mouse move events from spotlight device are detected connect(m_spotlight, &Spotlight::spotActiveChanged, this, - [desktopImageProvider, this](bool active) + [this](bool active) { if (active && !m_settings->overlayDisabled()) { @@ -224,7 +222,7 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio if (window->screen()) { if (m_settings->zoomEnabled()) { - desktopImageProvider->setPixmap(m_linuxDesktop->grabScreen(window->screen())); + window->setProperty("desktopPixmap", m_linuxDesktop->grabScreen(window->screen())); } const auto screenGeometry = window->screen()->geometry(); From 8c6dbbaf6546c6af4c88e09cb78d1f061b0ae71f Mon Sep 17 00:00:00 2001 From: Jahn F Date: Wed, 11 Nov 2020 18:39:52 +0100 Subject: [PATCH 46/65] Feature/workflow codescan (#113) * Create codeql-analysis.yml * CodeQL fixes. --- .github/workflows/codeql-analysis.yml | 53 +++++++++++++++++++++++++++ src/actiondelegate.cc | 6 +-- src/deviceinput.cc | 1 - src/deviceswidget.cc | 1 - src/inputmapconfig.cc | 1 - src/inputseqedit.cc | 4 +- src/linuxdesktop.cc | 4 +- src/logging.cc | 2 - src/main.cc | 1 - src/nativekeyseqedit.cc | 2 +- src/preferencesdlg.cc | 1 + src/projecteurapp.cc | 1 - src/virtualdevice.cc | 25 +++---------- src/virtualdevice.h | 2 - 14 files changed, 67 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..a1084f9f --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,53 @@ +name: "CodeQL" + +on: + push: + branches: + - "**" + pull_request: + branches: [develop, master] + +jobs: + analyse: + name: Analyse + runs-on: ubuntu-latest + + steps: + - name: Install dependencies + run: | + sudo apt-get install pkg-config qtdeclarative5-dev \ + qttools5-dev-tools qttools5-dev \ + qt5-default libqt5x11extras5-dev + + - name: Checkout repository + uses: actions/checkout@v2 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # If this run was triggered by a pull request event, then checkout + # the head of the pull request instead of the merge commit. + - run: git checkout HEAD^2 + if: ${{ github.event_name == 'pull_request' }} + + - name: Configure and build Qt moc cpps + run: | + mkdir build && cd build + cmake .. + make projecteur_autogen + make projecteur_autogen/mocs_compilation.cpp.o + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + queries: +security-and-quality + + - name: Build project + run: | + cd build + make -j2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/src/actiondelegate.cc b/src/actiondelegate.cc index d8c029db..db1a461f 100644 --- a/src/actiondelegate.cc +++ b/src/actiondelegate.cc @@ -309,9 +309,9 @@ void ActionTypeDelegate::actionContextMenu(QWidget* parent, InputMapConfigModel* QMenu* menu = new QMenu(parent); - for (const auto& item : items) { - const auto qaction = menu->addAction(item.icon, item.text); - connect(qaction, &QAction::triggered, this, [model, index, type=item.type](){ + for (const auto& entry : items) { + const auto qaction = menu->addAction(entry.icon, entry.text); + connect(qaction, &QAction::triggered, this, [model, index, type=entry.type](){ model->setItemActionType(index, type); }); } diff --git a/src/deviceinput.cc b/src/deviceinput.cc index 5fff1eca..50927479 100644 --- a/src/deviceinput.cc +++ b/src/deviceinput.cc @@ -471,7 +471,6 @@ const NativeKeySequence& NativeKeySequence::predefined::meta() { static const NativeKeySequence ks = [](){ NativeKeySequence ks; - //ks.m_keySequence = QKeySequence::fromString("Meta"); ks.m_nativeModifiers.push_back(NativeKeySequence::LeftMeta); KeyEvent pressed; KeyEvent released; pressed.emplace_back(EV_KEY, KEY_LEFTMETA, 1); diff --git a/src/deviceswidget.cc b/src/deviceswidget.cc index 1111c3f2..dd3ca1c7 100644 --- a/src/deviceswidget.cc +++ b/src/deviceswidget.cc @@ -77,7 +77,6 @@ QWidget* DevicesWidget::createDevicesWidget(Settings* settings, Spotlight* spotl vLayout->addWidget(tabWidget); tabWidget->addTab(createInputMapperWidget(settings, spotlight), tr("Input Mapping")); -// tabWidget->addTab(createDeviceInfoWidget(spotlight), tr("Device Info")); return dw; } diff --git a/src/inputmapconfig.cc b/src/inputmapconfig.cc index 2e38be2d..29effdb1 100644 --- a/src/inputmapconfig.cc +++ b/src/inputmapconfig.cc @@ -278,7 +278,6 @@ InputMapConfigView::InputMapConfigView(QWidget* parent) : QTableView(parent) , m_actionTypeDelegate(new ActionTypeDelegate(this)) { - // verticalHeader()->setHidden(true); verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); const auto imSeqDelegate = new InputSeqDelegate(this); diff --git a/src/inputseqedit.cc b/src/inputseqedit.cc index 70f4a6df..1fa8c651 100644 --- a/src/inputseqedit.cc +++ b/src/inputseqedit.cc @@ -91,7 +91,7 @@ namespace { } int sequenceWidth = 0; - const int paddingX = QStaticText(" ").size().width(); + const int paddingX = static_cast(QStaticText(" ").size().width()); for (auto it = kes.cbegin(); it!=kes.cend(); ++it) { if (it != kes.cbegin()) sequenceWidth += paddingX; @@ -368,7 +368,7 @@ int InputSeqEdit::drawEmptyIndicator(int startX, QPainter& p, const QStyleOption const auto top = (option.rect.height() - textNone.size().height()) / 2; p.drawStaticText(startX + option.rect.left(), option.rect.top() + top, textNone); p.restore(); - return textNone.size().width(); + return static_cast(textNone.size().width()); } // ------------------------------------------------------------------------------------------------- diff --git a/src/linuxdesktop.cc b/src/linuxdesktop.cc index f33d661e..f3cbf133 100644 --- a/src/linuxdesktop.cc +++ b/src/linuxdesktop.cc @@ -59,8 +59,8 @@ namespace { QPixmap grabScreenVirtualDesktop(QScreen* screen) { QRect g; - for (const auto screen : QGuiApplication::screens()) { - g = g.united(screen->geometry()); + for (const auto s : QGuiApplication::screens()) { + g = g.united(s->geometry()); } QPixmap pm(QApplication::primaryScreen()->grabWindow( diff --git a/src/logging.cc b/src/logging.cc index c378a191..01bf313a 100644 --- a/src/logging.cc +++ b/src/logging.cc @@ -108,8 +108,6 @@ namespace { // - if that changes and multiple threads will log, this needs a serious overhaul - NOT thread safe void projecteurLogHandler(QtMsgType type, const QMessageLogContext &context, const QString &msgQString) { - // const char *file = context.file ? context.file : ""; - // const char *function = context.function ? context.function : ""; const char *category = context.category ? context.category : ""; #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)) diff --git a/src/main.cc b/src/main.cc index 0f1613a3..b0f6e4d1 100644 --- a/src/main.cc +++ b/src/main.cc @@ -324,7 +324,6 @@ int main(int argc, char *argv[]) return 43; } - //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); ProjecteurApplication app(argc, argv, options); return app.exec(); } diff --git a/src/nativekeyseqedit.cc b/src/nativekeyseqedit.cc index 4b92e564..898fbe6c 100644 --- a/src/nativekeyseqedit.cc +++ b/src/nativekeyseqedit.cc @@ -115,7 +115,7 @@ void NativeKeySeqEdit::paintEvent(QPaintEvent*) int xPos = (option.rect.height()-fm.height()) / 2; if (recording()) { - const int spacingX = QStaticText(" ").size().width(); + const int spacingX = static_cast(QStaticText(" ").size().width()); xPos += drawRecordingSymbol(xPos, p, option) + spacingX; if (m_recordedQtKeys.empty()) { xPos += drawPlaceHolderText(xPos, p, option, tr("Press shortcut...")); diff --git a/src/preferencesdlg.cc b/src/preferencesdlg.cc index d422dbbb..6606f12b 100644 --- a/src/preferencesdlg.cc +++ b/src/preferencesdlg.cc @@ -362,6 +362,7 @@ QGroupBox* PreferencesDialog::createShapeGroupBox(Settings* settings) spotGrid->addWidget(shapeRotationLabel, 5, 0); spotGrid->addWidget(shapeRotationSb, 5, 1); + // Function for updating all spotlight shape related widgets auto updateShapeSettingsWidgets = [settings, shapeCombo, shapeRotationSb, shapeRotationLabel, spotGrid, this]() { if (shapeCombo->currentIndex() == -1) return; diff --git a/src/projecteurapp.cc b/src/projecteurapp.cc index 45d1e2a2..dcf86a5a 100644 --- a/src/projecteurapp.cc +++ b/src/projecteurapp.cc @@ -174,7 +174,6 @@ ProjecteurApplication::ProjecteurApplication(int &argc, char **argv, const Optio [this](QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { - //static const bool isKDE = (qgetenv("XDG_CURRENT_DESKTOP") == QByteArray("KDE")); const auto trayGeometry = m_trayIcon->geometry(); // This usually won't give us a valid geometry, since Qt isn't drawing the tray icon itself if (trayGeometry.isValid()) { diff --git a/src/virtualdevice.cc b/src/virtualdevice.cc index 1dc318e5..2ca2ad96 100644 --- a/src/virtualdevice.cc +++ b/src/virtualdevice.cc @@ -7,6 +7,8 @@ #include #include +#include + LOGGING_CATEGORY(virtualdevice, "virtualdevice") namespace { @@ -36,14 +38,14 @@ std::shared_ptr VirtualDevice::create(const char* name, uint16_t virtualVersionId, const char* location) { - // Open the input device - if (access(location, F_OK) == -1) { + const QFileInfo fi(location); + if (!fi.exists()) { logWarn(virtualdevice) << VirtualDevice_::tr("File not found: %1").arg(location); logWarn(virtualdevice) << VirtualDevice_::tr("Please check if uinput kernel module is loaded"); return std::unique_ptr(); } - int fd = ::open(location, O_WRONLY | O_NDELAY); + const int fd = ::open(location, O_WRONLY | O_NDELAY); if (fd < 0) { logWarn(virtualdevice) << VirtualDevice_::tr("Unable to open: %1").arg(location); logWarn(virtualdevice) << VirtualDevice_::tr("Please check if current user has write access"); @@ -91,23 +93,6 @@ std::shared_ptr VirtualDevice::create(const char* name, return std::make_shared(Token{}, fd); } - -// Public methods to emit event from the device -void VirtualDevice::emitEvent(uint16_t type, uint16_t code, int val) -{ - // TODO fill in timestamp - input_event ie {{}, type, code, val}; - emitEvent(std::move(ie)); -} - -void VirtualDevice::emitEvent(struct input_event ie) -{ - const auto bytesWritten = write(m_uinpFd, &ie, sizeof(ie)); - if (bytesWritten != sizeof(ie)) { - logError(virtualdevice) << VirtualDevice_::tr("Error while writing to virtual device."); - } -} - void VirtualDevice::emitEvents(const struct input_event input_events[], size_t num) { if (const ssize_t sz = sizeof(input_event) * num) { diff --git a/src/virtualdevice.h b/src/virtualdevice.h index 17b2ec57..5f36e461 100644 --- a/src/virtualdevice.h +++ b/src/virtualdevice.h @@ -28,8 +28,6 @@ class VirtualDevice explicit VirtualDevice(Token, int fd); ~VirtualDevice(); - void emitEvent(uint16_t type, uint16_t code, int val); - void emitEvent(struct input_event ie); void emitEvents(const struct input_event[], size_t num); void emitEvents(const std::vector& events); }; From 9f8b87ff7d2def7369704d0f9a48da08b080f242 Mon Sep 17 00:00:00 2001 From: Jahn Date: Thu, 26 Nov 2020 20:31:23 +0100 Subject: [PATCH 47/65] Add generic hidraw subdevice connection. #6 --- src/device.cc | 104 +++++++++++++++++++++++++++++++++++++++------- src/device.h | 13 +++++- src/devicescan.cc | 11 ++--- src/spotlight.cc | 17 ++++++-- 4 files changed, 119 insertions(+), 26 deletions(-) diff --git a/src/device.cc b/src/device.cc index 9d3afcfd..3d0123a6 100644 --- a/src/device.cc +++ b/src/device.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -107,7 +108,7 @@ std::shared_ptr SubEventConnection::create(const DeviceScan: ioctl(evfd, EVIOCGID, &id); // get the event sub-device id // Check against given device id - if ( id.vendor != dc.deviceId().vendorId || id.product != dc.deviceId().productId) + if (id.vendor != dc.deviceId().vendorId || id.product != dc.deviceId().productId) { ::close(evfd); logDebug(device) << tr("Device id mismatch: %1 (%2:%3)") @@ -130,6 +131,20 @@ std::shared_ptr SubEventConnection::create(const DeviceScan: auto connection = std::make_shared(Token{}, sd.deviceFile); + if (!!(bitmask & (1 << EV_SYN))) connection->m_details.deviceFlags |= DeviceFlag::SynEvents; + if (!!(bitmask & (1 << EV_REP))) connection->m_details.deviceFlags |= DeviceFlag::RepEvents; + if (!!(bitmask & (1 << EV_KEY))) connection->m_details.deviceFlags |= DeviceFlag::KeyEvents; + if (!!(bitmask & (1 << EV_REL))) + { + unsigned long relEvents = 0; + ioctl(evfd, EVIOCGBIT(EV_REL, sizeof(relEvents)), &relEvents); + const bool hasRelXEvents = !!(relEvents & (1 << REL_X)); + const bool hasRelYEvents = !!(relEvents & (1 << REL_Y)); + if (hasRelXEvents && hasRelYEvents) { + connection->m_details.deviceFlags |= DeviceFlag::RelativeEvents; + } + } + connection->m_details.grabbed = [&dc, evfd, &sd]() { // Grab device inputs if a virtual device exists. @@ -145,19 +160,6 @@ std::shared_ptr SubEventConnection::create(const DeviceScan: return false; }(); - if (!!(bitmask & (1 << EV_SYN))) connection->m_details.deviceFlags |= DeviceFlag::SynEvents; - if (!!(bitmask & (1 << EV_REP))) connection->m_details.deviceFlags |= DeviceFlag::RepEvents; - if (!!(bitmask & (1 << EV_REL))) - { - unsigned long relEvents = 0; - ioctl(evfd, EVIOCGBIT(EV_REL, sizeof(relEvents)), &relEvents); - const bool hasRelXEvents = !!(relEvents & (1 << REL_X)); - const bool hasRelYEvents = !!(relEvents & (1 << REL_Y)); - if (hasRelXEvents && hasRelYEvents) { - connection->m_details.deviceFlags |= DeviceFlag::RelativeEvents; - } - } - fcntl(evfd, F_SETFL, fcntl(evfd, F_GETFL, 0) | O_NONBLOCK); if ((fcntl(evfd, F_GETFL, 0) & O_NONBLOCK) == O_NONBLOCK) { connection->m_details.deviceFlags |= DeviceFlag::NonBlocking; @@ -179,3 +181,77 @@ std::shared_ptr SubEventConnection::create(const DeviceScan: return connection; } + +// ------------------------------------------------------------------------------------------------- +SubHidrawConnection::SubHidrawConnection(Token, const QString& path) + : SubDeviceConnection(path, ConnectionType::Hidraw, ConnectionMode::ReadWrite) {} + +// ------------------------------------------------------------------------------------------------- +std::shared_ptr SubHidrawConnection::create(const DeviceScan::SubDevice& sd, + const DeviceConnection& dc) +{ + const int devfd = ::open(sd.deviceFile.toLocal8Bit().constData(), O_RDWR|O_NONBLOCK , 0); + + if (devfd == -1) { + logWarn(device) << tr("Could not open hidraw device '%1' for read/write.").arg(sd.deviceFile); + return std::shared_ptr(); + } + + int descriptorSize = 0; + // Get Report Descriptor Size + if (ioctl(devfd, HIDIOCGRDESCSIZE, &descriptorSize) < 0) { + logWarn(device) << tr("Could retrieve report descriptor size of hidraw device '%1'.").arg(sd.deviceFile); + return std::shared_ptr(); + } + + struct hidraw_report_descriptor reportDescriptor{}; + reportDescriptor.size = descriptorSize; + if (ioctl(devfd, HIDIOCGRDESC, &reportDescriptor) < 0) { + logWarn(device) << tr("Could retrieve report descriptor size of hidraw device '%1'.").arg(sd.deviceFile); + return std::shared_ptr(); + } + + struct hidraw_devinfo devinfo{}; + // get the hidraw sub-device id info + if (ioctl(devfd, HIDIOCGRAWINFO, &devinfo) < 0) { + logWarn(device) << tr("Could get info from hidraw device '%1'.").arg(sd.deviceFile); + return std::shared_ptr(); + }; + + // Check against given device id + if (static_cast(devinfo.vendor) != dc.deviceId().vendorId + || static_cast(devinfo.product) != dc.deviceId().productId) + { + ::close(devfd); + logDebug(device) << tr("Device id mismatch: %1 (%2:%3)") + .arg(sd.deviceFile) + .arg(static_cast(devinfo.vendor), 4, 16, QChar('0')) + .arg(static_cast(devinfo.product), 4, 16, QChar('0')); + return std::shared_ptr(); + } + + auto connection = std::make_shared(Token{}, sd.deviceFile); + + fcntl(devfd, F_SETFL, fcntl(devfd, F_GETFL, 0) | O_NONBLOCK); + if ((fcntl(devfd, F_GETFL, 0) & O_NONBLOCK) == O_NONBLOCK) { + connection->m_details.deviceFlags |= DeviceFlag::NonBlocking; + } + + // Create socket notifier + connection->m_notifier = std::make_unique(devfd, QSocketNotifier::Read); + QSocketNotifier* const notifier = connection->m_notifier.get(); + // Auto clean up and close descriptor on destruction of notifier + connect(notifier, &QSocketNotifier::destroyed, [notifier]() { + ::close(static_cast(notifier->socket())); + }); + + connection->m_details.phys = sd.phys; + + // TODO add vibration support for Logitech Spotlight and + // TODO generalize features and protocol for proprietary device features like vibration + // for not only the Spotlight device. + // len intensity + // unsigned char vibrate[] = {0x10, 0x01, 0x09, 0x1a, 0x00, 0xe8, 0x80}; + + return connection; +} diff --git a/src/device.h b/src/device.h index 82863fa3..23e974fc 100644 --- a/src/device.h +++ b/src/device.h @@ -81,6 +81,7 @@ enum class DeviceFlag : uint32_t { SynEvents = 1 << 1, RepEvents = 1 << 2, RelativeEvents = 1 << 3, + KeyEvents = 1 << 4, }; ENUM(DeviceFlag, DeviceFlags) @@ -165,4 +166,14 @@ class SubEventConnection : public SubDeviceConnection }; // ------------------------------------------------------------------------------------------------- -// TODO SubHidrawConnection +class SubHidrawConnection : public SubDeviceConnection +{ + Q_OBJECT + class Token{}; + +public: + static std::shared_ptr create(const DeviceScan::SubDevice& sd, + const DeviceConnection& dc); + + SubHidrawConnection(Token, const QString& path); +}; diff --git a/src/devicescan.cc b/src/devicescan.cc index cb46aa85..2972eb38 100644 --- a/src/devicescan.cc +++ b/src/devicescan.cc @@ -209,6 +209,8 @@ namespace DeviceScan { return *find_it; }(); + int eventSubDeviceCount = 0; + // Iterate over 'input' sub-dircectory, check for input-hid device nodes const QFileInfo inputSubdir(QDir(hidIt.filePath()).filePath("input")); if (inputSubdir.exists() || inputSubdir.isExecutable()) @@ -234,6 +236,7 @@ namespace DeviceScan { if (subDevice.deviceFile.isEmpty()) continue; subDevice.phys = readStringFromDeviceFile(QDir(inputIt.filePath()).filePath("phys")); + ++eventSubDeviceCount; // Check if device supports relative events const auto supportedEvents = readULongLongFromDeviceFile(QDir(inputIt.filePath()).filePath("capabilities/ev")); @@ -254,13 +257,7 @@ namespace DeviceScan { } } - // For the Logitech Spotlight we are only interested in the hidraw sub device that has no event - // device, if there is already an event device we skip hidraw detection for this sub-device. - const bool hasInputEventDevices - = std::any_of(rootDevice.subDevices.cbegin(), rootDevice.subDevices.cend(), - [](const SubDevice& sd) { return sd.type == SubDevice::Type::Event; }); - - if (hasInputEventDevices) continue; + if (eventSubDeviceCount > 0) continue; // Iterate over 'hidraw' sub-dircectory, check for hidraw device node const QFileInfo hidrawSubdir(QDir(hidIt.filePath()).filePath("hidraw")); diff --git a/src/spotlight.cc b/src/spotlight.cc index ed4c2785..430caac3 100644 --- a/src/spotlight.cc +++ b/src/spotlight.cc @@ -111,6 +111,7 @@ std::vector Spotlight::connectedDevices() const int Spotlight::connectDevices() { const auto scanResult = DeviceScan::getDevices(m_options.additionalDevices); + for (const auto& dev : scanResult.devices) { auto& dc = m_deviceConnections[dev.id]; @@ -121,12 +122,21 @@ int Spotlight::connectDevices() const bool anyConnectedBefore = anySpotlightDeviceConnected(); for (const auto& scanSubDevice : dev.subDevices) { - if (scanSubDevice.type != DeviceScan::SubDevice::Type::Event) continue; if (!scanSubDevice.deviceReadable) continue; if (dc->hasSubDevice(scanSubDevice.deviceFile)) continue; - auto subDeviceConnection = SubEventConnection::create(scanSubDevice, *dc); - if (!addInputEventHandler(subDeviceConnection)) continue; + std::shared_ptr subDeviceConnection = + [&scanSubDevice, &dc, this]() -> std::shared_ptr { + if (scanSubDevice.type == DeviceScan::SubDevice::Type::Event) { + auto devCon = SubEventConnection::create(scanSubDevice, *dc); + if (addInputEventHandler(devCon)) return devCon; + } else if (scanSubDevice.type == DeviceScan::SubDevice::Type::Hidraw) { + return SubHidrawConnection::create(scanSubDevice, *dc); + } + return std::shared_ptr(); + }(); + + if (!subDeviceConnection) continue; if (dc->subDeviceCount() == 0) { // Load Input mapping settings when first sub-device gets added. @@ -208,7 +218,6 @@ void Spotlight::removeDeviceConnection(const QString &devicePath) auto& dc = dc_it->second; if (dc->removeSubDevice(devicePath)) { - emit subDeviceDisconnected(dc_it->first, dc->deviceName(), devicePath); } From 17f0f8f6e653d3343744983fe646fd97fa26d6ab Mon Sep 17 00:00:00 2001 From: freddii Date: Thu, 14 Jan 2021 13:46:50 +0100 Subject: [PATCH 48/65] fixed wrong spelled words --- cmake/modules/GitVersion.cmake | 2 +- cmake/modules/LinuxPackaging.cmake | 2 +- cmake/modules/Translation.cmake | 2 +- src/actiondelegate.cc | 2 +- src/deviceinput.h | 4 ++-- src/devicescan.cc | 2 +- src/extra-devices.cc.in | 2 +- src/preferencesdlg.cc | 2 +- src/virtualdevice.h | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmake/modules/GitVersion.cmake b/cmake/modules/GitVersion.cmake index 2c9dd1ea..dd8cf7a9 100644 --- a/cmake/modules/GitVersion.cmake +++ b/cmake/modules/GitVersion.cmake @@ -433,7 +433,7 @@ function(add_version_info_custom_prefix target prefix directory) endif() if(${${prefix}_VERSION_SUCCESS}) - # All informations gathered via git + # All information gathered via git else() message(STATUS "Version-Info: Failure during version retrieval. Possible incomplete version information!") endif() diff --git a/cmake/modules/LinuxPackaging.cmake b/cmake/modules/LinuxPackaging.cmake index 5df2c2aa..deafbae2 100644 --- a/cmake/modules/LinuxPackaging.cmake +++ b/cmake/modules/LinuxPackaging.cmake @@ -17,7 +17,7 @@ list(APPEND _LinuxPackaging_MAP_dist_pkgtype ) set(_LinuxPackaging_default_pkgtype "TGZ") -# Funtion that adds 'dist-package' target +# Function that adds 'dist-package' target # Arguments: # PROJECT : Project name to package # TARGET : Main executable target with version information diff --git a/cmake/modules/Translation.cmake b/cmake/modules/Translation.cmake index b2704d59..ca199ca0 100644 --- a/cmake/modules/Translation.cmake +++ b/cmake/modules/Translation.cmake @@ -111,7 +111,7 @@ if(NOT TARGET qm_files) endif() # Main function to be used in the main build configuration scripts. -# Will add a target 'translations' that will create/copy all the neccessary +# Will add a target 'translations' that will create/copy all the necessary # .qm files to the given _target_dir for the given _languages. # This includes also the translations from qt itself. function(add_translations_target _prefix _target_dir _ts_dirs _languages) diff --git a/src/actiondelegate.cc b/src/actiondelegate.cc index db1a461f..ea8ecfd4 100644 --- a/src/actiondelegate.cc +++ b/src/actiondelegate.cc @@ -284,7 +284,7 @@ void ActionTypeDelegate::actionContextMenu(QWidget* parent, InputMapConfigModel* static std::vector items { {Action::Type::KeySequence, Font::Icon::keyboard_4, tr("Key Sequence")}, {Action::Type::CyclePresets, Font::Icon::connection_8, tr("Cycle Presets")}, - {Action::Type::ToggleSpotlight, Font::Icon::power_on_off_11, tr("Toogle Spotlight")}, + {Action::Type::ToggleSpotlight, Font::Icon::power_on_off_11, tr("Toggle Spotlight")}, }; static bool initIcons = []() diff --git a/src/deviceinput.h b/src/deviceinput.h index bce9c44f..dbdc8881 100644 --- a/src/deviceinput.h +++ b/src/deviceinput.h @@ -232,9 +232,9 @@ class InputMapper : public QObject void configurationChanged(); void recordingModeChanged(bool recording); void keyEventRecorded(const KeyEvent&); - // Right befor first key event recorded: + // Right before first key event recorded: void recordingStarted(); - // After key sequence interval timer timout or max sequence length reached + // After key sequence interval timer timeout or max sequence length reached void recordingFinished(bool canceled); // canceled if recordingMode was set to false instead of interval time out void actionMapped(std::shared_ptr action); diff --git a/src/devicescan.cc b/src/devicescan.cc index cb46aa85..9e5e9fab 100644 --- a/src/devicescan.cc +++ b/src/devicescan.cc @@ -7,7 +7,7 @@ #include -// Function declaration to check for extra devices, defintion in generated source +// Function declaration to check for extra devices, definition in generated source bool isExtraDeviceSupported(quint16 vendorId, quint16 productId); QString getExtraDeviceName(quint16 vendorId, quint16 productId); diff --git a/src/extra-devices.cc.in b/src/extra-devices.cc.in index ee11d93f..f775a54e 100644 --- a/src/extra-devices.cc.in +++ b/src/extra-devices.cc.in @@ -13,7 +13,7 @@ namespace { }; } -// Function declaration to check for extra devices, defintion in generated source +// Function declaration to check for extra devices, definition in generated source bool isExtraDeviceSupported(quint16 vendorId, quint16 productId) { const auto it = std::find_if(supportedExtraDevices.cbegin(), supportedExtraDevices.cend(), diff --git a/src/preferencesdlg.cc b/src/preferencesdlg.cc index 6606f12b..792c49e1 100644 --- a/src/preferencesdlg.cc +++ b/src/preferencesdlg.cc @@ -209,7 +209,7 @@ QWidget* PreferencesDialog::createPresetSelector(Settings* settings) text = m_presetCombo->currentText().trimmed(); } - if (m_presetCombo->findText(text) >= 0) { // Item with same name alrady exists + if (m_presetCombo->findText(text) >= 0) { // Item with same name already exists text.append(" (%1)"); for (int i = 2; i < 1000; ++i) { if (m_presetCombo->findText(text.arg(i)) < 0) { diff --git a/src/virtualdevice.h b/src/virtualdevice.h index 5f36e461..482ad543 100644 --- a/src/virtualdevice.h +++ b/src/virtualdevice.h @@ -1,6 +1,6 @@ // This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md -// Virtal Device to emit customized events from Projecteur device +// Virtual Device to emit customized events from Projecteur device // The spotlight.cc grabs mouse inputs from Logitech Spotlight device. // This module is used when the input events are supposed to be forwarded to the system. From 868598e15f0078c65c55607e9797232b7bd0bb5b Mon Sep 17 00:00:00 2001 From: Jahn Date: Thu, 4 Feb 2021 08:41:00 +0100 Subject: [PATCH 49/65] Fix for fedora packages. #117 --- CMakeLists.txt | 2 ++ cmake/modules/LinuxPkgCPackConfig.cmake.in | 12 +++++++++++- cmake/modules/PkgDependenciesProjecteur.cmake | 4 +++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d1642298..2a6a4d82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -151,6 +151,8 @@ if (NOT CMAKE_INSTALL_UDEVRULESDIR) endif(PKGCONFIG_UDEV_FOUND) endif(PKG_CONFIG_FOUND) endif(NOT CMAKE_INSTALL_UDEVRULESDIR) +set (CMAKE_INSTALL_UDEVDIR ${UDEVDIR} CACHE PATH "Udev base dir.") +mark_as_advanced(CMAKE_INSTALL_UDEVDIR) set (CMAKE_INSTALL_UDEVRULESDIR ${UDEVDIR}/rules.d CACHE PATH "Where to install udev rules") mark_as_advanced(CMAKE_INSTALL_UDEVRULESDIR) diff --git a/cmake/modules/LinuxPkgCPackConfig.cmake.in b/cmake/modules/LinuxPkgCPackConfig.cmake.in index 42445757..4f1de07d 100644 --- a/cmake/modules/LinuxPkgCPackConfig.cmake.in +++ b/cmake/modules/LinuxPkgCPackConfig.cmake.in @@ -27,6 +27,16 @@ set(CPACK_RPM_PACKAGE_LICENSE "@PKG_LICENSE@") set(CPACK_RPM_PACKAGE_DESCRIPTION "@PKG_DESCRIPTION_FULL@") set(CPACK_RPM_PACKAGE_AUTOPROV 1) set(CPACK_RPM_PACKAGE_AUTOREQ 1) +set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION + "@CMAKE_INSTALL_PREFIX@" + "@CMAKE_INSTALL_PREFIX@/bin" + "@CMAKE_INSTALL_PREFIX@/share" + "@CMAKE_INSTALL_PREFIX@/share/applications" + "@CMAKE_INSTALL_PREFIX@/share/man" + "@CMAKE_INSTALL_PREFIX@/share/man/man1" + "@CMAKE_INSTALL_UDEVDIR@" + "@CMAKE_INSTALL_UDEVRULESDIR@" +) # Other settings set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "@PKG_HOMEPAGE@") @@ -34,7 +44,7 @@ set(CPACK_DEBIAN_PACKAGE_SECTION "@PKG_DEBIAN_SECTION@") # Set requires/depends set(CPACK_RPM_PACKAGE_REQUIRES "@PKG_DEPENDENCIES@") -set(CPACK_DEBIAN_PACKAGE_DEPENDS "@PKG_DEPENDENCIES@") +set(CPACK_DEBIAN_PACKAGE_DEPENDS "@PKG_DEPENDENCIES@") # Post and Pre-install actions if necessary set(CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "@PKG_PREINST_SCRIPT@") diff --git a/cmake/modules/PkgDependenciesProjecteur.cmake b/cmake/modules/PkgDependenciesProjecteur.cmake index b9e15214..9b9fb42d 100644 --- a/cmake/modules/PkgDependenciesProjecteur.cmake +++ b/cmake/modules/PkgDependenciesProjecteur.cmake @@ -8,7 +8,9 @@ list(APPEND _PkgDeps_Projecteur_opensuse ) list(APPEND _PkgDeps_Projecteur_fedora - "qt5 >= 5.7" + "qt5-qtbase >= 5.7" + "qt5-qtdeclarative >= 5.7" + "qt5-qtgraphicaleffects >= 5.7" "qt5-qtx11extras >= 5.7" "passwd" "udev" From 2b731b6f0a6be791f348ffa1927a4ab2dceb958c Mon Sep 17 00:00:00 2001 From: Jahn F Date: Fri, 5 Feb 2021 14:22:47 +0100 Subject: [PATCH 50/65] Minor documentation updates. (#119) * Update LinuxRepositories.md * Update README.md * Update copyright year. --- LICENSE.md | 2 +- README.md | 14 +++++++------- doc/LinuxRepositories.md | 13 ++++++++++++- src/aboutdlg.cc | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 9fccd92f..8e4e515f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2018-2020, Jahn Fuchs +Copyright 2018-2021, Jahn Fuchs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, diff --git a/README.md b/README.md index 629f334b..5754cef1 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ So here it is: a Linux application for the Logitech Spotlight. * [Pre-requisites](#pre-requisites) * [Application Menu](#application-menu) * [Command Line Interface](#command-line-interface) - * [Scriptability](#scriptability) + * [Scriptability / Keyboard shortcuts](#scriptability) * [Device Support](#device-support) * [Troubleshooting](#troubleshooting) * [License](#license) @@ -57,10 +57,10 @@ So here it is: a Linux application for the Logitech Spotlight. ## Supported Environments -The application was mostly tested on Ubuntu 18.04 (GNOME) and OpenSuse 15 (GNOME) -but should work on almost any Linux/X11 Desktop. In case you are building the -application yourself, make sure you have the correct udev rules installed -(see [pre-requisites section](#pre-requisites)). +The application was mostly tested on Ubuntu 18.04, Ubuntu 20.04 (GNOME) and +OpenSuse 15 (GNOME) but should work on almost any Linux/X11 Desktop. In case +you are building the application yourself, make sure you have the correct udev +rules installed (see [pre-requisites section](#pre-requisites)). ## How it works @@ -160,7 +160,7 @@ Additional to the standard `--help` and `--version` options, there is an option commands to a running instance of _Projecteur_ and the ability to set properties. ``` -Usage: projecteur [option] +Usage: projecteur [OPTION]... -h, --help Show command line usage. @@ -306,7 +306,7 @@ If the device shows as not connected, there are some things you can do: ## License -Copyright 2018-2020 Jahn Fuchs +Copyright 2018-2021 Jahn Fuchs This project is distributed under the [MIT License](https://opensource.org/licenses/MIT), see [LICENSE.md](./LICENSE.md) for more information. diff --git a/doc/LinuxRepositories.md b/doc/LinuxRepositories.md index b0f1b158..4b6cf5ca 100644 --- a/doc/LinuxRepositories.md +++ b/doc/LinuxRepositories.md @@ -13,6 +13,11 @@ with _Debian bullseye_. See [this listing](https://packages.debian.org/search?keywords=projecteur&searchon=names&suite=all§ion=all) for all available `projecteur` packages in Debian. +### Ubuntu + +Thanks to debian packages, _Projecteur_ is availabed in the official Ubuntu repositories +from Ubuntu 20.10 on. See: https://packages.ubuntu.com/search?keywords=projecteur&searchon=names + ### Gentoo Linux See: https://packages.gentoo.org/packages/x11-misc/projecteur @@ -24,6 +29,10 @@ See: https://packages.gentoo.org/packages/x11-misc/projecteur * https://aur.archlinux.org/packages/projecteur/ * https://aur.archlinux.org/packages/projecteur-git/ +### OpenSUSE + +User/community repositories: +* https://software.opensuse.org/package/projecteur?search_term=projecteur ### Projecteur's Development Repositories @@ -34,6 +43,8 @@ and are accessible as a Linux repository for different distributions. See also: * https://cloudsmith.io/~jahnf/repos/projecteur-develop/setup/#formats-deb * https://cloudsmith.io/~jahnf/repos/projecteur-develop/setup/#formats-rpm + +[![Cloudsmith OSS Hosting](https://img.shields.io/badge/OSS%20hosting%20by-cloudsmith-blue?logo=cloudsmith&style=for-the-badge)](https://cloudsmith.com) #### Debian Stretch @@ -129,4 +140,4 @@ rpm --import 'https://dl.cloudsmith.io/public/jahnf/projecteur-develop/cfg/gpg/g curl -1sLf 'https://dl.cloudsmith.io/public/jahnf/projecteur-develop/cfg/setup/config.rpm.txt?distro=el&codename=8' > /tmp/jahnf-projecteur-develop.repo yum-config-manager --add-repo '/tmp/jahnf-projecteur-develop.repo' yum -q makecache -y --disablerepo='*' --enablerepo='jahnf-projecteur-develop' -``` \ No newline at end of file +``` diff --git a/src/aboutdlg.cc b/src/aboutdlg.cc index b7cf63cf..701661d2 100644 --- a/src/aboutdlg.cc +++ b/src/aboutdlg.cc @@ -147,7 +147,7 @@ QWidget* AboutDialog::createVersionInfoWidget() } vbox->addWidget(new QLabel(qtVerText, this)); vbox->addSpacing(15); - vbox->addWidget(new QLabel("Copyright 2018-2020 Jahn Fuchs", this)); + vbox->addWidget(new QLabel("Copyright 2018-2021 Jahn Fuchs", this)); auto licenseText = new QLabel(tr("This project is distributed under the
" "" "MIT License"), this); From d1f1873fb203fb2b983d44681977b8af1f0c0688 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sat, 6 Feb 2021 18:27:16 +0100 Subject: [PATCH 51/65] Minor updates/refactoring. --- CMakeLists.txt | 1 + src/device-vibration.cc | 9 +++++++++ src/device-vibration.h | 2 ++ src/device.cc | 28 ++++++++-------------------- src/devicescan.cc | 3 +++ src/deviceswidget.cc | 6 ++++-- src/logging.cc | 3 +++ src/logging.h | 2 ++ src/main.cc | 4 ++-- src/settings.cc | 6 ++---- src/settings.h | 2 +- src/spotlight.cc | 15 ++++++--------- 12 files changed, 43 insertions(+), 38 deletions(-) create mode 100644 src/device-vibration.cc create mode 100644 src/device-vibration.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a6a4d82..f5f24b34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ add_executable(projecteur src/actiondelegate.cc src/actiondelegate.h src/colorselector.cc src/colorselector.h src/device.cc src/device.h + src/device-vibration.cc src/device-vibration.h src/deviceinput.cc src/deviceinput.h src/devicescan.cc src/devicescan.h src/deviceswidget.cc src/deviceswidget.h diff --git a/src/device-vibration.cc b/src/device-vibration.cc new file mode 100644 index 00000000..c667fc46 --- /dev/null +++ b/src/device-vibration.cc @@ -0,0 +1,9 @@ +// This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md +#include "device-vibration.h" + +// TODO add vibration support for Logitech Spotlight and +// TODO generalize features and protocol for proprietary device features like vibration +// for not only the Spotlight device. +// len intensity +// unsigned char vibrate[] = {0x10, 0x01, 0x09, 0x1a, 0x00, 0xe8, 0x80}; +// ::write(notifier->socket(), vibrate, 7); diff --git a/src/device-vibration.h b/src/device-vibration.h new file mode 100644 index 00000000..ab83db44 --- /dev/null +++ b/src/device-vibration.h @@ -0,0 +1,2 @@ +// This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md +#pragma once diff --git a/src/device.cc b/src/device.cc index 3d0123a6..45ce2039 100644 --- a/src/device.cc +++ b/src/device.cc @@ -12,12 +12,13 @@ #include #include - LOGGING_CATEGORY(device, "device") namespace { // ----------------------------------------------------------------------------------------------- - static auto registeredComparator_ = QMetaType::registerComparators(); + static const auto registeredComparator_ = QMetaType::registerComparators(); + + const auto hexId = logging::hexId; } // ------------------------------------------------------------------------------------------------- @@ -53,8 +54,8 @@ bool DeviceConnection::removeSubDevice(const QString& path) { if (find_it->second) { find_it->second->disconnect(); } // Important logDebug(device) << tr("Disconnected sub-device: %1 (%2:%3) %4") - .arg(m_deviceName).arg(m_deviceId.vendorId, 4, 16, QChar('0')) - .arg(m_deviceId.productId, 4, 16, QChar('0')).arg(path); + .arg(m_deviceName, hexId(m_deviceId.vendorId), + hexId(m_deviceId.productId), path); emit subDeviceDisconnected(m_deviceId, path); m_subDeviceConnections.erase(find_it); return true; @@ -112,9 +113,7 @@ std::shared_ptr SubEventConnection::create(const DeviceScan: { ::close(evfd); logDebug(device) << tr("Device id mismatch: %1 (%2:%3)") - .arg(sd.deviceFile) - .arg(id.vendor, 4, 16, QChar('0')) - .arg(id.product, 4, 16, QChar('0')); + .arg(sd.deviceFile, hexId(id.vendor), hexId(id.product)); return std::shared_ptr(); } @@ -123,9 +122,7 @@ std::shared_ptr SubEventConnection::create(const DeviceScan: { ::close(evfd); logWarn(device) << tr("Cannot get device properties: %1 (%2:%3)") - .arg(sd.deviceFile) - .arg(id.vendor, 4, 16, QChar('0')) - .arg(id.product, 4, 16, QChar('0')); + .arg(sd.deviceFile, hexId(id.vendor), hexId(id.product)); return std::shared_ptr(); } @@ -224,9 +221,7 @@ std::shared_ptr SubHidrawConnection::create(const DeviceSca { ::close(devfd); logDebug(device) << tr("Device id mismatch: %1 (%2:%3)") - .arg(sd.deviceFile) - .arg(static_cast(devinfo.vendor), 4, 16, QChar('0')) - .arg(static_cast(devinfo.product), 4, 16, QChar('0')); + .arg(sd.deviceFile, hexId(devinfo.vendor), hexId(devinfo.product)); return std::shared_ptr(); } @@ -246,12 +241,5 @@ std::shared_ptr SubHidrawConnection::create(const DeviceSca }); connection->m_details.phys = sd.phys; - - // TODO add vibration support for Logitech Spotlight and - // TODO generalize features and protocol for proprietary device features like vibration - // for not only the Spotlight device. - // len intensity - // unsigned char vibrate[] = {0x10, 0x01, 0x09, 0x1a, 0x00, 0xe8, 0x80}; - return connection; } diff --git a/src/devicescan.cc b/src/devicescan.cc index b57f06ee..84374bee 100644 --- a/src/devicescan.cc +++ b/src/devicescan.cc @@ -257,6 +257,9 @@ namespace DeviceScan { } } + // For now: only check for hidraw sub-devices that have support for custom "proprietary" + // functionality/protocol with Projecteur built in. + // TODO check if _Projecteur_ supports additional "proprietary" device protocol features.. if (eventSubDeviceCount > 0) continue; // Iterate over 'hidraw' sub-dircectory, check for hidraw device node diff --git a/src/deviceswidget.cc b/src/deviceswidget.cc index dd3ca1c7..9a4d55ca 100644 --- a/src/deviceswidget.cc +++ b/src/deviceswidget.cc @@ -21,9 +21,10 @@ DECLARE_LOGGING_CATEGORY(preferences) // ------------------------------------------------------------------------------------------------- namespace { + const auto hexId = logging::hexId; + QString descriptionString(const QString& name, const DeviceId& id) { - return QString("%1 (%2:%3) [%4]").arg(name).arg(id.vendorId, 4, 16, QChar('0')) - .arg(id.productId, 4, 16, QChar('0')).arg(id.phys); + return QString("%1 (%2:%3) [%4]").arg(name, hexId(id.vendorId), hexId(id.productId), id.phys); } const auto invalidDeviceId = DeviceId(); // vendorId = 0, productId = 0 @@ -228,6 +229,7 @@ void DevicesWidget::createDeviceComboBox(Spotlight* spotlight) const auto devId = qvariant_cast(m_devicesCombo->itemData(index)); const auto currentConn = spotlight->deviceConnection(devId); m_inputMapper = currentConn ? currentConn->inputMapper().get() : nullptr; + emit currentDeviceChanged(devId); }); diff --git a/src/logging.cc b/src/logging.cc index 01bf313a..46e4ee93 100644 --- a/src/logging.cc +++ b/src/logging.cc @@ -198,4 +198,7 @@ namespace logging { } } + QString hexId(unsigned short id) { + return QString("%1").arg(id, 4, 16, QChar('0')); + } } diff --git a/src/logging.h b/src/logging.h index 9bbbce16..7feed3f9 100644 --- a/src/logging.h +++ b/src/logging.h @@ -68,6 +68,8 @@ namespace logging { void setCurrentLevel(level lvl); void registerTextEdit(QPlainTextEdit* textEdit); + + QString hexId(unsigned short id); } diff --git a/src/main.cc b/src/main.cc index b0f6e4d1..850b93e2 100644 --- a/src/main.cc +++ b/src/main.cc @@ -263,8 +263,8 @@ int main(int argc, char *argv[]) return subDevice.deviceWritable; }); - print() << " " << "vendorId: " << QString("%1").arg(device.id.vendorId, 4, 16, QChar('0')); - print() << " " << "productId: " << QString("%1").arg(device.id.productId, 4, 16, QChar('0')); + print() << " " << "vendorId: " << logging::hexId(device.id.vendorId); + print() << " " << "productId: " << logging::hexId(device.id.productId); print() << " " << "phys: " << device.id.phys; print() << " " << "busType: " << busTypeToString(device.busType); print() << " " << "devices: " << subDeviceList.join(", "); diff --git a/src/settings.cc b/src/settings.cc index bf6a45d6..ab5f255a 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -94,9 +94,7 @@ namespace { // ----------------------------------------------------------------------------------------------- QString settingsKey(const DeviceId& dId, const QString& key) { return QString("Device_%1_%2/%3") - .arg(dId.vendorId, 4, 16, QChar('0')) - .arg(dId.productId, 4, 16, QChar('0')) - .arg(key); + .arg(logging::hexId(dId.vendorId), logging::hexId(dId.productId), key); } // ------------------------------------------------------------------------------------------------- @@ -262,7 +260,7 @@ const Settings::SettingRange& Settings::zoomFactorRange() { return setti const Settings::SettingRange& Settings::inputSequenceIntervalRange() { return settings::ranges::inputSequenceInterval; } // ------------------------------------------------------------------------------------------------- -const QList& Settings::spotShapes() const +const QList& Settings::spotShapes() { static const QList shapes{ SpotShape(::settings::defaultValue::spotShape, "Circle", tr("Circle"), false), diff --git a/src/settings.h b/src/settings.h index a394faec..7267f1c7 100644 --- a/src/settings.h +++ b/src/settings.h @@ -145,7 +145,7 @@ class Settings : public QObject friend class Settings; }; - const QList& spotShapes() const; + static const QList& spotShapes(); QQmlPropertyMap* shapeSettings(const QString& shapeName); struct StringProperty diff --git a/src/spotlight.cc b/src/spotlight.cc index 430caac3..6f68fe22 100644 --- a/src/spotlight.cc +++ b/src/spotlight.cc @@ -19,6 +19,7 @@ DECLARE_LOGGING_CATEGORY(device) namespace { + const auto hexId = logging::hexId; } // --- end anonymous namespace // ------------------------------------------------------------------------------------------------- @@ -183,19 +184,15 @@ int Spotlight::connectDevices() QTimer::singleShot(0, this, [this, id = dev.id, devName = dc->deviceName(), anyConnectedBefore](){ logInfo(device) << tr("Connected device: %1 (%2:%3)") - .arg(devName) - .arg(id.vendorId, 4, 16, QChar('0')) - .arg(id.productId, 4, 16, QChar('0')); + .arg(devName, hexId(id.vendorId), hexId(id.productId)); emit deviceConnected(id, devName); if (!anyConnectedBefore) emit anySpotlightDeviceConnectedChanged(true); }); } logDebug(device) << tr("Connected sub-device: %1 (%2:%3) %4") - .arg(dc->deviceName()) - .arg(dev.id.vendorId, 4, 16, QChar('0')) - .arg(dev.id.productId, 4, 16, QChar('0')) - .arg(scanSubDevice.deviceFile); + .arg(dc->deviceName(), hexId(dev.id.vendorId), + hexId(dev.id.productId), scanSubDevice.deviceFile); emit subDeviceConnected(dev.id, dc->deviceName(), scanSubDevice.deviceFile); } @@ -224,8 +221,8 @@ void Spotlight::removeDeviceConnection(const QString &devicePath) if (dc->subDeviceCount() == 0) { logInfo(device) << tr("Disconnected device: %1 (%2:%3)") - .arg(dc->deviceName()).arg(dc_it->first.vendorId, 4, 16, QChar('0')) - .arg(dc_it->first.productId, 4, 16, QChar('0')); + .arg(dc->deviceName(), hexId(dc_it->first.vendorId), + hexId(dc_it->first.productId)); emit deviceDisconnected(dc_it->first, dc->deviceName()); dc_it = m_deviceConnections.erase(dc_it); } From 1c0d62902e4ca8d888a518b6f20c34a8e821499d Mon Sep 17 00:00:00 2001 From: Jahn Date: Thu, 11 Feb 2021 22:03:15 +0100 Subject: [PATCH 52/65] Added IconLabel widget. --- src/device-vibration.cc | 14 ++++++++++++++ src/device-vibration.h | 12 ++++++++++++ src/iconwidgets.cc | 17 +++++++++++++++++ src/iconwidgets.h | 11 +++++++++++ 4 files changed, 54 insertions(+) diff --git a/src/device-vibration.cc b/src/device-vibration.cc index c667fc46..91f13fc9 100644 --- a/src/device-vibration.cc +++ b/src/device-vibration.cc @@ -1,9 +1,23 @@ // This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md #include "device-vibration.h" +#include "iconwidgets.h" + +#include + // TODO add vibration support for Logitech Spotlight and // TODO generalize features and protocol for proprietary device features like vibration // for not only the Spotlight device. // len intensity // unsigned char vibrate[] = {0x10, 0x01, 0x09, 0x1a, 0x00, 0xe8, 0x80}; // ::write(notifier->socket(), vibrate, 7); + +// ------------------------------------------------------------------------------------------------- +TimerWidget::TimerWidget(QWidget* parent) + : QWidget(parent) +{ + // TODO Widget with up to 3 (or more) configurable timers.. + const auto layout = new QVBoxLayout(this); + const auto iconLabel = new IconLabel(Font::time_19, this); + layout->addWidget(iconLabel); +} diff --git a/src/device-vibration.h b/src/device-vibration.h index ab83db44..cc30bebc 100644 --- a/src/device-vibration.h +++ b/src/device-vibration.h @@ -1,2 +1,14 @@ // This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md #pragma once + +#include + +// ------------------------------------------------------------------------------------------------- +class TimerWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TimerWidget(QWidget* parent = nullptr); + +}; diff --git a/src/iconwidgets.cc b/src/iconwidgets.cc index 3e44a103..5b9aa1c9 100644 --- a/src/iconwidgets.cc +++ b/src/iconwidgets.cc @@ -26,3 +26,20 @@ IconButton::IconButton(Font::Icon symbol, QWidget* parent) : QColor(Qt::lightGray).lighter()); setPalette(p); } + +// ------------------------------------------------------------------------------------------------- +IconLabel::IconLabel(Font::Icon symbol, QWidget* parent) + : QLabel(QChar(symbol), parent) +{ + QFont iconFont("projecteur-icons"); + iconFont.setPixelSize(32); + setFont(iconFont); +} + +// ------------------------------------------------------------------------------------------------- +void IconLabel::setPixelSize(int pixelSize) +{ + auto font = this->font(); + font.setPixelSize(pixelSize); + setFont(font); +} diff --git a/src/iconwidgets.h b/src/iconwidgets.h index d1e48656..e179627a 100644 --- a/src/iconwidgets.h +++ b/src/iconwidgets.h @@ -4,6 +4,7 @@ #include "projecteur-icons-def.h" #include +#include // ------------------------------------------------------------------------------------------------- class IconButton : public QToolButton @@ -14,3 +15,13 @@ class IconButton : public QToolButton IconButton(Font::Icon symbol, QWidget* parent = nullptr); }; +// ------------------------------------------------------------------------------------------------- +class IconLabel : public QLabel +{ + Q_OBJECT + +public: + IconLabel(Font::Icon symbol, QWidget* parent = nullptr); + + void setPixelSize(int pixelSize); +}; From f2479fd85c54b6b6fe2c56afd921d74664cf5fa5 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sat, 13 Feb 2021 10:56:16 +0100 Subject: [PATCH 53/65] Added timer widget #6 --- icons/icon-font/.fontcustom-manifest.json | 22 +- .../svg/iconmonstr-media-control-48.svg | 1 + .../svg/iconmonstr-media-control-50.svg | 1 + icons/projecteur-icons.ttf | Bin 4240 -> 4368 bytes src/device-vibration.cc | 233 +++++++++++++++++- src/device-vibration.h | 54 +++- src/iconwidgets.cc | 4 +- src/projecteur-icons-def.h | 2 + 8 files changed, 306 insertions(+), 11 deletions(-) create mode 100644 icons/icon-font/svg/iconmonstr-media-control-48.svg create mode 100644 icons/icon-font/svg/iconmonstr-media-control-50.svg diff --git a/icons/icon-font/.fontcustom-manifest.json b/icons/icon-font/.fontcustom-manifest.json index 92f6e4ac..8a1ff0eb 100644 --- a/icons/icon-font/.fontcustom-manifest.json +++ b/icons/icon-font/.fontcustom-manifest.json @@ -1,14 +1,14 @@ { "checksum": { - "previous": "d4f6bb1053795e614d87b855da33da2b7ee5eefaaf01d5bc4d3976114651c9d5", - "current": "d4f6bb1053795e614d87b855da33da2b7ee5eefaaf01d5bc4d3976114651c9d5" + "previous": "a9504014a04ad54718a75911f452551882ad71440ff3fac45f0dad3a5042a9ef", + "current": "a9504014a04ad54718a75911f452551882ad71440ff3fac45f0dad3a5042a9ef" }, "fonts": [ - "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.ttf", - "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.svg", - "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.woff", - "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.eot", - "output/fonts/projecteur-icons_d4f6bb1053795e614d87b855da33da2b.woff2" + "output/fonts/projecteur-icons_a9504014a04ad54718a75911f4525518.ttf", + "output/fonts/projecteur-icons_a9504014a04ad54718a75911f4525518.svg", + "output/fonts/projecteur-icons_a9504014a04ad54718a75911f4525518.woff", + "output/fonts/projecteur-icons_a9504014a04ad54718a75911f4525518.eot", + "output/fonts/projecteur-icons_a9504014a04ad54718a75911f4525518.woff2" ], "glyphs": { "iconmonstr-arrow-73": { @@ -59,6 +59,14 @@ "codepoint": 61711, "source": "svg/iconmonstr-keyboard-4.svg" }, + "iconmonstr-media-control-48": { + "codepoint": 61719, + "source": "svg/iconmonstr-media-control-48.svg" + }, + "iconmonstr-media-control-50": { + "codepoint": 61720, + "source": "svg/iconmonstr-media-control-50.svg" + }, "iconmonstr-plus-5": { "codepoint": 61703, "source": "svg/iconmonstr-plus-5.svg" diff --git a/icons/icon-font/svg/iconmonstr-media-control-48.svg b/icons/icon-font/svg/iconmonstr-media-control-48.svg new file mode 100644 index 00000000..31a69427 --- /dev/null +++ b/icons/icon-font/svg/iconmonstr-media-control-48.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/icon-font/svg/iconmonstr-media-control-50.svg b/icons/icon-font/svg/iconmonstr-media-control-50.svg new file mode 100644 index 00000000..9ca071a4 --- /dev/null +++ b/icons/icon-font/svg/iconmonstr-media-control-50.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/projecteur-icons.ttf b/icons/projecteur-icons.ttf index dedbfe8a58dabc7d10a01511de2b375af55e4b2c..e37116c9de3fd3eee85f217c01616b0a76c524d3 100644 GIT binary patch delta 565 zcmaKoPiWI%7{;GBiP&M5wAi|iRY*kzw*|XQr40Y%Fm@YLHs^%OmUY=WNSd++nU`%S z6ud}T(2KnbyY`|7#a(208iJlYh&wqw7|>k?i*Mt>n}?70`@PTiJ>U1d&3yCBItV~F zHX!2W*x1x~kbmC-lE1i{o0z<4OdQ<10MHEcT-mj1U+2F~F|$td<@>&d6paF6N0*kY zdM%3B3CSPKi6!U3;=rx%1=gEDSFvJS3%#$NKW6PSuPc>`y>5v==^dM;xpB7Q`m545 zc9~x?k2qf0>Ran|*r3F0xYlY7cB~hfwW{UX&Fx(ckaqy#RL!gV=MHc00m>I-TJk%i(f<=iYPAx#hlcZxIYYJ4z6-5DYHOm*0E# z0DG3E?$F|h8v0oq0N66|jYv|<+^Q`z#Os8WNG|I^2MYk=9O+U_+szmTIl-PFZiyud zTj%wOQ_@R-Rfkh*(UKL3Z6^c;}3ISF<6kDv-Ynz`bD!dpYsPLKKE_u+ZddIw~PxXO6 hxA*cJxsw;<1BxXmLws9tRD(*2@eg~q?LT_B@e5ylYP$dc diff --git a/src/device-vibration.cc b/src/device-vibration.cc index 91f13fc9..2de900e7 100644 --- a/src/device-vibration.cc +++ b/src/device-vibration.cc @@ -3,8 +3,18 @@ #include "iconwidgets.h" +#include +#include +#include +#include +#include +#include +#include #include +#include +#include + // TODO add vibration support for Logitech Spotlight and // TODO generalize features and protocol for proprietary device features like vibration // for not only the Spotlight device. @@ -12,12 +22,231 @@ // unsigned char vibrate[] = {0x10, 0x01, 0x09, 0x1a, 0x00, 0xe8, 0x80}; // ::write(notifier->socket(), vibrate, 7); +// ------------------------------------------------------------------------------------------------- +namespace { + constexpr int numTimers = 3; +} + +// ------------------------------------------------------------------------------------------------- +struct TimerWidget::Impl +{ + // ----------------------------------------------------------------------------------------------- + Impl(QWidget* parent) + : stack(new QStackedWidget(parent)) + , editor(new QWidget(parent)) + , overlay(new QWidget(parent)) + , checkbox(new QCheckBox(parent)) + , sbHours(new QSpinBox(parent)) + , sbMinutes(new QSpinBox(parent)) + , sbSeconds(new QSpinBox(parent)) + , btnStartStop(new IconButton(Font::Icon::media_control_48, parent)) + , timer(new QTimer(parent)) + , countdownTimer(new QTimer(parent)) + , overlayLabel(new QLabel(parent)) + { + const auto layout = new QHBoxLayout(parent); + layout->addWidget(checkbox); + layout->addWidget(stack); + layout->setMargin(0); + + stack->addWidget(editor); + stack->addWidget(overlay); + const auto editLayout = new QHBoxLayout(editor); + const auto m = editLayout->contentsMargins(); + editLayout->setContentsMargins(m.left(), 0, m.right(), 0); + editLayout->addWidget(sbHours); + editLayout->addWidget(new QLabel(TimerWidget::tr("h"), editor)); + editLayout->addWidget(sbMinutes); + editLayout->addWidget(new QLabel(TimerWidget::tr("m"), editor)); + editLayout->addWidget(sbSeconds); + editLayout->addWidget(new QLabel(TimerWidget::tr("s"), editor)); + editLayout->addStretch(1); + + sbHours->setRange(0, 24); + sbMinutes->setRange(0, 59); + sbSeconds->setRange(0, 59); + + layout->addWidget(btnStartStop); + btnStartStop->setCheckable(true); + QObject::connect(btnStartStop, &IconButton::toggled, parent, [this](bool checked) { + stack->setCurrentWidget(checked ? overlay : editor); + btnStartStop->setText(checked ? QChar(Font::Icon::media_control_50) + : QChar(Font::Icon::media_control_48)); + if (checked) { + secondsLeft = valueSeconds(); + updateOverlayLabel(secondsLeft); + countdownTimer->start(); + timer->start(); + } else { + timer->stop(); + countdownTimer->stop(); + } + }); + + const auto overlayLayout = new QHBoxLayout(overlay); + overlayLayout->addWidget(overlayLabel); + overlayLayout->setContentsMargins(m.left(), 0, m.right(), 0); + overlayLabel->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + + editor->setEnabled(checkbox->isChecked()); + btnStartStop->setEnabled(checkbox->isChecked()); + QObject::connect(checkbox, &QCheckBox::toggled, parent, [this](bool checked) { + editor->setEnabled(checked); + if (!checked) btnStartStop->setChecked(false); + btnStartStop->setEnabled(checked); + }); + + QObject::connect(timer, &QTimer::timeout, parent, [this](){ btnStartStop->setChecked(false); }); + QObject::connect(sbHours, static_cast(&QSpinBox::valueChanged), + parent, [this](){ updateTimerInterval(); }); + QObject::connect(sbMinutes, static_cast(&QSpinBox::valueChanged), + parent, [this](){ updateTimerInterval(); }); + QObject::connect(sbSeconds, static_cast(&QSpinBox::valueChanged), + parent, [this](){ updateTimerInterval(); }); + + timer->setSingleShot(true); + countdownTimer->setInterval(1000); + + QObject::connect(countdownTimer, &QTimer::timeout, parent, [this](){ + updateOverlayLabel(--secondsLeft); + }); + } + + int valueSeconds() const { + return sbSeconds->value() + sbMinutes->value() * 60 + sbHours->value() * 60 * 60; + } + + // ----------------------------------------------------------------------------------------------- + void updateTimerInterval() { + timer->setInterval(valueSeconds() * 1000); + } + + // ----------------------------------------------------------------------------------------------- + void updateOverlayLabel(int remainingSeconds) + { + const std::chrono::seconds remainingTime(remainingSeconds); + const auto hours = std::chrono::duration_cast(remainingTime); + const auto mins = std::chrono::duration_cast(remainingTime-hours); + const auto secs = std::chrono::duration_cast(remainingTime-hours-mins); + + overlayLabel->setText(QString("%1:%2:%3") + .arg(hours.count(), 2, 10, QChar('0')) + .arg(mins.count(), 2, 10, QChar('0')) + .arg(secs.count(), 2, 10, QChar('0'))); + } + + // ----------------------------------------------------------------------------------------------- + QStackedWidget* stack = nullptr; + QWidget* editor = nullptr; + QWidget* overlay = nullptr; + QCheckBox* checkbox = nullptr; + QSpinBox* sbHours = nullptr; + QSpinBox* sbMinutes = nullptr; + QSpinBox* sbSeconds = nullptr; + IconButton* btnStartStop = nullptr; + QTimer* timer = nullptr; + QTimer* countdownTimer = nullptr; + QLabel* overlayLabel = nullptr; + int secondsLeft = 0; +}; + // ------------------------------------------------------------------------------------------------- TimerWidget::TimerWidget(QWidget* parent) : QWidget(parent) + , m_impl(new Impl(this)) +{ + connect(m_impl->timer, &QTimer::timeout, this, &TimerWidget::timeout); +} + +// ------------------------------------------------------------------------------------------------- +TimerWidget::~TimerWidget() = default; + +// ------------------------------------------------------------------------------------------------- +bool TimerWidget::timerEnabled() const { + return m_impl->checkbox->isChecked(); +} + +// ------------------------------------------------------------------------------------------------- +void TimerWidget::setTimerEnabled(bool enabled) { + m_impl->checkbox->setChecked(enabled); +} + +// ------------------------------------------------------------------------------------------------- +bool TimerWidget::timerRunning() const { + return m_impl->timer->isActive(); +} + +// ------------------------------------------------------------------------------------------------- +void TimerWidget::start() { + m_impl->btnStartStop->setChecked(true); +} + +// ------------------------------------------------------------------------------------------------- +void TimerWidget::stop() { + m_impl->btnStartStop->setChecked(false); +} + +// ------------------------------------------------------------------------------------------------- +void TimerWidget::setValueSeconds(int seconds) +{ + const std::chrono::seconds totalSecs(seconds); + const auto hours = std::chrono::duration_cast(totalSecs); + const auto mins = std::chrono::duration_cast(totalSecs-hours); + const auto secs = std::chrono::duration_cast(totalSecs-hours-mins); + m_impl->sbHours->setValue(hours.count()); + m_impl->sbMinutes->setValue(mins.count()); + m_impl->sbSeconds->setValue(secs.count()); +} + +// ------------------------------------------------------------------------------------------------- +void TimerWidget::setValueMinutes(int minutes) { + setValueSeconds(minutes * 60); +} + +// ------------------------------------------------------------------------------------------------- +struct MultiTimerWidget::Impl +{ + Impl(QWidget* parent) + { + for (size_t i = 0; i < numTimers; ++i) { + timers.at(i) = new TimerWidget(parent); + } + } + + std::array timers = {}; +}; + +// ------------------------------------------------------------------------------------------------- +MultiTimerWidget::MultiTimerWidget(QWidget* parent) + : QWidget(parent) + , m_impl(new Impl(this)) { - // TODO Widget with up to 3 (or more) configurable timers.. - const auto layout = new QVBoxLayout(this); + const auto layout = new QHBoxLayout(this); const auto iconLabel = new IconLabel(Font::time_19, this); layout->addWidget(iconLabel); + layout->setAlignment(iconLabel, Qt::AlignTop); + + const auto groupBox = new QGroupBox(tr("Timers"), this); + groupBox->setSizePolicy(groupBox->sizePolicy().horizontalPolicy(), + QSizePolicy::Maximum); + layout->addWidget(groupBox); + layout->setAlignment(groupBox, Qt::AlignTop); + const auto timerLayout = new QVBoxLayout(groupBox); + + for (size_t i = 0; i < numTimers; ++i) { + timerLayout->addWidget(m_impl->timers.at(i), i, 0); + m_impl->timers.at(i)->setValueMinutes(15 + i * 15); + } + + layout->setStretch(0, 0); + layout->setStretch(1, 1); +} + +// ------------------------------------------------------------------------------------------------- +MultiTimerWidget::~MultiTimerWidget() = default; + +// ------------------------------------------------------------------------------------------------- +int MultiTimerWidget::timerCount() const +{ + return numTimers; } diff --git a/src/device-vibration.h b/src/device-vibration.h index cc30bebc..6d08ae5c 100644 --- a/src/device-vibration.h +++ b/src/device-vibration.h @@ -2,6 +2,7 @@ #pragma once #include +#include // ------------------------------------------------------------------------------------------------- class TimerWidget : public QWidget @@ -9,6 +10,57 @@ class TimerWidget : public QWidget Q_OBJECT public: - explicit TimerWidget(QWidget* parent = nullptr); + TimerWidget(QWidget* parent); + ~TimerWidget() override; + bool timerEnabled() const; + void setTimerEnabled(bool enabled); + + void start(); + void stop(); + bool timerRunning() const; + void setValueSeconds(int seconds); + void setValueMinutes(int minutes); + int valueSeconds() const; + +signals: + void timeout(); + +private: + struct Impl; + std::unique_ptr m_impl; +}; + +// ------------------------------------------------------------------------------------------------- +class MultiTimerWidget : public QWidget +{ + Q_OBJECT + +public: + explicit MultiTimerWidget(QWidget* parent = nullptr); + virtual ~MultiTimerWidget() override; + + /// Returns the number of timers + int timerCount() const; + + void setTimerEnabled(int timerId, bool enabled); + bool timerEnabled(int timerId) const; + + void restartTimer(int timerId); + void stopTimer(int timerId); + bool timerRunning(int timerId) const; + /// Returns remaining time of a timer or -1 if timer is not running. + int remainingTime(int timerId) const; + + void setTimerValue(int timerId, int seconds); + int timerValue(int timerId) const; + +signals: + /// Emitted when a timer times out. + void timeout(int timerId); + void timerEnabledChanged(int timerId, bool enabled); + +private: + struct Impl; + std::unique_ptr m_impl; }; diff --git a/src/iconwidgets.cc b/src/iconwidgets.cc index 5b9aa1c9..1c034b23 100644 --- a/src/iconwidgets.cc +++ b/src/iconwidgets.cc @@ -8,6 +8,8 @@ namespace { } bool isDark(const QColor& c) { return !isLight(c); } + + constexpr int defaultIconLabelSize = 32; } // ------------------------------------------------------------------------------------------------- @@ -32,7 +34,7 @@ IconLabel::IconLabel(Font::Icon symbol, QWidget* parent) : QLabel(QChar(symbol), parent) { QFont iconFont("projecteur-icons"); - iconFont.setPixelSize(32); + iconFont.setPixelSize(defaultIconLabelSize); setFont(iconFont); } diff --git a/src/projecteur-icons-def.h b/src/projecteur-icons-def.h index 33cd8991..d0337e14 100644 --- a/src/projecteur-icons-def.h +++ b/src/projecteur-icons-def.h @@ -18,6 +18,8 @@ namespace Font gear_12 = 0xf106, // svg/iconmonstr-gear-12.svg keyboard_14 = 0xf10e, // svg/iconmonstr-keyboard-14.svg keyboard_4 = 0xf10f, // svg/iconmonstr-keyboard-4.svg + media_control_48 = 0xf117, // svg/iconmonstr-media-control-48.svg + media_control_50 = 0xf118, // svg/iconmonstr-media-control-50.svg plus_5 = 0xf107, // svg/iconmonstr-plus-5.svg power_on_off_11 = 0xf115, // svg/iconmonstr-power-on-off-11.svg share_8 = 0xf108, // svg/iconmonstr-share-8.svg From ed9118633c5ff93cd9f856c9d8633f9ec2beebad Mon Sep 17 00:00:00 2001 From: Jahn Date: Sat, 13 Feb 2021 13:37:01 +0100 Subject: [PATCH 54/65] Fix compiler warning, show timer tab when Logitech Spotlight (USB) is connected #6 --- src/device-vibration.cc | 67 +++++++++++++++++++++++++++++++++++++++-- src/device-vibration.h | 5 ++- src/device.cc | 7 +++++ src/device.h | 3 ++ src/deviceswidget.cc | 34 ++++++++++++++++++++- src/deviceswidget.h | 1 + 6 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/device-vibration.cc b/src/device-vibration.cc index 2de900e7..e6734a38 100644 --- a/src/device-vibration.cc +++ b/src/device-vibration.cc @@ -203,6 +203,11 @@ void TimerWidget::setValueMinutes(int minutes) { setValueSeconds(minutes * 60); } +// ------------------------------------------------------------------------------------------------- +int TimerWidget::valueSeconds() const { + return m_impl->valueSeconds(); +} + // ------------------------------------------------------------------------------------------------- struct MultiTimerWidget::Impl { @@ -234,7 +239,7 @@ MultiTimerWidget::MultiTimerWidget(QWidget* parent) const auto timerLayout = new QVBoxLayout(groupBox); for (size_t i = 0; i < numTimers; ++i) { - timerLayout->addWidget(m_impl->timers.at(i), i, 0); + timerLayout->addWidget(m_impl->timers.at(i)); m_impl->timers.at(i)->setValueMinutes(15 + i * 15); } @@ -246,7 +251,63 @@ MultiTimerWidget::MultiTimerWidget(QWidget* parent) MultiTimerWidget::~MultiTimerWidget() = default; // ------------------------------------------------------------------------------------------------- -int MultiTimerWidget::timerCount() const -{ +int MultiTimerWidget::timerCount() const { return numTimers; } + +// ------------------------------------------------------------------------------------------------- +void MultiTimerWidget::setTimerEnabled(int timerId, bool enabled) +{ + if (timerId < 0 || timerId >= numTimers) return; + m_impl->timers.at(timerId)->setTimerEnabled(enabled); +} + +// ------------------------------------------------------------------------------------------------- +bool MultiTimerWidget::timerEnabled(int timerId) const +{ + if (timerId < 0 || timerId >= numTimers) return false; + return m_impl->timers.at(timerId)->timerEnabled(); +} + +// ------------------------------------------------------------------------------------------------- +void MultiTimerWidget::startTimer(int timerId) +{ + if (timerId < 0 || timerId >= numTimers) return; + m_impl->timers.at(timerId)->start(); +} + +// ------------------------------------------------------------------------------------------------- +void MultiTimerWidget::stopTimer(int timerId) +{ + if (timerId < 0 || timerId >= numTimers) return; + m_impl->timers.at(timerId)->stop(); +} + +// ------------------------------------------------------------------------------------------------- +void MultiTimerWidget::stopAllTimers() +{ + for (size_t i = 0; i < numTimers; ++i) { + m_impl->timers.at(i)->stop(); + } +} + +// ------------------------------------------------------------------------------------------------- +bool MultiTimerWidget::timerRunning(int timerId) const +{ + if (timerId < 0 || timerId >= numTimers) return false; + return m_impl->timers.at(timerId)->timerRunning(); +} + +// ------------------------------------------------------------------------------------------------- +void MultiTimerWidget::setTimerValue(int timerId, int seconds) +{ + if (timerId < 0 || timerId >= numTimers) return; + m_impl->timers.at(timerId)->setValueSeconds(seconds); +} + +// ------------------------------------------------------------------------------------------------- +int MultiTimerWidget::timerValue(int timerId) const +{ + if (timerId < 0 || timerId >= numTimers) return -1; + return m_impl->timers.at(timerId)->valueSeconds(); +} diff --git a/src/device-vibration.h b/src/device-vibration.h index 6d08ae5c..cf6e3ace 100644 --- a/src/device-vibration.h +++ b/src/device-vibration.h @@ -46,11 +46,10 @@ class MultiTimerWidget : public QWidget void setTimerEnabled(int timerId, bool enabled); bool timerEnabled(int timerId) const; - void restartTimer(int timerId); + void startTimer(int timerId); void stopTimer(int timerId); + void stopAllTimers(); bool timerRunning(int timerId) const; - /// Returns remaining time of a timer or -1 if timer is not running. - int remainingTime(int timerId) const; void setTimerValue(int timerId, int seconds); int timerValue(int timerId) const; diff --git a/src/device.cc b/src/device.cc index 45ce2039..d3907e90 100644 --- a/src/device.cc +++ b/src/device.cc @@ -232,6 +232,13 @@ std::shared_ptr SubHidrawConnection::create(const DeviceSca connection->m_details.deviceFlags |= DeviceFlag::NonBlocking; } + // For now vibration is only supported for the Logitech Spotlight (USB) + // TODO A more generic approach + if (dc.deviceId().vendorId == 0x46d && dc.deviceId().productId == 0xc53e) { + qDebug() << "hello"; + connection->m_details.deviceFlags |= DeviceFlag::Vibrate; + } + // Create socket notifier connection->m_notifier = std::make_unique(devfd, QSocketNotifier::Read); QSocketNotifier* const notifier = connection->m_notifier.get(); diff --git a/src/device.h b/src/device.h index 23e974fc..36f29504 100644 --- a/src/device.h +++ b/src/device.h @@ -59,6 +59,7 @@ class DeviceConnection : public QObject bool hasSubDevice(const QString& path) const; void addSubDevice(std::shared_ptr); bool removeSubDevice(const QString& path); + const auto& subDevices() { return m_subDeviceConnections; } signals: void subDeviceConnected(const DeviceId& id, const QString& path); @@ -82,6 +83,8 @@ enum class DeviceFlag : uint32_t { RepEvents = 1 << 2, RelativeEvents = 1 << 3, KeyEvents = 1 << 4, + + Vibrate = 1 << 16, }; ENUM(DeviceFlag, DeviceFlags) diff --git a/src/deviceswidget.cc b/src/deviceswidget.cc index 9a4d55ca..e31c28f0 100644 --- a/src/deviceswidget.cc +++ b/src/deviceswidget.cc @@ -1,6 +1,7 @@ // This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md #include "deviceswidget.h" +#include "device-vibration.h" #include "deviceinput.h" #include "iconwidgets.h" #include "inputmapconfig.h" @@ -79,6 +80,38 @@ QWidget* DevicesWidget::createDevicesWidget(Settings* settings, Spotlight* spotl tabWidget->addTab(createInputMapperWidget(settings, spotlight), tr("Input Mapping")); + auto hasVibrateSupport = [spotlight](const DeviceId& devId) { + const auto currentConn = spotlight->deviceConnection(devId); + if (currentConn) { + for (const auto& item : currentConn->subDevices()) { + if ((item.second->flags() & DeviceFlag::Vibrate) == DeviceFlag::Vibrate) return true; + } + } + return false; + }; + + if (hasVibrateSupport(currentDeviceId())) { + m_vibrationTimerWidget = new MultiTimerWidget(this); + tabWidget->addTab(m_vibrationTimerWidget, tr("Timer")); + } + + connect(this, &DevicesWidget::currentDeviceChanged, this, + [hasVibrateSupport=std::move(hasVibrateSupport), tabWidget, this](const DeviceId& devId) { + const bool vibrateSupport = hasVibrateSupport(devId); + if (vibrateSupport) { + if (m_vibrationTimerWidget == nullptr) { + m_vibrationTimerWidget = new MultiTimerWidget(this); + } + if (tabWidget->indexOf(m_vibrationTimerWidget) < 0) { + tabWidget->addTab(m_vibrationTimerWidget, tr("Timer")); + } + } + else if (m_vibrationTimerWidget) { + const auto idx = tabWidget->indexOf(m_vibrationTimerWidget); + if (idx >= 0) tabWidget->removeTab(idx); + } + }); + return dw; } @@ -130,7 +163,6 @@ QWidget* DevicesWidget::createInputMapperWidget(Settings* settings, Spotlight* / const auto imModel = new InputMapConfigModel(m_inputMapper, imWidget); if (m_inputMapper) imModel->setConfiguration(m_inputMapper->configuration()); - tblView->setModel(imModel); const auto selectionModel = tblView->selectionModel(); diff --git a/src/deviceswidget.h b/src/deviceswidget.h index 69e11d3f..b50832dc 100644 --- a/src/deviceswidget.h +++ b/src/deviceswidget.h @@ -30,5 +30,6 @@ class DevicesWidget : public QWidget QWidget* createDeviceInfoWidget(Spotlight* spotlight); QComboBox* m_devicesCombo = nullptr; + QWidget* m_vibrationTimerWidget = nullptr; QPointer m_inputMapper; }; From 448805820aafe1739870115e18b484dc749403a1 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sat, 13 Feb 2021 16:26:26 +0100 Subject: [PATCH 55/65] Add vibration settings widget #6 --- src/device-vibration.cc | 118 +++++++++++++++++++++++++++++++++++++--- src/device-vibration.h | 32 +++++++++++ src/device.cc | 1 - src/deviceswidget.cc | 49 ++++++++++++----- src/deviceswidget.h | 5 +- 5 files changed, 179 insertions(+), 26 deletions(-) diff --git a/src/device-vibration.cc b/src/device-vibration.cc index e6734a38..2f0b4e10 100644 --- a/src/device-vibration.cc +++ b/src/device-vibration.cc @@ -1,12 +1,15 @@ // This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md #include "device-vibration.h" +#include "device.h" #include "iconwidgets.h" #include #include #include #include +#include +#include #include #include #include @@ -14,13 +17,7 @@ #include #include - -// TODO add vibration support for Logitech Spotlight and -// TODO generalize features and protocol for proprietary device features like vibration -// for not only the Spotlight device. -// len intensity -// unsigned char vibrate[] = {0x10, 0x01, 0x09, 0x1a, 0x00, 0xe8, 0x80}; -// ::write(notifier->socket(), vibrate, 7); +#include // ------------------------------------------------------------------------------------------------- namespace { @@ -178,7 +175,8 @@ bool TimerWidget::timerRunning() const { // ------------------------------------------------------------------------------------------------- void TimerWidget::start() { - m_impl->btnStartStop->setChecked(true); + if (timerEnabled()) + m_impl->btnStartStop->setChecked(true); } // ------------------------------------------------------------------------------------------------- @@ -243,7 +241,6 @@ MultiTimerWidget::MultiTimerWidget(QWidget* parent) m_impl->timers.at(i)->setValueMinutes(15 + i * 15); } - layout->setStretch(0, 0); layout->setStretch(1, 1); } @@ -311,3 +308,106 @@ int MultiTimerWidget::timerValue(int timerId) const if (timerId < 0 || timerId >= numTimers) return -1; return m_impl->timers.at(timerId)->valueSeconds(); } + +// ------------------------------------------------------------------------------------------------- +VibrationSettingsWidget::VibrationSettingsWidget(QWidget* parent) + : QWidget(parent) + , m_sbLength(new QSpinBox(this)) + , m_sbIntensity(new QSpinBox(this)) +{ + m_sbLength->setRange(0, 10); + m_sbIntensity->setRange(25, 255); + + const auto layout = new QHBoxLayout(this); + const auto iconLabel = new IconLabel(Font::control_panel_9, this); + layout->addWidget(iconLabel); + layout->setAlignment(iconLabel, Qt::AlignTop); + + const auto groupBox = new QGroupBox(tr("Vibration Settings"), this); + groupBox->setSizePolicy(groupBox->sizePolicy().horizontalPolicy(), + QSizePolicy::Maximum); + layout->addWidget(groupBox); + layout->setAlignment(groupBox, Qt::AlignTop); + + const auto grid = new QGridLayout(groupBox); + grid->addWidget(new QLabel(tr("Length"), this), 0, 0); + grid->addWidget(new QLabel(tr("Intensity"), this), 1, 0); + grid->addWidget(m_sbLength, 0, 1); + grid->addWidget(m_sbIntensity, 1, 1); + grid->setColumnStretch(0, 1); + grid->setColumnStretch(1, 2); + + const auto testBtn = new QPushButton(tr("Test"), this); + grid->addWidget(testBtn, 2, 0, 1, 2); + + m_sbLength->setValue(0x00); + m_sbIntensity->setValue(0x80); + + connect(m_sbLength, static_cast(&QSpinBox::valueChanged), this, + [this](int value){ + emit lengthChanged(value); + }); + + connect(m_sbIntensity, static_cast(&QSpinBox::valueChanged), this, + [this](int value){ + emit intensityChanged(value); + }); + + connect(testBtn, &QPushButton::clicked, this, &VibrationSettingsWidget::sendVibrateCommand); + + layout->setStretch(1, 1); +} + +// ------------------------------------------------------------------------------------------------- +uint8_t VibrationSettingsWidget::length() const { + return m_sbLength->value(); +} + +// ------------------------------------------------------------------------------------------------- +uint8_t VibrationSettingsWidget::intensity() const { + return m_sbIntensity->value(); +} + +// ------------------------------------------------------------------------------------------------- +void VibrationSettingsWidget::setLength(uint8_t len) +{ + if (m_sbLength->value() == len) return; + m_sbLength->setValue(len); +} + +// ------------------------------------------------------------------------------------------------- +void VibrationSettingsWidget::setIntensity(uint8_t intensity) +{ + if (m_sbIntensity->value() == intensity) return; + m_sbIntensity->setValue(intensity); +} + +// ------------------------------------------------------------------------------------------------- +void VibrationSettingsWidget::setSubDeviceConnection(SubDeviceConnection *sdc) +{ + m_subDeviceConnection = sdc; +} + +// ------------------------------------------------------------------------------------------------- +void VibrationSettingsWidget::sendVibrateCommand() +{ + if (!m_subDeviceConnection) return; + if ((m_subDeviceConnection->flags() & DeviceFlag::Vibrate) != DeviceFlag::Vibrate) return; + if (!m_subDeviceConnection->isConnected()) return; + + // TODO generalize features and protocol for proprietary device features like vibration + // for not only the Spotlight device. + // + // Spotlight: + // len intensity + // unsigned char vibrate[] = {0x10, 0x01, 0x09, 0x1a, 0x00, 0xe8, 0x80}; + const uint8_t vlen = m_sbLength->value(); + const uint8_t vint = m_sbIntensity->value(); + const uint8_t vibrateCmd[] = {0x10, 0x01, 0x09, 0x1a, vlen, 0xe8, vint}; + // ::write(notifier->socket(), vibrate, 7); + const auto notifier = m_subDeviceConnection->socketNotifier(); + const auto res = ::write(notifier->socket(), &vibrateCmd[0], sizeof(vibrateCmd)); + if (res != sizeof(vibrateCmd)) { + // TODO logging... + } +} diff --git a/src/device-vibration.h b/src/device-vibration.h index cf6e3ace..5dc645e1 100644 --- a/src/device-vibration.h +++ b/src/device-vibration.h @@ -1,9 +1,13 @@ // This file is part of Projecteur - https://github.com/jahnf/projecteur - See LICENSE.md and README.md #pragma once +#include #include #include +class QSpinBox; +class SubDeviceConnection; + // ------------------------------------------------------------------------------------------------- class TimerWidget : public QWidget { @@ -63,3 +67,31 @@ class MultiTimerWidget : public QWidget struct Impl; std::unique_ptr m_impl; }; + +// ------------------------------------------------------------------------------------------------- +class VibrationSettingsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit VibrationSettingsWidget(QWidget* parent = nullptr); + + uint8_t length() const; + void setLength(uint8_t len); + + uint8_t intensity() const; + void setIntensity(uint8_t intensity); + + void setSubDeviceConnection(SubDeviceConnection* sdc); + +signals: + void intensityChanged(uint8_t intensity); + void lengthChanged(uint8_t length); + +private: + void sendVibrateCommand(); + + QPointer m_subDeviceConnection; + QSpinBox* m_sbLength = nullptr; + QSpinBox* m_sbIntensity = nullptr; +}; diff --git a/src/device.cc b/src/device.cc index d3907e90..92a71028 100644 --- a/src/device.cc +++ b/src/device.cc @@ -235,7 +235,6 @@ std::shared_ptr SubHidrawConnection::create(const DeviceSca // For now vibration is only supported for the Logitech Spotlight (USB) // TODO A more generic approach if (dc.deviceId().vendorId == 0x46d && dc.deviceId().productId == 0xc53e) { - qDebug() << "hello"; connection->m_details.deviceFlags |= DeviceFlag::Vibrate; } diff --git a/src/deviceswidget.cc b/src/deviceswidget.cc index e31c28f0..acce7f47 100644 --- a/src/deviceswidget.cc +++ b/src/deviceswidget.cc @@ -61,6 +61,22 @@ const DeviceId DevicesWidget::currentDeviceId() const return qvariant_cast(m_devicesCombo->currentData()); } +// ------------------------------------------------------------------------------------------------- +QWidget* DevicesWidget::createTimerTabWidget(Settings* settings, Spotlight* spotlight) +{ + Q_UNUSED(settings); + Q_UNUSED(spotlight); + + const auto w = new QWidget(this); + const auto layout = new QVBoxLayout(w); + const auto timerWidget = new MultiTimerWidget(this); + m_vibrationSettingsWidget = new VibrationSettingsWidget(this); + + layout->addWidget(timerWidget); + layout->addWidget(m_vibrationSettingsWidget); + return w; +} + // ------------------------------------------------------------------------------------------------- QWidget* DevicesWidget::createDevicesWidget(Settings* settings, Spotlight* spotlight) { @@ -80,35 +96,38 @@ QWidget* DevicesWidget::createDevicesWidget(Settings* settings, Spotlight* spotl tabWidget->addTab(createInputMapperWidget(settings, spotlight), tr("Input Mapping")); - auto hasVibrateSupport = [spotlight](const DeviceId& devId) { + auto vibrateConn = [spotlight](const DeviceId& devId) { const auto currentConn = spotlight->deviceConnection(devId); if (currentConn) { for (const auto& item : currentConn->subDevices()) { - if ((item.second->flags() & DeviceFlag::Vibrate) == DeviceFlag::Vibrate) return true; + if ((item.second->flags() & DeviceFlag::Vibrate) == DeviceFlag::Vibrate) return item.second; } } - return false; + return std::shared_ptr{}; }; - if (hasVibrateSupport(currentDeviceId())) { - m_vibrationTimerWidget = new MultiTimerWidget(this); - tabWidget->addTab(m_vibrationTimerWidget, tr("Timer")); + if (const auto conn = vibrateConn(currentDeviceId())) { + m_timerTabWidget = createTimerTabWidget(settings, spotlight); + tabWidget->addTab(m_timerTabWidget, tr("Timer")); + m_vibrationSettingsWidget->setSubDeviceConnection(conn.get()); } connect(this, &DevicesWidget::currentDeviceChanged, this, - [hasVibrateSupport=std::move(hasVibrateSupport), tabWidget, this](const DeviceId& devId) { - const bool vibrateSupport = hasVibrateSupport(devId); - if (vibrateSupport) { - if (m_vibrationTimerWidget == nullptr) { - m_vibrationTimerWidget = new MultiTimerWidget(this); + [vibrateConn=std::move(vibrateConn), tabWidget, settings, spotlight, this] + (const DeviceId& devId) { + if (const auto conn = vibrateConn(devId)) { + if (m_timerTabWidget == nullptr) { + m_timerTabWidget = createTimerTabWidget(settings, spotlight); } - if (tabWidget->indexOf(m_vibrationTimerWidget) < 0) { - tabWidget->addTab(m_vibrationTimerWidget, tr("Timer")); + if (tabWidget->indexOf(m_timerTabWidget) < 0) { + tabWidget->addTab(m_timerTabWidget, tr("Timer")); } + m_vibrationSettingsWidget->setSubDeviceConnection(conn.get()); } - else if (m_vibrationTimerWidget) { - const auto idx = tabWidget->indexOf(m_vibrationTimerWidget); + else if (m_timerTabWidget) { + const auto idx = tabWidget->indexOf(m_timerTabWidget); if (idx >= 0) tabWidget->removeTab(idx); + m_vibrationSettingsWidget->setSubDeviceConnection(nullptr); } }); diff --git a/src/deviceswidget.h b/src/deviceswidget.h index b50832dc..6e3c865c 100644 --- a/src/deviceswidget.h +++ b/src/deviceswidget.h @@ -9,6 +9,7 @@ class InputMapper; class QComboBox; class Settings; class Spotlight; +class VibrationSettingsWidget; // ------------------------------------------------------------------------------------------------- class DevicesWidget : public QWidget @@ -28,8 +29,10 @@ class DevicesWidget : public QWidget QWidget* createDevicesWidget(Settings* settings, Spotlight* spotlight); QWidget* createInputMapperWidget(Settings* settings, Spotlight* spotlight); QWidget* createDeviceInfoWidget(Spotlight* spotlight); + QWidget* createTimerTabWidget(Settings* settings, Spotlight* spotlight); QComboBox* m_devicesCombo = nullptr; - QWidget* m_vibrationTimerWidget = nullptr; + QWidget* m_timerTabWidget = nullptr; + VibrationSettingsWidget* m_vibrationSettingsWidget = nullptr; QPointer m_inputMapper; }; From 75142251db81f89a0345fe1c9694fac53b46d0b4 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sat, 13 Feb 2021 18:23:55 +0100 Subject: [PATCH 56/65] Add vibration support for Logitech Spotlight (USB) #6 --- src/device-vibration.cc | 35 +++++++++++++++++++++++------- src/device-vibration.h | 6 ++++-- src/deviceswidget.cc | 47 +++++++++++++++++++++++++++++++++++++++++ src/settings.cc | 43 +++++++++++++++++++++++++++++++++++++ src/settings.h | 8 ++++++- 5 files changed, 128 insertions(+), 11 deletions(-) diff --git a/src/device-vibration.cc b/src/device-vibration.cc index 2f0b4e10..07e48452 100644 --- a/src/device-vibration.cc +++ b/src/device-vibration.cc @@ -28,7 +28,7 @@ namespace { struct TimerWidget::Impl { // ----------------------------------------------------------------------------------------------- - Impl(QWidget* parent) + Impl(TimerWidget* parent) : stack(new QStackedWidget(parent)) , editor(new QWidget(parent)) , overlay(new QWidget(parent)) @@ -87,19 +87,29 @@ struct TimerWidget::Impl editor->setEnabled(checkbox->isChecked()); btnStartStop->setEnabled(checkbox->isChecked()); - QObject::connect(checkbox, &QCheckBox::toggled, parent, [this](bool checked) { + QObject::connect(checkbox, &QCheckBox::toggled, parent, [this, parent](bool checked) { editor->setEnabled(checked); if (!checked) btnStartStop->setChecked(false); btnStartStop->setEnabled(checked); + emit parent->enabledChanged(checked); }); QObject::connect(timer, &QTimer::timeout, parent, [this](){ btnStartStop->setChecked(false); }); - QObject::connect(sbHours, static_cast(&QSpinBox::valueChanged), - parent, [this](){ updateTimerInterval(); }); - QObject::connect(sbMinutes, static_cast(&QSpinBox::valueChanged), - parent, [this](){ updateTimerInterval(); }); - QObject::connect(sbSeconds, static_cast(&QSpinBox::valueChanged), - parent, [this](){ updateTimerInterval(); }); + QObject::connect(sbHours, static_cast(&QSpinBox::valueChanged), parent, + [this, parent]() { + updateTimerInterval(); + emit parent->valueSecondsChanged(valueSeconds()); + }); + QObject::connect(sbMinutes, static_cast(&QSpinBox::valueChanged), parent, + [this, parent]() { + updateTimerInterval(); + emit parent->valueSecondsChanged(valueSeconds()); + }); + QObject::connect(sbSeconds, static_cast(&QSpinBox::valueChanged), parent, + [this, parent]() { + updateTimerInterval(); + emit parent->valueSecondsChanged(valueSeconds()); + }); timer->setSingleShot(true); countdownTimer->setInterval(1000); @@ -239,6 +249,15 @@ MultiTimerWidget::MultiTimerWidget(QWidget* parent) for (size_t i = 0; i < numTimers; ++i) { timerLayout->addWidget(m_impl->timers.at(i)); m_impl->timers.at(i)->setValueMinutes(15 + i * 15); + connect(m_impl->timers.at(i), &TimerWidget::valueSecondsChanged, this, [this, i](int secs) { + emit timerValueChanged(i, secs); + }); + connect(m_impl->timers.at(i), &TimerWidget::enabledChanged, this, [this, i](bool enabled) { + emit timerEnabledChanged(i, enabled); + }); + connect(m_impl->timers.at(i), &TimerWidget::timeout, this, [this, i](){ + emit timeout(i); + }); } layout->setStretch(1, 1); diff --git a/src/device-vibration.h b/src/device-vibration.h index 5dc645e1..99d9bb6f 100644 --- a/src/device-vibration.h +++ b/src/device-vibration.h @@ -29,6 +29,8 @@ class TimerWidget : public QWidget signals: void timeout(); + void valueSecondsChanged(int); + void enabledChanged(bool); private: struct Impl; @@ -62,6 +64,7 @@ class MultiTimerWidget : public QWidget /// Emitted when a timer times out. void timeout(int timerId); void timerEnabledChanged(int timerId, bool enabled); + void timerValueChanged(int timerId, int seconds); private: struct Impl; @@ -83,14 +86,13 @@ class VibrationSettingsWidget : public QWidget void setIntensity(uint8_t intensity); void setSubDeviceConnection(SubDeviceConnection* sdc); + void sendVibrateCommand(); signals: void intensityChanged(uint8_t intensity); void lengthChanged(uint8_t length); private: - void sendVibrateCommand(); - QPointer m_subDeviceConnection; QSpinBox* m_sbLength = nullptr; QSpinBox* m_sbIntensity = nullptr; diff --git a/src/deviceswidget.cc b/src/deviceswidget.cc index acce7f47..f5b8c329 100644 --- a/src/deviceswidget.cc +++ b/src/deviceswidget.cc @@ -74,6 +74,53 @@ QWidget* DevicesWidget::createTimerTabWidget(Settings* settings, Spotlight* spot layout->addWidget(timerWidget); layout->addWidget(m_vibrationSettingsWidget); + + auto loadSettings = [this, settings, timerWidget](const DeviceId& dId) { + for (int i = 0; i < timerWidget->timerCount(); ++i) { + const auto ts = settings->timerSettings(dId, i); + timerWidget->setTimerEnabled(i, ts.first); + timerWidget->setTimerValue(i, ts.second); + } + const auto vs = settings->vibrationSettings(dId); + m_vibrationSettingsWidget->setLength(vs.first); + m_vibrationSettingsWidget->setIntensity(vs.second); + }; + + loadSettings(currentDeviceId()); + + connect(this, &DevicesWidget::currentDeviceChanged, this, + [loadSettings=std::move(loadSettings), timerWidget, this](const DeviceId& dId) { + timerWidget->stopAllTimers(); + timerWidget->blockSignals(true); + m_vibrationSettingsWidget->blockSignals(true); + loadSettings(dId); + m_vibrationSettingsWidget->blockSignals(false); + timerWidget->blockSignals(false); + }); + + connect(timerWidget, &MultiTimerWidget::timerValueChanged, this, + [timerWidget, settings, this](int id, int secs) { + settings->setTimerSettings(currentDeviceId(), id, timerWidget->timerEnabled(id), secs); + }); + + connect(timerWidget, &MultiTimerWidget::timerEnabledChanged, this, + [timerWidget, settings, this](int id, bool enabled) { + settings->setTimerSettings(currentDeviceId(), id, enabled, timerWidget->timerValue(id)); + }); + + connect(m_vibrationSettingsWidget, &VibrationSettingsWidget::intensityChanged, this, + [settings, this](uint8_t intensity) { + settings->setVibrationSettings(currentDeviceId(), m_vibrationSettingsWidget->length(), intensity); + }); + + connect(m_vibrationSettingsWidget, &VibrationSettingsWidget::lengthChanged, this, + [settings, this](uint8_t len) { + settings->setVibrationSettings(currentDeviceId(), len, m_vibrationSettingsWidget->intensity()); + }); + + connect(timerWidget, &MultiTimerWidget::timeout, + m_vibrationSettingsWidget, &VibrationSettingsWidget::sendVibrateCommand); + return w; } diff --git a/src/settings.cc b/src/settings.cc index ab5f255a..5e8e3607 100644 --- a/src/settings.cc +++ b/src/settings.cc @@ -6,6 +6,7 @@ #include "logging.h" #include +#include #include #include @@ -41,6 +42,10 @@ namespace { // -- device specific constexpr char inputSequenceInterval[] = "inputSequenceInterval"; constexpr char inputMapConfig[] = "inputMapConfig"; + constexpr char timerEnabled[] = "timer%1enabled"; + constexpr char timerSeconds[] = "timer%1seconds"; + constexpr char vibrationLength[] = "vibrationLength"; + constexpr char vibrationIntensity[] = "vibrationIntensity"; namespace defaultValue { constexpr bool showSpotShade = true; @@ -64,6 +69,8 @@ namespace { // -- device specific defaults constexpr int inputSequenceInterval = 250; + constexpr uint8_t vibrationLength = 0; + constexpr uint8_t vibrationIntensity = 128; } namespace ranges { @@ -842,6 +849,42 @@ InputMapConfig Settings::getDeviceInputMapConfig(const DeviceId& dId) return cfg; } +// ------------------------------------------------------------------------------------------------- +void Settings::setTimerSettings(const DeviceId& dId, int timerId, bool enabled, int seconds) +{ + m_settings->setValue(settingsKey(dId, QString(::settings::timerEnabled).arg(timerId)), enabled); + m_settings->setValue(settingsKey(dId, QString(::settings::timerSeconds).arg(timerId)), seconds); +} + +// ------------------------------------------------------------------------------------------------- +std::pair Settings::timerSettings(const DeviceId& dId, int timerId) const +{ + const auto enabled = m_settings->value( + settingsKey(dId, QString(::settings::timerEnabled).arg(timerId)), false).toBool(); + const auto seconds = m_settings->value( + settingsKey(dId, QString(::settings::timerSeconds).arg(timerId)), 900 + 900 * timerId).toInt(); + return std::make_pair(enabled, seconds); +} + +// ------------------------------------------------------------------------------------------------- +void Settings::setVibrationSettings(const DeviceId& dId, uint8_t len, uint8_t intensity) +{ + m_settings->setValue(settingsKey(dId, ::settings::vibrationLength), len); + m_settings->setValue(settingsKey(dId, ::settings::vibrationIntensity), intensity); +} + +// ------------------------------------------------------------------------------------------------- +std::pair Settings::vibrationSettings(const DeviceId& dId) const +{ + const auto len = m_settings->value( + settingsKey(dId, ::settings::vibrationLength), + ::settings::defaultValue::vibrationLength).toUInt(); + const auto intensity = m_settings->value( + settingsKey(dId, ::settings::vibrationIntensity), + ::settings::defaultValue::vibrationIntensity).toUInt(); + return std::make_pair(len, intensity); +} + // ------------------------------------------------------------------------------------------------- // ------------------------------------------------------------------------------------------------- PresetModel::PresetModel(QObject* parent) diff --git a/src/settings.h b/src/settings.h index 7267f1c7..c9c4b83e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -171,6 +171,12 @@ class Settings : public QObject void setDeviceInputMapConfig(const DeviceId& dId, const InputMapConfig& imc); InputMapConfig getDeviceInputMapConfig(const DeviceId& dId); + void setTimerSettings(const DeviceId& dId, int timerId, bool enabled, int seconds); + std::pair timerSettings(const DeviceId& dId, int timerId) const; + + void setVibrationSettings(const DeviceId& dId, uint8_t len, uint8_t intensity); + std::pair vibrationSettings(const DeviceId& dId) const; + signals: void showSpotShadeChanged(bool show); void spotSizeChanged(int size); @@ -219,7 +225,7 @@ class Settings : public QObject bool m_showSpotShade = true; bool m_showCenterDot = false; bool m_spotRotationAllowed = false; - bool m_showBorder=false; + bool m_showBorder = false; bool m_multiScreenOverlayEnabled = false; bool m_overlayDisabled = false; From 223caba8b54e5868f57de374e2bdec9389fe0061 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sat, 13 Feb 2021 19:06:40 +0100 Subject: [PATCH 57/65] Update change log and README for v0.9 --- README.md | 3 ++- doc/CHANGELOG.md | 23 +++++++++++++++++++++++ src/deviceswidget.cc | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5754cef1..af8528af 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ So here it is: a Linux application for the Logitech Spotlight. * Button mapping: * Map any button on the device to (almost) any keyboard combination. * Switch between (cycle through) custom spotlight presets. +* Vibration (Timer) Support for the Logitech Spotlight (USB) ### Screenshots @@ -53,7 +54,7 @@ So here it is: a Linux application for the Logitech Spotlight. ### Planned features * Support for more customizable button mapping actions. -* Vibration (Timer) Support (Logitech Spotlight) +* Support of more proprietary features of the Logitech Spotlight and other devices. ## Supported Environments diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index defcdeed..48d1cc0c 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## v0.9 + +### Changes/Updates: + +- Added man pages and Appstream files - thanks to @llimeht ([#97][p97]); +- Command line option to toggle the spotlight ([#104][i104]); +- Bugfix when moving the cursor from one screen to a different screen with higher resolution; +- Multi-screen overlay option ([#80][i80]); +- Added bash-completion ([#110][p110]); +- Added automated Fedora-33 build ([#111)][p111]; +- Added automated OpenSUSE 15.2 build ([#115][p115]); +- Automated build: Added automated CodeQL security analysis ([#113][p113]); +- Added vibration support for the Logitech Spotlight (USB) ([#6][i6]); + +[p97]: https://github.com/jahnf/Projecteur/pull/97 +[i104]: https://github.com/jahnf/Projecteur/issues/104 +[i80]: https://github.com/jahnf/Projecteur/issues/80 +[p110]: https://github.com/jahnf/Projecteur/pull/110 +[p111]: https://github.com/jahnf/Projecteur/pull/111 +[p115]: https://github.com/jahnf/Projecteur/pull/115 +[p113]: https://github.com/jahnf/Projecteur/pull/113 +[i6]: https://github.com/jahnf/Projecteur/issues/6 + ## v0.8 ### Changes/Updates: diff --git a/src/deviceswidget.cc b/src/deviceswidget.cc index f5b8c329..080a5747 100644 --- a/src/deviceswidget.cc +++ b/src/deviceswidget.cc @@ -155,7 +155,7 @@ QWidget* DevicesWidget::createDevicesWidget(Settings* settings, Spotlight* spotl if (const auto conn = vibrateConn(currentDeviceId())) { m_timerTabWidget = createTimerTabWidget(settings, spotlight); - tabWidget->addTab(m_timerTabWidget, tr("Timer")); + tabWidget->addTab(m_timerTabWidget, tr("Vibration Timer")); m_vibrationSettingsWidget->setSubDeviceConnection(conn.get()); } @@ -167,7 +167,7 @@ QWidget* DevicesWidget::createDevicesWidget(Settings* settings, Spotlight* spotl m_timerTabWidget = createTimerTabWidget(settings, spotlight); } if (tabWidget->indexOf(m_timerTabWidget) < 0) { - tabWidget->addTab(m_timerTabWidget, tr("Timer")); + tabWidget->addTab(m_timerTabWidget, tr("Vibration Timer")); } m_vibrationSettingsWidget->setSubDeviceConnection(conn.get()); } From b5c5d1a02a40ba7633b2369f53c613bf10bd8a69 Mon Sep 17 00:00:00 2001 From: Jahn F Date: Sat, 13 Feb 2021 19:13:13 +0100 Subject: [PATCH 58/65] Update codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a1084f9f..541c3b57 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -10,7 +10,7 @@ on: jobs: analyse: name: Analyse - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 steps: - name: Install dependencies From df0594186b514d6fdac348979d4c9c3139ad5b8e Mon Sep 17 00:00:00 2001 From: Jahn F Date: Sat, 13 Feb 2021 19:16:10 +0100 Subject: [PATCH 59/65] Update codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 541c3b57..27414be4 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Install dependencies run: | - sudo apt-get install pkg-config qtdeclarative5-dev \ + sudo apt-get --no-install-recommends install pkg-config qtdeclarative5-dev \ qttools5-dev-tools qttools5-dev \ qt5-default libqt5x11extras5-dev From b3403cd02611d4e9809582daffdae8d6cd418771 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sat, 13 Feb 2021 19:39:17 +0100 Subject: [PATCH 60/65] Fix CodeQL findings. --- src/device-vibration.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/device-vibration.cc b/src/device-vibration.cc index 07e48452..a5e427ed 100644 --- a/src/device-vibration.cc +++ b/src/device-vibration.cc @@ -3,6 +3,7 @@ #include "device.h" #include "iconwidgets.h" +#include "logging.h" #include #include @@ -19,6 +20,8 @@ #include #include +DECLARE_LOGGING_CATEGORY(device) + // ------------------------------------------------------------------------------------------------- namespace { constexpr int numTimers = 3; @@ -423,10 +426,10 @@ void VibrationSettingsWidget::sendVibrateCommand() const uint8_t vlen = m_sbLength->value(); const uint8_t vint = m_sbIntensity->value(); const uint8_t vibrateCmd[] = {0x10, 0x01, 0x09, 0x1a, vlen, 0xe8, vint}; - // ::write(notifier->socket(), vibrate, 7); + const auto notifier = m_subDeviceConnection->socketNotifier(); const auto res = ::write(notifier->socket(), &vibrateCmd[0], sizeof(vibrateCmd)); if (res != sizeof(vibrateCmd)) { - // TODO logging... + logWarn(device) << "Could not write vibrate command to device socket."; } } From 4fac6c8100ca9fa0b2ead44324770d10e97bbf1f Mon Sep 17 00:00:00 2001 From: Jahn Date: Sun, 14 Feb 2021 10:01:09 +0100 Subject: [PATCH 61/65] Create release branch for v0.9 #121 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f5f24b34..cd972018 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ set_target_properties(projecteur PROPERTIES VERSION_MAJOR 0 VERSION_MINOR 9 VERSION_PATCH 0 - VERSION_TYPE develop + VERSION_TYPE release ) add_version_info(projecteur "${CMAKE_CURRENT_SOURCE_DIR}") From 70ccdb4bdd7968d88de22e4d53b89cabbea2fec0 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sun, 14 Feb 2021 11:25:25 +0100 Subject: [PATCH 62/65] Use reference links in README.md and update contributors list. #121 --- README.md | 36 +++++++++++++++++++++++++----------- src/aboutdlg.cc | 1 + 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index af8528af..912065ef 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,16 @@ # Projecteur -develop: [ ![Build Status develop](https://github.com/jahnf/Projecteur/workflows/ci-build/badge.svg?branch=develop) ](https://github.com/jahnf/Projecteur/actions?query=workflow%3Aci-build+branch%3Adevelop) -master: [ ![Build Status master](https://github.com/jahnf/Projecteur/workflows/ci-build/badge.svg?branch=master) ](https://github.com/jahnf/Projecteur/actions?query=workflow%3Aci-build+branch%3Amaster) +develop: [ ![Build Status develop][gh-badge-dev] ][gh-link-dev] +master: [ ![Build Status master][gh-badge-rel] ][gh-link-rel] Linux/X11 application for the Logitech Spotlight device (and similar devices). \ See **[Download](#download)** section for binary packages. +[gh-badge-dev]: https://github.com/jahnf/Projecteur/workflows/ci-build/badge.svg?branch=develop +[gh-badge-rel]: https://github.com/jahnf/Projecteur/workflows/ci-build/badge.svg?branch=master +[gh-link-dev]: https://github.com/jahnf/Projecteur/actions?query=workflow%3Aci-build+branch%3Adevelop +[gh-link-rel]: https://github.com/jahnf/Projecteur/actions?query=workflow%3Aci-build+branch%3Amaster + ## Motivation I saw the Logitech Spotlight device in action at a conference and liked it immediately. @@ -30,6 +35,7 @@ So here it is: a Linux application for the Logitech Spotlight. * [Scriptability / Keyboard shortcuts](#scriptability) * [Device Support](#device-support) * [Troubleshooting](#troubleshooting) + * [Changelog](#changelog) * [License](#license) ## Features @@ -93,15 +99,17 @@ The latest binary packages for some Linux distributions are available for downlo Currently binary packages for _Ubuntu_, _Debian_, _Fedora_, _OpenSuse_, _CentOS_ and _Arch_ Linux are automatically built. -* Latest develop: -[ ![Download](https://api.bintray.com/packages/jahnf/Projecteur/projecteur-develop/images/download.svg) ](https://bintray.com/jahnf/Projecteur/projecteur-develop/_latestVersion#files) - -* Latest release: -[ ![Download](https://api.bintray.com/packages/jahnf/Projecteur/projecteur-master/images/download.svg) ](https://bintray.com/jahnf/Projecteur/projecteur-master/_latestVersion#files) +* Latest develop: [ ![Download][bintray-dev-img] ][dl-dev-bintray] +* Latest release: [ ![Download][bintray-rel-img] ][dl-rel-bintray] See also the [list of Linux repositories](./doc/LinuxRepositories.md) where _Projecteur_ is available. +[dl-dev-bintray]: https://bintray.com/jahnf/Projecteur/projecteur-develop/_latestVersion#files +[dl-rel-bintray]: https://bintray.com/jahnf/Projecteur/projecteur-master/_latestVersion#files +[bintray-dev-img]: https://api.bintray.com/packages/jahnf/Projecteur/projecteur-develop/images/download.svg +[bintray-rel-img]: https://api.bintray.com/packages/jahnf/Projecteur/projecteur-master/images/download.svg + ## Building ### Requirements @@ -254,13 +262,15 @@ See [Command Line Interface](#command-line-interface). There is also a command line option (`-m`) to prevent the preferences dialog from hiding, allowing it only to minimize - behaving more like a regular application window. -On some distributions that have a **GNOME Desktop** by default there is **no system tray extensions** -installed (_Fedora_ for example). You can install the -[KStatusNotifierItem/AppIndicator Support](https://extensions.gnome.org/extension/615/appindicator-support/) -or the [TopIcons Plus](https://extensions.gnome.org/extension/1031/topicons/) +On some distributions that have a **GNOME Desktop** by default there is +**no system tray extensions** installed (_Fedora_ for example). You can install the +[KStatusNotifierItem/AppIndicator Support][appind-ext] or the [TopIcons Plus][topicon-ext] GNOME extension to have a system tray that can show the _Projecteur_ tray icon (and also from other applications like Dropbox or Skype). +[appind-ext]: https://extensions.gnome.org/extension/615/appindicator-support/ +[topicon-ext]: https://extensions.gnome.org/extension/1031/topicons/ + #### Zoom is not updated while spotlight is shown Zoom does not update while spotlight is shown due to how the zoom currently works. A screenshot is @@ -305,6 +315,10 @@ If the device shows as not connected, there are some things you can do: the udev rule yourself and later you used the automatically built Linux packages to install _Projecteur_. +## Changelog + +See [CHANGELOG.md](./doc/CHANGELOG.md) for a detailed changelog. + ## License Copyright 2018-2021 Jahn Fuchs diff --git a/src/aboutdlg.cc b/src/aboutdlg.cc index 701661d2..4e315834 100644 --- a/src/aboutdlg.cc +++ b/src/aboutdlg.cc @@ -64,6 +64,7 @@ namespace { Contributor("Brandon Johnson", "dbrandonjohnson"), Contributor("Stuart Prescott", "llimeht"), Contributor("Crista Renouard", "Lumnicence"), + Contributor("freddii", "freddii"), }; static std::mt19937 g(std::random_device{}()); From ca64415889daea1a3669d16ee24914d75640d87a Mon Sep 17 00:00:00 2001 From: Jahn Date: Sun, 14 Feb 2021 14:05:56 +0100 Subject: [PATCH 63/65] Fix some lintian warnings for created debian packages. #121 --- CMakeLists.txt | 22 ++++++++++++++++--- cmake/modules/PkgDependenciesProjecteur.cmake | 1 + 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cd972018..d432f97a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,6 +131,14 @@ add_custom_target(non-sources SOURCES README.md LICENSE.md doc/CHANGELOG.md devi cmake/templates/Projecteur.desktop.in) # Install +#--------------------------------------------------------------------------------------------------- +# Set default directory permissions with CMake >= 3.11, avoids lintian errors for deb packages. +set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE +) + install(TARGETS projecteur DESTINATION bin) set(PROJECTEUR_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/bin/projecteur") #used in desktop file template @@ -217,7 +225,7 @@ configure_file("${TMPLDIR}/projecteur.1" "projecteur.1" @ONLY) find_program(GZIP_EXECUTABLE gzip) add_custom_command( OUTPUT ${OUTDIR}/projecteur.1.gz - COMMAND ${GZIP_EXECUTABLE} -9f "${OUTDIR}/projecteur.1" + COMMAND ${GZIP_EXECUTABLE} -9f -n "${OUTDIR}/projecteur.1" WORKING_DIRECTORY ${OUTDIR} ) add_custom_target(gzip-manpage ALL DEPENDS "${OUTDIR}/projecteur.1.gz") @@ -248,15 +256,23 @@ if(PACKAGE_TARGETS) PROJECT "${CMAKE_PROJECT_NAME}" TARGET projecteur DESCRIPTION_BRIEF "Linux/X11 application for the Logitech Spotlight device." - DESCRIPTION_FULL "Linux/X11 application for the Logitech Spotlight device.\nHomepage: ${HOMEPAGE}" + DESCRIPTION_FULL +"Projecteur is a virtual laser pointer for use with inertial pointers such as + the Logitech Spotlight. Projecteur can show a colored dot, a highlighted + circle or a zoom effect to act as a pointer. The location of the pointer + moves in response to moving the handheld pointer device. The effect is much + like that of a traditional laser pointer, except that it is captured by + recording software and works across multiple screens." CONTACT "Jahn Fuchs " HOMEPAGE "${HOMEPAGE}" DEBIAN_SECTION "utils" - PREINST_SCRIPT "${OUTDIR}/pkg/scripts/preinst" + # PREINST_SCRIPT "${OUTDIR}/pkg/scripts/preinst" POSTINST_SCRIPT "${OUTDIR}/pkg/scripts/postinst" ) endif() +add_dependencies(dist-package gzip-manpage projecteur) + option(ENABLE_IWYU "Enable Include-What-You-Use" OFF) find_program(iwyu_path NAMES include-what-you-use iwyu) if(ENABLE_IWYU AND iwyu_path) diff --git a/cmake/modules/PkgDependenciesProjecteur.cmake b/cmake/modules/PkgDependenciesProjecteur.cmake index 9b9fb42d..eeef027f 100644 --- a/cmake/modules/PkgDependenciesProjecteur.cmake +++ b/cmake/modules/PkgDependenciesProjecteur.cmake @@ -31,6 +31,7 @@ list(APPEND _PkgDeps_Projecteur_debian "libqt5x11extras5 (>= 5.7)" "passwd" "udev" + "libc6" ) list(APPEND _PkgDeps_Projecteur_archlinux From 2032ccc8959e8f97c2573f62c956b17762131f76 Mon Sep 17 00:00:00 2001 From: Jahn Date: Sun, 14 Feb 2021 15:25:12 +0100 Subject: [PATCH 64/65] Fix more lintian warning for debian packages. #121 --- CMakeLists.txt | 23 ++++++++++++++++++++--- cmake/modules/LinuxPackaging.cmake | 18 +++++++++++------- cmake/templates/copyright.in | 30 ++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 cmake/templates/copyright.in diff --git a/CMakeLists.txt b/CMakeLists.txt index d432f97a..41c6665f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,13 +132,21 @@ add_custom_target(non-sources SOURCES README.md LICENSE.md doc/CHANGELOG.md devi # Install #--------------------------------------------------------------------------------------------------- -# Set default directory permissions with CMake >= 3.11, avoids lintian errors for deb packages. +# Set default directory permissions with CMake >= 3.11, avoids some +# lintian 'non-standard-dir-perm' errors for deb packages. set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) +## -- prevent additional lintian warnings 'non-standard-dir-perm' for the cmake install prefix +set(DIR_TO_INSTALL "${CMAKE_INSTALL_PREFIX}") +while(NOT "${DIR_TO_INSTALL}" STREQUAL "/" AND NOT "${DIR_TO_INSTALL}" STREQUAL "") + install(DIRECTORY DESTINATION "${DIR_TO_INSTALL}") + get_filename_component(DIR_TO_INSTALL "${DIR_TO_INSTALL}" PATH) +endwhile() + install(TARGETS projecteur DESTINATION bin) set(PROJECTEUR_INSTALL_PATH "${CMAKE_INSTALL_PREFIX}/bin/projecteur") #used in desktop file template @@ -269,10 +277,19 @@ if(PACKAGE_TARGETS) # PREINST_SCRIPT "${OUTDIR}/pkg/scripts/preinst" POSTINST_SCRIPT "${OUTDIR}/pkg/scripts/postinst" ) + add_dependencies(dist-package gzip-manpage projecteur) + + # Additional files for debian packages, adhering to some debian rules, + # see https://manpages.debian.org/buster/lintian/lintian.1.en.html + if ("${PKG_TYPE}" STREQUAL "DEB") + # TODO Lintian expects the the copyright file in /usr/share/doc/projecteur + # This clashes with the default CMAKE_INSTALL_PREFIX /usr/local + # Need to check if this would clash with packages from the debian/ubuntu repos. + #configure_file("${TMPLDIR}/copyright.in" "pkg/copyright" @ONLY) + #install(FILES "${OUTDIR}/pkg/copyright" DESTINATION /usr/share/doc/projecteur/) + endif() endif() -add_dependencies(dist-package gzip-manpage projecteur) - option(ENABLE_IWYU "Enable Include-What-You-Use" OFF) find_program(iwyu_path NAMES include-what-you-use iwyu) if(ENABLE_IWYU AND iwyu_path) diff --git a/cmake/modules/LinuxPackaging.cmake b/cmake/modules/LinuxPackaging.cmake index deafbae2..37db4edc 100644 --- a/cmake/modules/LinuxPackaging.cmake +++ b/cmake/modules/LinuxPackaging.cmake @@ -28,13 +28,13 @@ set(_LinuxPackaging_default_pkgtype "TGZ") # DEBIAN_SECTION.....: A valid debian package section (default=devel) function(add_dist_package_target) - set(oneValueArgs + set(oneValueArgs PROJECT # project name to package TARGET # main executable build target that has version information attached to it - DESCRIPTION_BRIEF + DESCRIPTION_BRIEF DESCRIPTION_FULL CONTACT # Maintainer / contact person - HOMEPAGE + HOMEPAGE DEBIAN_SECTION PREINST_SCRIPT POSTINST_SCRIPT @@ -130,7 +130,7 @@ function(add_dist_package_target) endif() endforeach() endif() - + if(INCLUDED_PROJECT_DEPENDENCIES AND PkgDependenciesMake_MAP_${PKG_PROJECT}) set(PKG_BUILD_DEPENDENCY_FOUND 0) # Find dependencies for Linux distribution (and version) @@ -168,10 +168,14 @@ function(add_dist_package_target) endif() configure_file( - "${_LinuxPackaging_DIRECTORY}/travis-ci-bintray-deploy.json.in" + "${_LinuxPackaging_DIRECTORY}/travis-ci-bintray-deploy.json.in" "${CMAKE_CURRENT_BINARY_DIR}/travis-ci-bintray-deploy.json" @ONLY) message(STATUS "Configured target 'dist-package' with Linux '${PKG_DIST}' and package type '${PKG_TYPE}'") + + # Make some information available to parent scope + set(PKG_DIST "${PKG_DIST}" PARENT_SCOPE) + set(PKG_TYPE "${PKG_TYPE}" PARENT_SCOPE) endfunction() # makepg packaging (arch linux/pacman) @@ -186,7 +190,7 @@ function(_makepkg_packaging) set(PKG_PKGBUILD_INSTALL_FILE_PATH "${PKG_SOURCE_ARCHIVE_DIR}/${PKG_PKGBUILD_INSTALL_FILE}") file(MAKE_DIRECTORY "${PKG_SOURCE_ARCHIVE_DIR}") file(WRITE "${PKG_PKGBUILD_INSTALL_FILE_PATH}" "# generated install file\n\n") - + if(PKG_PREINST_SCRIPT) file(READ "${PKG_PREINST_SCRIPT}" _pkg_preinst_script_content) file(APPEND "${PKG_PKGBUILD_INSTALL_FILE_PATH}" @@ -237,7 +241,7 @@ function(_makepkg_packaging) # makepkg: '-' is not allowed in version number string(REPLACE "-" "" PKG_PKGBUILD_VER "${PKG_VERSION_STRING_FULL}") - + set(PKG_CONFIG_TEMPLATE "${_LinuxPackaging_DIRECTORY}/PKGBUILD.in") set(PKG_CONFIG_FILE "${PKG_SOURCE_ARCHIVE_DIR}/PKGBUILD") configure_file("${PKG_CONFIG_TEMPLATE}" "${PKG_CONFIG_FILE}" @ONLY) diff --git a/cmake/templates/copyright.in b/cmake/templates/copyright.in new file mode 100644 index 00000000..24085c35 --- /dev/null +++ b/cmake/templates/copyright.in @@ -0,0 +1,30 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: projecteur +Source: @HOMEPAGE@ +License: Expat + +Files: * +Copyright: 2018-2021, Jahn Fuchs +License: Expat + +License: Expat + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + . + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file From da6f39768678d7f8902fd232fc03d5a3c1b6d05e Mon Sep 17 00:00:00 2001 From: Jahn F Date: Mon, 15 Feb 2021 08:30:24 +0100 Subject: [PATCH 65/65] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 912065ef..552d7eca 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ So here it is: a Linux application for the Logitech Spotlight. * [Application Menu](#application-menu) * [Command Line Interface](#command-line-interface) * [Scriptability / Keyboard shortcuts](#scriptability) + * [Using Projecteur without a device](#using-projecteur-without-a-device) * [Device Support](#device-support) * [Troubleshooting](#troubleshooting) * [Changelog](#changelog) @@ -49,6 +50,7 @@ So here it is: a Linux application for the Logitech Spotlight. * Map any button on the device to (almost) any keyboard combination. * Switch between (cycle through) custom spotlight presets. * Vibration (Timer) Support for the Logitech Spotlight (USB) +* Usable without a presenter device (e.g. for online presentations) ### Screenshots @@ -214,6 +216,14 @@ shortcuts in your window manager (e.g. GNOME) to run the commands `projecteur -c and `projecteur -c spot=off` or `projecteur -c spot=toggle`, and therefore turning the spot on and off with a keyboard shortcut. +### Using Projecteur without a device + +You can use _Projecteur_ for your online presentations and video conferences without a presenter +device. For this you can assign a global keyboard shortcut in your window manager +(e.g. KDE, GNOME...) to run the command `projecteur -c spot=toggle`. You will then be able to +turn the digital spot on and off with the assigned keyboard shortcut while sharing +your screen in an online presentation or call. + ### Device Support Besides the _Logitech Spotlight_, the following devices are currently supported out of the box: