最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

haskell - Force a delay between key presses in XMonad - Stack Overflow

programmeradmin3浏览0评论

Using XMonad, I have a keybinding on my desktop that on Windows+F2 runs volume down and Windows+F3 runs volume up. This works just fine except it can be awkward at times.

So I'm trying to switch to just F2 and F3. The problem is that when I don't have my super key (Windows) in the keymap, as long as I am pressing the volume up or down buttons XMonad is running my simple volume up script. This creates a really unpredictable nature in my desktop where it is hard to control how much I am actually changing the volume unless I do it through the terminal.

((0, xK_F2), spawn "volume down 5"), -- run /usr/bin/volume --> ~/.config/scripts/cpp/volume
((0, xK_F3), spawn "volume up 5")  -- ditto

While I do not know how it works, the spawn function essentially does a std::system("volume up 5") which runs a very simple C++ program I wrote to easily change the volume the way I like it.

So here is the philosophical solution I have as a programmer with experience in procedural languages, but I cannot for the life of me implement in Haskell: Every time the button is pressed save the CPU time in a variable like lastpress and compare it to the last instance of the variable last press and if the difference is greater than 15ms (about the human reaction speed with some room for fine tuning/debugging) then allow spawn to run. This prevents spawn from running unless I am intentionally pressing the button. I can also write some more sophisticated function later to allow holding the button down but in a more predictable way, once I get the basic idea working.

Can anyone give me any pointers as to how I can accomplish this in Haskell?


For the sake of reference, here is what I see as a temporary solution, which I implemented by changing the volume C++ program spawned by xmonad:

#include <cstdio>
#include <cstdlib>
#include <utility>
#include <string>
#include <fstream>
#include <chrono>

int main(int argc, char* argv[])
{
    // store the command to run here
    std::string command;
    
    // If there is no argument, print the master volume
    if(argc == 1) command = std::string("amixer get Master | awk -F\"[][]\" '/Left:/ { print $2 }'");
    else 
    {
        // This is not production code and I am using it in a very limited case on my system.
        // Minimal security implemented!!
        // Assume only 2 arguments

        // Read the contents of the file into a string. This file contains the system time of the last time this point was reached in the code.
        // This is a temporary solution until I learn how to do this in the xmonad.hs file instead
        std::ifstream fs_in("/home/nick/.config/lastvolpress");
        std::string str( (std::istreambuf_iterator<char>( fs_in )), (std::istreambuf_iterator<char>()) );
        long long last_ms = std::stoll( str ); // Convert the last press time to a long long (probably the type that will be exported by count function later)
        fs_in.close();

        const auto now = std::chrono::system_clock::now(); // get the current time
        long long now_ms = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count(); // Number of microseconds since 01-01-1970 0:00
        if(now_ms - last_ms < 15) return 0; // If 15ms has not passed since the last time the volume was adjusted from this program, exit.
        else
        {
            std::ofstream fs_out("/home/nick/.config/lastvolpress", std::ios::trunc);
            fs_out << now_ms;
            fs_out.close();
            auto action = std::make_pair( std::string(argv[1]), std::string(argv[2]));
            if(action.first == "up") 
            {
                command = std::string("pactl set-sink-volume alsa_output.pci-0000_00_1f.3.analog-stereo +") + action.second + std::string("%");
            }
            else // assume down because I will know how to use it
            {
                command = std::string("pactl set-sink-volume alsa_output.pci-0000_00_1f.3.analog-stereo -") + action.second + std::string("%");
            }
        }

    }
    std::system(command.c_str()); // run the command
    return 0;
}

This works precisely as I meant it to, so I have removed the modmask (Windows+) from my xmonad.hs code. Now my volume controls are so much more responsive and my problem is solved temporarily.

This is not a true fix, though, because it is short-sighted. This script is not meant to be used only by the volume keys, so I would like to separate the functionality between key-bindings and system scripts to keep my system anized. Also, since volume is not always running, I have to store the time in a file rather than just a variable as I could do in Xmonad. This is wasteful and contrived lol. I still will keep trying to accomplish this in haskell.

Using XMonad, I have a keybinding on my desktop that on Windows+F2 runs volume down and Windows+F3 runs volume up. This works just fine except it can be awkward at times.

So I'm trying to switch to just F2 and F3. The problem is that when I don't have my super key (Windows) in the keymap, as long as I am pressing the volume up or down buttons XMonad is running my simple volume up script. This creates a really unpredictable nature in my desktop where it is hard to control how much I am actually changing the volume unless I do it through the terminal.

((0, xK_F2), spawn "volume down 5"), -- run /usr/bin/volume --> ~/.config/scripts/cpp/volume
((0, xK_F3), spawn "volume up 5")  -- ditto

While I do not know how it works, the spawn function essentially does a std::system("volume up 5") which runs a very simple C++ program I wrote to easily change the volume the way I like it.

