iberianpig / fusuma

Multitouch gestures with libinput driver on Linux
MIT License
3.58k stars 146 forks source link

optional blocking begin and end commands #290

Open ces42 opened 1 year ago

ces42 commented 1 year ago

Is your feature request related to a problem? Please describe. I've been trying to using the begin and end triggers to focus the window the cursor is over/refocus the previously focused window so that I can use 3-finger swipes to change tabs (by sending ctrl+PgUp/Down with ydotool) in windows that aren't currently focused. It kind of works, but it seems like sometimes wmctrl command I use to focus the window hasn't finished when ydotool sends the keypress, so the wrong window receives it.

Describe the solution you'd like My could probably be fixed if there was some way to make commands (at least for begin and end "gesture") be executed synchronously, i.e. no other commands are executed before their process has exited. Probably the easiest way to do this would be for the whole fusuma process to wait until the command exits.

ces42 commented 1 year ago

I've been playing around trying to do this and it seems to me that replacing https://github.com/iberianpig/fusuma/blob/1853e2d18918e8975c2c514066ab18914c337e87/lib/fusuma/plugin/executors/command_executor.rb#L27 by

if event.record.index.keys[-1].symbol == :begin
  Process.wait(pid)
else
  Process.detach(pid)
end

would implement this always, for begin commands.

I don't really have any idea how to create an option in config.yml that would allow the user to turn this on/off.

iberianpig commented 1 year ago

Could you paste your config.yml? I would like to know in detail what kind of workflow you are trying to create.

ces42 commented 1 year ago

Here's the relevant part of the config

swipe:
    3:
      begin:
        command: |-
            sedGetValue='s/.*=\(.*\)/\1/'
            old_win=$(xdotool getactivewindow)
            id=$(xdotool getmouselocation --shell | grep WINDOW | sed 's/.*=\(.*\)/\1/')
            if [ $old_win != $id ]; then
                # wmctrl -iR "$id"
                xdotool windowfocus "$id"
                echo $old_win > "/var/run/user/$(id -u)/fusuma-old-win"
            else
                echo 'SAME' > "/var/run/user/$(id -u)/fusuma-old-win"
            fi
        interval: 0.1
      end:
        command: |-
            old_win=$(cat /var/run/user/$(id -u)/fusuma-old-win)
            echo $old_win
            if [ "$old_win" != 'SAME' ]; then
                xdotool windowfocus $old_win
            fi
      left:
        update:
          command: |-
              ydotool key --key-delay 0 29:1 109:1 109:0 29:0 # ctrl and PgDown
          threshold: 0.5
          interval: 1.7
      right:
        update:
          command: |-
              ydotool key --key-delay 0 29:1 104:1 104:0 29:0 # ctrl and PgUp
          threshold: 0.5
          interval: 1.7
      up:
          command: |-
              case $(xprop -id $id WM_CLASS | cut -d " -f 4) in
                  firefox|"Firefox Developer Edition"|"Tor Browser"|kitty)
                      ydotool key --key-delay 0 29:1 62:1 62:0 29:0
                      ;;
                  *)
                      ydotool key --key-delay 0 29:1 17:1 17:0 29:0
                      ;;
              esac
          threshold: 0.7
      down:
          command: |-
              case $(xprop -id $id WM_CLASS | cut -d " -f 4) in
                  Tilix|kitty|Guake)
                      ydotool key --key-delay 0 29:1 42:1 20:1 20:0 42:0 29:0
                      echo 'SAME' > "/var/run/user/$(id -u)/fusuma-old-win"
                      ;;
                  *)
                      ydotool key --key-delay 0 29:1 20:1 20:0 29:0
                      echo 'SAME' > "/var/run/user/$(id -u)/fusuma-old-win"
                      ;;
              esac
          threshold: 0.6

(I'm having the different shell commands communicate using "files" is /var/run/.)

The problem I was describing was mostly solved by replacing wmctrl -iR "$id" by xdotool windowfocus "$id", the latter seems to be much faster. But I expect that this will still cause trouble under load.

ces42 commented 1 year ago

Although I've been thinking that probably the best solution for my problems is to switch to a client-server model where fusuma just writes someting like 3finger-begin, 3finger-right, etc. to a named pipe and I have a separate process reading from it and executing what I want.

iberianpig commented 1 year ago

As you say, it would be nice to implement job-queue in separate processes.

Considering the cost of implementation, it also seems good to use waitpid https://github.com/iberianpig/fusuma/issues/290#issuecomment-1478803327

libinput_command_input_plugin may be helpful for how to get values ​​from config. https://github.com/iberianpig/fusuma/blob/1853e2d18918e8975c2c514066ab18914c337e87/lib/fusuma/plugin/inputs/libinput_command_input.rb#L16

https://github.com/iberianpig/fusuma/blob/1853e2d18918e8975c2c514066ab18914c337e87/lib/fusuma/plugin/inputs/libinput_command_input.rb#L49

ces42 commented 1 year ago

I went ahead and wrote an executor plugin:

# frozen_string_literal: true

module Fusuma
  module Plugin
    module Executors
      # Server executor plugin
      class ServerExecutor < Executor

        # executor properties on config.yml
        # @return [Array<Symbol>]
        def execute_keys
          [:server]
        end

        def config_param_types
          {
            'pipe': [String],
          }
        end

        def initialize
          super()
          @pipe = config_params(:'pipe')
        end

        def execute(event)
          command = search_command(event)

          MultiLogger.info(server: command, args: event.record.args)

          File.write(@pipe, command.to_s + "\n")
        rescue SystemCallError => e
          MultiLogger.error("#{event.record.index.keys}": e.message.to_s)
        end

        def executable?(event)
          event.tag.end_with?("_detector") &&
            event.record.type == :index &&
            search_command(event)
        end

        # @param event [Event]
        # @return [String]
        def search_command(event)
          command_index = Config::Index.new([*event.record.index.keys, :server])
          Config.search(command_index)
        end

      end
    end
  end
end

(This is mostly copy pasted from command_executor, I don't really know any ruby). My config.yaml now is something like

swipe:
    3:
        begin:
          server: three_finger_begin
          interval: 0.1
        end:
          server: three_finger_end
        left:
          update:
            server: three_finger_left
            threshold: 0.5
            interval: 1.7
        right:
          update:
            server: three_finger_right
            threshold: 0.5
            interval: 1.7
        up:
            server: three_finger_up
            threshold: 0.7
        down:
            server: three_finger_down
            threshold: 0.6
plugin:
  executors:
    server_executor:
      pipe: /var/run/user/1000/fusuma_fifo

My "server" is currently just a sh process reading from that pipe after I have it source a file with function definitions.

iberianpig commented 1 year ago

Oh, you wrote a fusuma plugin without any knowledge of Ruby? That's amazing.