Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions doc/USER-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ Applications → Projecteur**.

Projecteur stores its settings in `~/.config/projecteurrc`.

## Pointer modes: Spotlight & Laser Pointer

Projecteur supports two primary on-screen pointer modes:

1. **Spotlight Mode**: Dims the background display and highlights the area of interest using configurable shapes (circle, square, star, ngon), borders, center dot, and live KPipeWire magnification.
2. **Laser Pointer Mode**: Projects a crisp, vibrant digital laser dot with an optical diode hotspot core and an optional glowing halo / aura. Does not dim the screen or hide background elements.

Switch modes easily from the Plasma applet menu, **Preferences → Active Pointer Mode**, global shortcuts, presenter button actions, or CLI/D-Bus commands.

## Spotlight and presets

Under **Preferences → Spotlight**, you can configure:
Expand All @@ -32,13 +41,22 @@ Under **Preferences → Spotlight**, you can configure:
- multi-screen behavior.

Save combinations as presets when different situations need different treatment:
for example, a small dot for slides, a large text magnifier for a code demo, and
a borderless highlight for video.
for example, a small dot for slides, a large text magnifier for a code demo, a
glowing red laser pointer for annotations, and a borderless highlight for video.
Presets remember whether they are configured for Spotlight or Laser Pointer mode.

Presets are ordered alphabetically when Projecteur starts. Prefix names with
numbers if you want a fixed cycle order, such as `1 Slides`, `2 Demo`, and
`3 Questions`.

## Laser pointer customization

Under **Preferences → Laser Pointer**, you can configure:

- **Laser Dot**: Dot size (in pixels), color (any RGB/HTML color), and opacity.
- **Laser Glow / Halo**: Enable or disable the outer soft glowing aura, glow size, glow color, and glow opacity.
- **Laser Trail Mark**: Leave an optical fading persistence trace behind pointer movement for gesturing, circling, and underlining slides. Configure trail duration (100–2000 ms), stroke width (1–30 px), color, and opacity.

## Live zoom modes

Zoom uses a low-latency stream from KWin through KPipeWire. Videos, animations,
Expand All @@ -61,16 +79,18 @@ only from the build directory cannot use the normal zoom path.

Projecteur registers native KDE global actions for:

- toggling the spotlight;
- opening preferences;
- starting or resetting the presentation timer;
- selecting the next or previous preset.
- toggling the spotlight (`toggle_spotlight`);
- toggling the laser pointer (`toggle_laser`);
- toggling between pointer modes (`toggle_pointer_mode`);
- opening preferences (`show_preferences`);
- starting or resetting the presentation timer (`start_restart_timer`, `reset_timer`);
- selecting the next or previous preset (`next_preset`, `previous_preset`).

No key combinations are assigned by default. Set them under **Preferences →
Shortcuts** or **System Settings → Keyboard → Shortcuts → Projecteur**.

This also makes Projecteur useful without presenter hardware: assign **Toggle
Spotlight**, then use it while sharing your screen in a meeting or recording a
Spotlight** or **Toggle Laser Pointer**, then use it while sharing your screen in a meeting or recording a
demo.

## Presentation timer
Expand All @@ -87,7 +107,10 @@ can provide configurable haptic feedback.
Projecteur can map device input to:

- a keyboard sequence;
- the next or previous spotlight preset;
- toggling the spotlight overlay;
- toggling the laser pointer;
- toggling between pointer modes (Spotlight / Laser);
- cycling or selecting presets;
- vertical or horizontal scrolling;
- volume control;
- other built-in presentation actions.
Expand Down Expand Up @@ -119,6 +142,10 @@ Projecteur can control an already running instance from scripts. Common examples