So here is the philosophical solution I have as a programmer with experience in procedural languages, but I cannot for the life of me implement in Haskell: Every time the button is pressed save the CPU time in a variable like lastpress and compare it to the last instance of the variable last press and if the difference is greater than 15ms (about the human reaction speed with some room for fine tuning/debugging) then allow spawn to run. This prevents spawn from running unless I am intentionally pressing the button. I can also write some more sophisticated function later to allow holding the button down but in a more predictable way, once I get the basic idea working.

Can anyone give me any pointers as to how I can accomplish this in Haskell?


For the sake of reference, here is what I see as a temporary solution, which I implemented by changing the volume C++ program spawned by xmonad:

#include <cstdio>
#include <cstdlib>
#include <utility>
#include <string>
#include <fstream>
#include <chrono>

int main(int argc, char* argv[])
{
    // store the command to run here
    std::string command;
    
    // If there is no argument, print the master volume
    if(argc == 1) command = std::string("amixer get Master | awk -F\"[][]\" '/Left:/ { print $2 }'");
    else 
    {
        // This is not production code and I am using it in a very limited case on my system.
        // Minimal security implemented!!
        // Assume only 2 arguments

        // Read the contents of the file into a string. This file contains the system time of the last time this point was reached in the code.
        // This is a temporary solution until I learn how to do this in the xmonad.hs file instead
        std::ifstream fs_in("/home/nick/.config/lastvolpress");
        std::string str( (std::istreambuf_iterator<char>( fs_in )), (std::istreambuf_iterator<char>()) );
        long long last_ms = std::stoll( str ); // Convert the last press time to a long long (probably the type that will be exported by count function later)
        fs_in.close();

        const auto now = std::chrono::system_clock::now(); // get the current time
        long long now_ms = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count(); // Number of microseconds since 01-01-1970 0:00
        if(now_ms - last_ms < 15) return 0; // If 15ms has not passed since the last time the volume was adjusted from this program, exit.
        else
        {
            std::ofstream fs_out("/home/nick/.config/lastvolpress", std::ios::trunc);
            fs_out << now_ms;
            fs_out.close();
            auto action = std::make_pair( std::string(argv[1]), std::string(argv[2]));
            if(action.first == "up") 
            {
                command = std::string("pactl set-sink-volume alsa_output.pci-0000_00_1f.3.analog-stereo +") + action.second + std::string("%");
            }
            else // assume down because I will know how to use it
            {
                command = std::string("pactl set-sink-volume alsa_output.pci-0000_00_1f.3.analog-stereo -") + action.second + std::string("%");
            }
        }

    }
    std::system(command.c_str()); // run the command
    return 0;
}

This works precisely as I meant it to, so I have removed the modmask (Windows+) from my xmonad.hs code. Now my volume controls are so much more responsive and my problem is solved temporarily.

This is not a true fix, though, because it is short-sighted. This script is not meant to be used only by the volume keys, so I would like to separate the functionality between key-bindings and system scripts to keep my system anized. Also, since volume is not always running, I have to store the time in a file rather than just a variable as I could do in Xmonad. This is wasteful and contrived lol. I still will keep trying to accomplish this in haskell.

Share Improve this question asked Feb 16 at 13:39 Nicholas AllbrittonNicholas Allbritton 191 silver badge4 bronze badges 4
  • Upon rereading the question, I'm no longer sure about what you mean by "prevents spawn from running unless I am intentionally pressing the button". Is the problem that when you hold the keys the volume changes faster than you'd like, with that only being if you aren't using the Super mod in the keymap? – duplode Commented Feb 16 at 14:41
  • Yeah exactly that. It's unpredictable. Really small thing to be bothered by but if I can fix it why not? – Nicholas Allbritton Commented Feb 16 at 14:47
  • I had a quick try of a simplified version of what you're doing, spawning pactl directly from XMonad (e.g. ((0, xK_F2), spawn "pactl set-sink-volume 0 -3%")). What I see when I press F2 and look at the pavucontrol window is an initial 3% change, a brief pause, and then quick changes at about the rate I'd expect the input to be when holding a keyboard key. Also, the behaviour is exactly the same with or without the Super mod. Does that match what you're seeing, and what you'd like to make slower? (If so, it should be possible to simplify the question by removing some inessential details.) – duplode Commented Feb 16 at 15:15
  • yeah that's right. I want it to only change one time when I press the key basically. Except when I include the super key in the key map I don't get that type of behavior, or it isn't as obvious if it is so I didn't notice it. – Nicholas Allbritton Commented Feb 16 at 15:22
Add a comment  | 

1 Answer 1

Reset to default 3

One solution would be to modify your C++ program to take an flock on its own executable for your desired delay time (and quit without changing the volume if another process already has the lock).

Another somewhat more intrusive solution would be to modify your keyboard repeat rate; check out xset for that.

Here's an example of the flock approach. Toss this in a script somewhere. Supposing you name it chvol, you can then bind some keys to spawn chvol + and chvol - in xmonad.

#!/bin/sh
sink=alsa_output.pci-0000_00_1f.3.analog-stereo
flock -n "$0" sh -c "pactl set-sink-volume $sink $1; sleep 0.1"
发布评论

评论列表(0)

  1. 暂无评论