pdfpc / pdfpc

A presenter console with multi-monitor support for PDF files.
https://pdfpc.github.io/
GNU General Public License v3.0
1.55k stars 116 forks source link

Allow mode-specific key and mouse bindings #426

Open dev-zero opened 5 years ago

dev-zero commented 5 years ago

I'm trying to setup the Logitech Spotlight presenter with pdfpc. The presenter itself is properly recognized by Linux as a mouse device which generates a LEFT and RIGHT key events with the control buttons.

The button to turn on the gyroscope-based mouse generates a mouse button 3 event as per xev:

ButtonPress event, serial 41, synthetic NO, window 0x7200001,
    root 0x296, subw 0x7200002, time 1217807330, (30,44), root:(3594,73),
    state 0x10, button 3, same_screen YES

EnterNotify event, serial 41, synthetic NO, window 0x7200001,
    root 0x296, subw 0x0, time 1217807330, (30,44), root:(3594,73),
    mode NotifyGrab, detail NotifyInferior, same_screen YES,
    focus YES, state 1040

KeymapNotify event, serial 41, synthetic NO, window 0x0,
    keys:  4294967190 0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   
           0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   

ButtonRelease event, serial 41, synthetic NO, window 0x7200001,
    root 0x296, subw 0x7200002, time 1217807394, (30,44), root:(3594,73),
    state 0x410, button 3, same_screen YES

LeaveNotify event, serial 41, synthetic NO, window 0x7200001,
    root 0x296, subw 0x0, time 1217807394, (30,44), root:(3594,73),
    mode NotifyUngrab, detail NotifyInferior, same_screen YES,
    focus YES, state 16

Using the binding mouse 3 switchMode pointer I can switch the mode to pointer while at the same time enabling the mouse. The problem is that it doesn't switch back to normal mode (and hide the pointer) when releasing the button again.

What I would need is something like:

mouse 3:down switchMode pointer
mouse 3:up switchMode normal

Plus maybe an option to not hide the mouse pointer on the presenter console when in normal mode to remember where I left the pointer.

An alternative to always switching modes could be to add a new command to change the opacity of the pointer.

Following the code I guess I would have to add a new attribute to the KeyDef type (and the BindTouple before including the respective config parsing) and then remove && button.type == Gdk.EventType.BUTTON_PRESS in the button_press implementation in src/classes/presentation_controller.vala?

fnevgeny commented 5 years ago

This is just the first thought, but I am not sure that enabling binding to on/off button events is the right thing. What your presenter does is essentially (in the standard GUI terms) sending the mouse drag events, right? So probably a way to specify what to do on mouse drag would be the way to go.

An alternative to always switching modes could be to add a new command to change the opacity of the pointer.

That would be a dirty hack and rather a resource-hungry one at that...

dev-zero commented 5 years ago

This is just the first thought, but I am not sure that enabling binding to on/off button events is the right thing. What your presenter does is essentially (in the standard GUI terms) sending the mouse drag events, right? So probably a way to specify what to do on mouse drag would be the way to go.

hmm, I did some more testing and it seems that the device is a bit more complicated than I thought.

The button to enable the mouse has two modes: one is the click, which gives a ButtonPress/ButtonRelease with button 3. The other is press+hold at which point there is no Button-event, only the MotionNotify start to appear when moving the presenter and stop when releasing the button again.

ButtonPress event, serial 40, synthetic NO, window 0x7200001,
    root 0x296, subw 0x0, time 1225783166, (190,287), root:(2626,567),
    state 0x10, button 3, same_screen YES

ButtonRelease event, serial 40, synthetic NO, window 0x7200001,
    root 0x296, subw 0x0, time 1225783232, (190,287), root:(2626,567),
    state 0x410, button 3, same_screen YES

MotionNotify event, serial 40, synthetic NO, window 0x7200001,
    root 0x296, subw 0x0, time 1225785466, (189,287), root:(2625,567),
    state 0x10, is_hint 0, same_screen YES

MotionNotify event, serial 40, synthetic NO, window 0x7200001,
    root 0x296, subw 0x0, time 1225785485, (189,287), root:(2625,567),
    state 0x10, is_hint 0, same_screen YES

This means that easiest way to get it basically working is a toggleMode action. Then I could simply click once to enable the pointer, do a click+hold to drag it around and then click once again to switch back to normal.

fnevgeny commented 5 years ago

The other is press+hold at which point there is no Button-event, only the MotionNotify start to appear when moving the presenter and stop when releasing the button again.

Well, isn't it exactly the same what normal mouse does?

dev-zero commented 5 years ago

The other is press+hold at which point there is no Button-event, only the MotionNotify start to appear when moving the presenter and stop when releasing the button again.

Well, isn't it exactly the same what normal mouse does?

Yes and no: on a mouse you have a ButtonDown event first, then the Motion events, then the ButtonRelease event, which gives you the drag action, right? Here it's just that while holding the button it acts as a normal mouse but you don't get the ButtonDown/Release events.

fnevgeny commented 5 years ago

Here it's just that while holding the button it acts as a normal mouse but you don't get the ButtonDown/Release events.

I'm confused. When you press the button, how does the device know - will you just release it (i.e., intending to make a click and then it needs to send ButtonDown) or continue keeping it pressed (no ButtonDown now and no ButtonRelease later on)? Does it come with a crystal ball attached? :)

PS. It's so inconsiderate of Logitech not sending their new devices to pdfpc developers for testing :)

dev-zero commented 5 years ago

Here it's just that while holding the button it acts as a normal mouse but you don't get the ButtonDown/Release events.