```sh
projecteur --command spot=toggle
projecteur --command laser=toggle
projecteur --command pointer=laser
projecteur --command pointer=toggle
projecteur --command laser.size.adjust=+5
projecteur --command settings=show
projecteur --command preset="2 Demo"
```
Expand Down
16 changes: 16 additions & 0 deletions plasma/projecteurapplet.cc
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ void ProjecteurApplet::setSpotlightActive(bool active)
if (m_interface) { m_interface->SetSpotlightActive(active); }
}

void ProjecteurApplet::setPointerMode(const QString& mode)
{
if (m_interface) { m_interface->SetPointerMode(mode); }
}

void ProjecteurApplet::loadPreset(const QString& preset)
{
if (m_interface) { m_interface->LoadPreset(preset); }
Expand Down Expand Up @@ -129,6 +134,13 @@ void ProjecteurApplet::remoteSpotlightActiveChanged(bool active)
emit spotlightActiveChanged();
}

void ProjecteurApplet::remotePointerModeChanged(const QString& mode)
{
if (m_pointerMode == mode) { return; }
m_pointerMode = mode;
emit pointerModeChanged();
}

void ProjecteurApplet::remoteConnectedDevicesChanged(const QStringList& devices)
{
if (m_connectedDevices == devices) { return; }
Expand Down Expand Up @@ -203,6 +215,8 @@ void ProjecteurApplet::createInterface()
this, &ProjecteurApplet::remoteOverlayEnabledChanged);
connect(m_interface, &OrgProjecteurProjecteurInterface::spotlightActiveChanged,
this, &ProjecteurApplet::remoteSpotlightActiveChanged);
connect(m_interface, &OrgProjecteurProjecteurInterface::pointerModeChanged,
this, &ProjecteurApplet::remotePointerModeChanged);
connect(m_interface, &OrgProjecteurProjecteurInterface::connectedDevicesChanged,
this, &ProjecteurApplet::remoteConnectedDevicesChanged);
connect(m_interface,
Expand Down Expand Up @@ -236,6 +250,7 @@ void ProjecteurApplet::refresh()
}
remoteOverlayEnabledChanged(m_interface->overlayEnabled());
remoteSpotlightActiveChanged(m_interface->spotlightActive());
remotePointerModeChanged(m_interface->pointerMode());
remoteConnectedDevicesChanged(m_interface->connectedDevices());
remoteConnectedDeviceBatteryLevelsChanged(m_interface->connectedDeviceBatteryLevels());
remoteConnectedDeviceBatteryStatusesChanged(m_interface->connectedDeviceBatteryStatuses());
Expand All @@ -259,6 +274,7 @@ void ProjecteurApplet::resetState()
}
remoteOverlayEnabledChanged(true);
remoteSpotlightActiveChanged(false);
remotePointerModeChanged(QStringLiteral("spotlight"));
remoteConnectedDevicesChanged({});
remoteConnectedDeviceBatteryLevelsChanged({});
remoteConnectedDeviceBatteryStatusesChanged({});
Expand Down
6 changes: 6 additions & 0 deletions plasma/projecteurapplet.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class ProjecteurApplet : public Plasma::Applet
Q_PROPERTY(bool trayVisible READ trayVisible NOTIFY trayVisibleChanged)
Q_PROPERTY(bool overlayEnabled READ overlayEnabled NOTIFY overlayEnabledChanged)
Q_PROPERTY(bool spotlightActive READ spotlightActive NOTIFY spotlightActiveChanged)
Q_PROPERTY(QString pointerMode READ pointerMode NOTIFY pointerModeChanged)
Q_PROPERTY(QStringList connectedDevices READ connectedDevices NOTIFY connectedDevicesChanged)
Q_PROPERTY(QList<int> connectedDeviceBatteryLevels READ connectedDeviceBatteryLevels
NOTIFY connectedDeviceBatteryLevelsChanged)
Expand All @@ -38,6 +39,7 @@ class ProjecteurApplet : public Plasma::Applet
bool trayVisible() const { return m_trayVisible; }
bool overlayEnabled() const { return m_overlayEnabled; }
bool spotlightActive() const { return m_spotlightActive; }
QString pointerMode() const { return m_pointerMode; }
QStringList connectedDevices() const { return m_connectedDevices; }
QList<int> connectedDeviceBatteryLevels() const { return m_connectedDeviceBatteryLevels; }
QStringList connectedDeviceBatteryStatuses() const { return m_connectedDeviceBatteryStatuses; }
Expand All @@ -50,6 +52,7 @@ class ProjecteurApplet : public Plasma::Applet