I'm confused. When you press the button, how does the device know - will you just release it (i.e., intending to make a click and then it needs to send ButtonDown) or continue keeping it pressed (no ButtonDown now and no ButtonRelease later on)? Does it come with a crystal ball attached? :)

When I press+hold the button there is a small delay until the cursor starts moving. So my guess is that they have a built-in delay to either generate a ButtonDown+ButtonRelease event or start sending the movement events.

PS. It's so inconsiderate of Logitech not sending their new devices to pdfpc developers for testing :)

Given that Logitech does not care for Linux and that on Windows and macOS they have their own software taking care of it (and allowing for additional effects), I doubt they would :(

Anyway, my first attempt is something like:

diff --git a/src/classes/presentation_controller.vala b/src/classes/presentation_controller.vala
index 9ae2010..d8b355e 100644
--- a/src/classes/presentation_controller.vala
+++ b/src/classes/presentation_controller.vala
@@ -344,6 +344,7 @@ namespace pdfpc {
         }

         private AnnotationMode annotation_mode = AnnotationMode.NORMAL;
+        private AnnotationMode prev_annotation_mode = AnnotationMode.NORMAL;

         /**
          * Instantiate a new controller
@@ -610,6 +611,7 @@ namespace pdfpc {
                 return;
             }

+            this.prev_annotation_mode = this.annotation_mode;
             this.annotation_mode = mode;

             switch (mode) {
@@ -902,6 +904,10 @@ namespace pdfpc {
             this.set_mode(AnnotationMode.parse(mode_variant.get_string()));
         }

+        public void toggle_mode() {
+            this.set_mode(this.prev_annotation_mode);
+        }
+
         protected void add_actions() {
             add_action("next", this.next_page,
                 "Go to the next slide");
@@ -968,6 +974,8 @@ namespace pdfpc {
             add_action_with_parameter("switchMode", GLib.VariantType.STRING,
                 this.set_mode_to_string,
                 "Switch annotation mode (normal|pointer|pen|eraser)", "mode");
+            add_action("toggleMode", this.toggle_mode,
+                "Toggle between current and previous annotation mode");

             add_action("increaseSize", this.increase_size,
                 "Increase the size of notes|pointer|pen|eraser");

... only problem being that my toggle_mode doesn't get called anymore after entering pointer mode.

fnevgeny commented 5 years ago

... only problem being that my toggle_mode doesn't get called anymore after entering pointer mode.

Yes, the configurable mouse bindings actually operate only in the "normal" mode, in others everything is hardcoded (including some keystrokes). To some extent it's reasonable, since e.g., the (standard) binding of mouse1 to next shouldn't affect one's ability to draw a curve in the drawing mode, etc...

I'm not sure what to do. One option is to introduce configurable context-sensitive (that's, mode-sensitive) bindings (either only mouse or mouse and keys alike). This is probably the most general approach, but would mean a rather substantial coding. Another option is to hardcode mouse3 to switch to the normal mode (when in any other mode). Which might come handy also with a normal mouse.

dev-zero commented 5 years ago

:+1: 🤣 thanks for the hint!

diff --git a/src/classes/presentation_controller.vala b/src/classes/presentation_controller.vala
index 9ae2010..088f0cf 100644
--- a/src/classes/presentation_controller.vala
+++ b/src/classes/presentation_controller.vala
@@ -770,6 +770,9 @@ namespace pdfpc {
                 drag_x=-1;
                 drag_y=-1;

+                if (event.button == 3) {
+                    this.set_normal_mode();
+                }
                 return true;
             });
dev-zero commented 5 years ago

Small update: on a newer kernel (4.20.12 than 4.12.14), the button to enable the mouse shows up as button 1 instead of button 3. Everything else remains the same.

fnevgeny commented 5 years ago

Hmm, then you cannot use it for forward movement (when in the normal mode)??

fnevgeny commented 5 years ago

OK, I see. The next/prev buttons send the keyboard events; the device is recognized as a keyboard/mouse combo.

fnevgeny commented 5 years ago

Maybe instead of catering for this rather specific device, an external application should translate/map its events reasonably? Have you seen this project, BTW?

dev-zero commented 5 years ago

Yes, I've seen that and almost tried it. But then I discovered that pdfpc has a pointer mode already built-in and decided it would be much easier to use, especially when it comes to mapping from a presenter console to the presentation screen.

In fact, the only thing missing to make it work is a (limited, but separate) action-support in pointer mode.

The alternative would be to setup some mapping as described in one of the following:

fnevgeny commented 5 years ago

@dev-zero BTW, do you know how to send commands back to the presenter device (make it vibrate)? It would be possible to couple it with some timer events (which is what the vendor software does under Win/Mac, if I understand correctly).

mayanksuman commented 4 years ago

@fnevgeny Check this comment. It is possible to make the presenter vibrate by sending a special package on usb.

fnevgeny commented 4 years ago

Thanks. It doesn't work for me, though, for a reason. Also, not applicable when connecting through BT directly...

mayanksuman commented 4 years ago

Thanks. It doesn't work for me, though, for a reason. Also, not applicable when connecting through BT directly...

It is not clear to me. Does it work on usb? The problem only remain if the device is used with BT.

The code is not supposed to work with BT as it uses libusb which do not support BT devices. I am currently working on hidapi based code that might work with BT also.

fnevgeny commented 4 years ago

It is not clear to me. Does it work on usb?

No. Don't have it now with me, the error was something like command sequence not recognized.

fnevgeny commented 4 years ago

The problem is that it doesn't switch back to normal mode (and hide the pointer) when releasing the button again.

Does your original problem boil down to switching off the cursor (after some period of inactivity)? That would make sense in general, irrespective of a specific input device used.