Q_INVOKABLE void setOverlayEnabled(bool enabled);
Q_INVOKABLE void setSpotlightActive(bool active);
Q_INVOKABLE void setPointerMode(const QString& mode);
Q_INVOKABLE void loadPreset(const QString& preset);
Q_INVOKABLE void setTimerEnabled(bool enabled);
Q_INVOKABLE void startTimer();
Expand All @@ -66,6 +69,7 @@ class ProjecteurApplet : public Plasma::Applet
void trayVisibleChanged();
void overlayEnabledChanged();
void spotlightActiveChanged();
void pointerModeChanged();
void connectedDevicesChanged();
void connectedDeviceBatteryLevelsChanged();
void connectedDeviceBatteryStatusesChanged();
Expand All @@ -81,6 +85,7 @@ private slots:
void serviceUnregistered(const QString& service);
void remoteOverlayEnabledChanged(bool enabled);
void remoteSpotlightActiveChanged(bool active);
void remotePointerModeChanged(const QString& mode);
void remoteConnectedDevicesChanged(const QStringList& devices);
void remoteConnectedDeviceBatteryLevelsChanged(const QList<int>& levels);
void remoteConnectedDeviceBatteryStatusesChanged(const QStringList& statuses);
Expand All @@ -103,6 +108,7 @@ private slots:
bool m_trayVisible = true;
bool m_overlayEnabled = true;
bool m_spotlightActive = false;
QString m_pointerMode = QStringLiteral("spotlight");
QStringList m_connectedDevices;
QList<int> m_connectedDeviceBatteryLevels;
QStringList m_connectedDeviceBatteryStatuses;
Expand Down
33 changes: 31 additions & 2 deletions plasma/qml/FullRepresentation.qml
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ PlasmaExtras.Representation {
spacing: Kirigami.Units.smallSpacing

PlasmaComponents3.Label {
text: i18n("Spotlight")
text: i18n("Pointer Controls")
font.bold: true
}

Expand All @@ -320,6 +320,29 @@ PlasmaExtras.Representation {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing

RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing

PlasmaComponents3.Label {
text: i18n("Mode:")
}

PlasmaComponents3.ComboBox {
id: modeCombo

Layout.fillWidth: true
enabled: root.backend && root.backend.serviceAvailable
model: [i18n("Spotlight"), i18n("Laser Pointer")]
currentIndex: (root.backend && root.backend.pointerMode === "laser") ? 1 : 0
onActivated: (index) => {
if (root.backend) {
root.backend.setPointerMode(index === 1 ? "laser" : "spotlight");
}
}
}
}

RowLayout {
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing
Expand Down Expand Up @@ -355,7 +378,13 @@ PlasmaExtras.Representation {
PlasmaComponents3.Button {
Layout.fillWidth: true
enabled: root.backend && root.backend.serviceAvailable
text: root.backend && root.backend.spotlightActive ? i18n("Hide Spotlight") : i18n("Test Spotlight")
text: {
const isLaser = root.backend && root.backend.pointerMode === "laser";
if (root.backend && root.backend.spotlightActive) {
return isLaser ? i18n("Hide Laser") : i18n("Hide Spotlight");
}
return isLaser ? i18n("Test Laser") : i18n("Test Spotlight");
}
icon.name: root.backend && root.backend.spotlightActive ? "visibility-hidden-symbolic" : "visibility-symbolic"
onClicked: root.backend.setSpotlightActive(!root.backend.spotlightActive)
}
Expand Down
Loading