Xfce Forum

Sub domains
 

You are not logged in.

#1 2024-06-12 19:40:28

advice1010
Member
Registered: 2023-02-19
Posts: 101

Super key to open up menu but also be used as a modifier key

I was just wondering if someone could confirm a couple things for me because I am using a Debian based distro that does not have access to newer versions of XFCE to test.
The distro that I am using handles the following items fine, but I just always felt that these should be built directly into XFCE itself.  I made a request for these recently and have been told that in newer versions they actually exist already so my requests have been closed.

FEATURES REQUESTED

REQUEST 1
-Single Super key press & release = toggle panel menu open/closed
-Hold down Super key = use as a modifier key

I have been told that the Super key press / modifier action was added in “libxfce4ui 4.18.5”
(This used to require 3rd party applications such as xfce-superkey)


REQUEST 2
-Add the ability to be able to choose the command that the Super key uses with a single press & release (mentioned above)
This would allow user the ability to be able to choose for example which menu they want to assign to the key, such as XFCE application menu, Whisker menu, or a 3rd party menu for example something like ROFI.
This would also allow users to be able to choose to add OPTIONS to the commands, for example if you wanted to use Whisker -c OPTION to be able to load up the menu in the center of your screen.

I was also told that the ability to choose the command/OPTION is available as well.
"Adding a command shortcut via xfce4-keyboard-settings already allows to achieve this, and the panel plugins have a "remote-event" signal to implement this (as do whisker and the app menu shipped with the panel)."

I know you can setup custom hotkeys for applications, so does this mean that with the new Super key update all you have to do is assign Super L (Left Super keyboard button not the letter L) to the command?
I have not looked it up yet, I am not aware of panel “remote-event” signals.

------------------------------------------------------------------------------------------------------------------------------
Again just wondering if someone could confirm that these features have been added in the way that I was looking for.  I didn't realize they had been added already, I did not really see anything about this when researching before making the request.  Anyways, just wanted to know that they will be available in over a year from now when I will actually be able to access them smile

Thank you to anyone who reads this and is willing to confirm this.

Last edited by advice1010 (2024-06-12 19:42:05)

Offline

#2 2024-06-12 20:40:20

ToZ
Administrator
From: Canada
Registered: 2011-06-02
Posts: 11,313

Re: Super key to open up menu but also be used as a modifier key

Yes, the new code allows you to have separate keyboard shortcuts where one can be the just the super key and another the superkey + another key.

However, the super key does not act like a toggle. In other words, it can open the menu, but pressing it again won't close the menu. I wonder if thats a GTK limitation.

I was also told that the ability to choose the command/OPTION is available as well.
"Adding a command shortcut via xfce4-keyboard-settings already allows to achieve this, and the panel plugins have a "remote-event" signal to implement this (as do whisker and the app menu shipped with the panel)."

Yes:

xfce4-panel --plugin-event=applicationsmenu:popup:bool:false

...or:

xfce4-panel --plugin-event=whiskermenu:popup:bool:false

But there are helper scripts available to do that:
- /usr/bin/xfce4-popup-applicationsmenu
- /usr/bin/xfce4-popup-whiskermenu


Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki  |  Community | Contribute ---

Offline

#3 2024-06-12 22:46:57

advice1010
Member
Registered: 2023-02-19
Posts: 101

Re: Super key to open up menu but also be used as a modifier key

@ToZ
Again amazing answer, thank you so much.
Wow, I didn't realize this was added, didn't see anything about this when doing research before making my posts. That is great, as mentioned I always thought it was strange that something like this was not built in, but now it is. I cannot remember but I think it was also mentioned to me that this had to be done for future support of Wayland, but not sure.

Not fully needed, but I do really like the toggle just in case I want to close, do you know if the "Escape" key at least works?
I don't know if it would be a GTK limitation, because I am currently using xfce-superkey and that allows for the toggle, but I don't know if that means anything.  If not a limitation though, I wonder why this was not added into XFCE, maybe I will mention.

I apologize because I just might not fully understand your example, but do you know if that would allow user to be able to use an option like -c for Whisker Menu to load in center of your screen?  I mentioned to the developer maybe adding that center option to Whiskers GUI settings window but no response.

Anyways, that is great news, thank you so much for confirming this for me / your response.
Always strange when making requests, using a Debian based distro, won't have access to these updates for years but at least I know they will be available in the future smile

Last edited by advice1010 (2024-06-12 22:48:44)

Offline

#4 2024-06-13 00:08:44

ToZ
Administrator
From: Canada
Registered: 2011-06-02
Posts: 11,313

Re: Super key to open up menu but also be used as a modifier key

advice1010 wrote:

Not fully needed, but I do really like the toggle just in case I want to close, do you know if the "Escape" key at least works?

Yes it does.

I don't know if it would be a GTK limitation, because I am currently using xfce-superkey and that allows for the toggle, but I don't know if that means anything.  If not a limitation though, I wonder why this was not added into XFCE, maybe I will mention.

I'm not 100% sure, but I don't think Gtk process all keys when a menu is popped up - meaning it doesn't "see" the Super key being pressed again.

...do you know if that would allow user to be able to use an option like -c for Whisker Menu to load in center of your screen?  I mentioned to the developer maybe adding that center option to Whiskers GUI settings window but no response.

Yes. The "-c" option was added to the whiskermenu in version 2.8.0.


Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki  |  Community | Contribute ---

Offline

#5 2024-06-14 00:14:13

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

It could be scripted ToZ.
Install xmacro
Then someting like this would be a start:

#!/bin/bash

SUPER=false
xmacrorec2 | while read line; do
           [[ "$line" == "KeyStrPress Super_L" ]] && SUPER=true;
           [[ "$SUPER" == "true" ]] && [[ "$line" != "KeyStrRelease Super_L" ]]  && COMBO="Super+${line/#*' '}";
           case "${COMBO}" in
                      "Super+x"|"Super+X") echo "Start your app here for Super+x";;
                      "Super+1")           echo "Start your app here for Super+1";;
                      *) ;;  # do nothing
           esac
           [[ "$line" == "KeyStrRelease Super_L" ]] && SUPER=false;
           echo "$SUPER";
done

You may want to set -k option so that xmacrorec2 doesn't require input for quit key.
More on that here:
https://askubuntu.com/questions/153316/ … er-i-click


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#6 2024-06-14 19:02:53

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

I'm not really satisfied with xmacrorec2's stability. however it was fun making a dialog for setting and editing configuration file.
This has two pieces, one is a daemon that reads $HOME/.super_key_commands configuration file.
Configuration file is created and managed by super_combo.sh, you can add, edit and delete key combos.
Daemon needs to be restarted manually after making changes.

super_combo_daemon.sh

#!/bin/bash

# Configuration file to store key combinations and commands
CONFIG_FILE="$HOME/.super_key_commands"

# Load the key combinations and commands from the configuration file
declare -A commands
if [ -f "$CONFIG_FILE" ]; then
    while IFS='|' read -r combo command; do
        commands["$combo"]="$command"
    done < "$CONFIG_FILE"
fi

# Function to execute commands
execute_command() {
    local command="$1"
    # Check if the command has options
    if [[ "$command" == *' '* ]]; then
        # Command has options, split into command and arguments
        local cmd="${command%% *}"     # Extract the command (before the first space)
        local args="${command#* }"    # Extract the arguments (after the first space)
        # Execute command with its arguments
        "$cmd" $args
    else
        # Command does not have options, execute as is
        "$command"
    fi
}

SUPER="FALSE"
while :; do
    xmacrorec2 -k 93 2>/dev/null | while read -r line ; do
        # Check for key press event
        if [[ "$line" =~ ^KeyStrPress.?Super_L ]] || [[ "$line" =~ ^KeyStrPress.?Super_R ]]; then
            SUPER="TRUE"
        fi
        if [[ "$SUPER" == "TRUE" ]] && [[ "$line" =~ ^KeyStrPress.* ]]; then
               COMBO="Super+${line/#*' '}"
               if [[ -n "${commands[$COMBO]}" ]]; then
                   execute_command "${commands[$COMBO]}" &
                   SUPER="FALSE"
               fi
               COMBO=""
        fi
        if [[ "$SUPER" == "TRUE" ]] && [[ "$line" =~ ^KeyStrRelease.* ]]; then
           if [[ "$line" =~ ^KeyStrRelease.?Super_L ]] || [[ "$line" =~ ^KeyStrRelease.?Super_R ]]; then
                   COMBO="Super"
                   if [[ -n "${commands[$COMBO]}" ]] ; then
                       execute_command "${commands[$COMBO]}" &
                   fi
                   COMBO=""
           fi
           SUPER="FALSE"
        fi
    done
    #yad --text="xmacrorec2 is restarting" --no-buttons --timeout=3 &
done

super_combo.sh

#!/bin/bash

# Check if yad is installed
if ! hash yad 2>/dev/null; then
    echo "yad could not be found. Please install it first."
    exit 1
fi

# Configuration file to store key combinations and commands
CONFIG_FILE="$HOME/.super_key_commands"

# Ensure configuration file exists
if [ ! -f "$CONFIG_FILE" ]; then
    touch "$CONFIG_FILE"
fi

# Help text embedded in the script
HELP_TEXT="
=== Super Key Combo Manager Help ===

Welcome to the Super Key Combo Manager!

This tool allows you to associate commands with key combinations
using the Super key (often represented as 'Win' key).

Here's how to use the manager:

1. **Add a New Key Combo:**
   - Click on 'Add' to create a new key combo.
   - Enter your desired key combination in the 'Key Combo' field.
     - Example: Super+x, Super+1, Super+Shift+s
   - Enter the command you want to execute when
     the key combo is pressed in the 'Command' field.
     - Example: gedit, gnome-terminal, /usr/bin/firefox
   - Click 'OK' to save.

2. **Edit an Existing Key Combo:**
   - Click on 'Edit' to modify an existing key combo.
   - Select the key combo from the list.
   - Modify the 'Key Combo' or 'Command' fields as needed.
   - Click 'OK' to save the changes.

3. **Delete a Key Combo:**
   - Click on 'Delete' to remove an existing key combo.
   - Select the key combo from the list.
   - Click 'OK' to confirm deletion.

Tips:
- Ensure your key combo starts with 'Super+'
  followed by a valid key identifier.
- Commands should be executable programs or scripts.
- Avoid using the '|' character in commands,
  as it's reserved for separating fields in the manager.

Enjoy managing your Super key combos with ease!
"

# Function to display help text
show_help() {
   printf "%s\n" "$HELP_TEXT" | yad --text-info --title="Super Key Combo Manager Help" --width=600 --height=400
}

# Function to validate if a command is executable
is_executable() {
    local cmd_with_args="$1"
    local cmd
    # Extract the command part (before the first space)
    cmd="${cmd_with_args%% *}"
    
    # Check if the extracted command is executable and does not contain "|"
    if [[ "$cmd" != *'|'* ]] && command -v "$cmd" >/dev/null 2>&1; then
        return 0  # Command is executable
    else
        return 1  # Command is not executable
    fi
}

# Function to validate key combo structure
validate_key_combo() {
    local combo="$1"
    # Check if the key combo starts with "Super+" and ends with a valid key identifier (or is just "Super")
    [[ "$combo" == "Super" || "$combo" =~ ^Super\+[a-zA-Z0-9]+$ ]]
}

# Function to check if key combo already exists
combo_exists() {
    local check_combo="$1"
    while IFS='|' read -r combo _; do
        if [ "$combo" == "$check_combo" ]; then
            return 0  # Combo exists
        fi
    done < "$CONFIG_FILE"
    return 1  # Combo does not exist
}

# Function to add a new key combo and command entry
add_entry() {
    local combo_command
    local key_combo
    local command

    combo_command=$(yad --form --title="Add Super Key Combo" --field="Key Combo" --field="Command")
    if [ $? -eq 0 ]; then
        IFS='|' read -r key_combo command <<< "$combo_command"
        
        # Validate key combo structure
        if ! validate_key_combo "$key_combo"; then
            yad --error --text="Invalid key combo format. Please use 'Super' or 'Super+&lt;key&gt;'."
            return
        fi
        
        # Check if command is empty (and not for Super alone)
        if [ -z "$command" ] && [ "$key_combo" != "Super" ]; then
            yad --error --text="Command cannot be empty."
            return
        fi
        
        # Check if command contains "|" (and not for Super alone)
        if [[ "$command" == *'|'* ]] && [ "$key_combo" != "Super" ]; then
            yad --error --text="Command cannot contain the '|' character."
            return
        fi
        
        # Check if command is executable (including with options, only if command is provided)
        if [ -n "$command" ] && ! is_executable "$command"; then
            yad --error --text="Command '$command' is not executable or does not exist."
            return
        fi
        
        # Check if the key combo already exists
        if combo_exists "$key_combo"; then
            yad --error --text="Key combo '$key_combo' already exists. Please use a unique key combo."
            return
        fi
        
        # Add the new entry to the config file
        echo "$key_combo|$command" >> "$CONFIG_FILE"
    fi
}

# Function to edit existing key combo and command entries
edit_entry() {
    local entries=()
    while IFS='|' read -r combo cmd; do
        entries+=("$combo" "$cmd")
    done < "$CONFIG_FILE"
    
    local selected_entry=$(yad --list --height=500 --title="Edit Key Combos" --column="Key Combo" --column="Command" "${entries[@]}")
    if [ $? -eq 0 ]; then
        if [ -n "$selected_entry" ]; then
            local old_combo
            IFS='|' read -r old_combo old_cmd <<< "$selected_entry"
            
            local new_combo_command
            new_combo_command=$(yad --form --title="Edit Super Key Combo" \
                                --field="Key Combo" "$old_combo" \
                                --field="Command" "$old_cmd")
            
            if [ $? -eq 0 ]; then
                IFS='|' read -r new_combo new_cmd <<< "$new_combo_command"
                
                # Validate key combo structure
                if ! validate_key_combo "$new_combo"; then
                    yad --error --text="Invalid key combo format. Please use 'Super' or 'Super+&lt;key&gt;'."
                    return
                fi
                
                # Check if command is empty (and not for Super alone)
                if [ -z "$new_cmd" ] && [ "$new_combo" != "Super" ]; then
                    yad --error --text="Command cannot be empty."
                    return
                fi
                
                # Check if command contains "|" (and not for Super alone)
                if [[ "$new_cmd" == *'|'* ]] && [ "$new_combo" != "Super" ]; then
                    yad --error --text="Command cannot contain the '|' character."
                    return
                fi
                
                # Check if command is executable (including with options, only if command is provided)
                if [ -n "$new_cmd" ] && ! is_executable "$new_cmd"; then
                    yad --error --text="Command '$new_cmd' is not executable or does not exist."
                    return
                fi
                
                # Check if the new key combo already exists (and is not the old combo)
                if [ "$new_combo" != "$old_combo" ] && combo_exists "$new_combo"; then
                    yad --error --text="Key combo '$new_combo' already exists. Please use a unique key combo."
                    return
                fi
                
                # Create a temporary file to hold updated entries
                local temp_file=$(mktemp)
                
                # Update the entries in the temporary file
                while IFS='|' read -r combo cmd; do
                    if [ "$combo" == "$old_combo" ] && [ "$cmd" == "$old_cmd" ]; then
                        echo "$new_combo|$new_cmd" >> "$temp_file"
                    else
                        echo "$combo|$cmd" >> "$temp_file"
                    fi
                done < "$CONFIG_FILE"
                
                # Replace the original config file with the updated temp file
                mv "$temp_file" "$CONFIG_FILE"
            fi
        fi
    fi
}

# Function to delete key combinations and commands
delete_entry() {
    local entries=()
    while IFS='|' read -r combo cmd; do
        entries+=("$combo" "$cmd")
    done < "$CONFIG_FILE"
    
    local selected_entry=$(yad --list --height=500 --title="Delete Key Combos" --column="Key Combo" --column="Command" --separator="!" "${entries[@]}")
    if [ $? -eq 0 ]; then
        if [ -n "$selected_entry" ]; then
            # Create new temp file without the selected entries
            local temp_file=$(mktemp)
            IFS='!' read -ra selected_parts <<< "$selected_entry"
            for ((i = 0; i < ${#selected_parts[@]}; i += 2)); do
                local selected_combo="${selected_parts[i]}"
                local selected_cmd="${selected_parts[i+1]}"
                while IFS='|' read -r combo cmd; do
                    if [ "$combo" != "$selected_combo" ] || [ "$cmd" != "$selected_cmd" ]; then
                        echo "$combo|$cmd" >> "$temp_file"
                    fi
                done < "$CONFIG_FILE"
            done
            mv "$temp_file" "$CONFIG_FILE"
        fi
    fi
}

# Loop to show the configuration menu repeatedly
while :; do
    action=$(yad --list --title="Super Key Combo Manager" --column="Action" "Add" "Edit" "Delete" "Help" --height=300)
    if [ $? -ne 0 ]; then
        break
    fi
    case $action in
        "Add|") add_entry;;
        "Edit|") edit_entry;;
        "Delete|") delete_entry;;
        "Help|") show_help;;
    esac
done

The problem with this approach is in fact that if you press Super+1 for example 1 will be typed in text editor if you have it open.


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#7 2024-06-16 02:23:46

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

xmacrorec2 was too heavy on resources.

I have a better solution, made an app in C that grabs super keys from the root window.
and prints out the key combinations when pressed with other keys.
Super or Super+<key>
It gets key map from xmodmap.
It doesn't interfere with other keys and key combos.

/* gcc -o super_combo_daemon super_combo_daemon.c -lX11 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/XKBlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h> 
#include <errno.h> 


#define MAX_LINE_LENGTH 256
#define MAX_KEYBOARD_IDS 10
#define LOCKFILE "/tmp/super_combo_daemon.lock"

int lockfile_fd = -1;

// Function to get the keycode map
void get_keycode_map(Display *display, char keycode_map[256][MAX_LINE_LENGTH]) {
    char command[] = "xmodmap -pke";
    FILE *fp;
    char path[MAX_LINE_LENGTH];
    int keycode;
    char keyname[MAX_LINE_LENGTH];

    fp = popen(command, "r");
    if (fp == NULL) {
        perror("popen");
        exit(EXIT_FAILURE);
    }

    while (fgets(path, sizeof(path), fp) != NULL) {
        if (sscanf(path, "keycode %d = %s", &keycode, keyname) == 2) {
            strcpy(keycode_map[keycode], keyname);
        }
    }

    pclose(fp);
}

// Function to handle key events
void handle_key_events(Display *display, Window root, char keycode_map[256][MAX_LINE_LENGTH]) {
    XEvent event;
    int super_pressed = 0;
    KeyCode super_l_keycode = XKeysymToKeycode(display, XStringToKeysym("Super_L"));
    KeyCode super_r_keycode = XKeysymToKeycode(display, XStringToKeysym("Super_R"));
    while (1) {
        XNextEvent(display, &event);
        if (event.type == KeyPress) {
            KeySym keysym = XkbKeycodeToKeysym(display, event.xkey.keycode, 0, 0);
            if (keysym == XK_Super_L || keysym == XK_Super_R) {
                super_pressed = 1;
            } else if (super_pressed) {
                char combo_key[MAX_LINE_LENGTH];
                snprintf(combo_key, sizeof(combo_key), "Super+%s", XKeysymToString(keysym));
                printf("%s\n", combo_key);
                fflush(stdout);
                super_pressed = 0;
            }
        } else if (event.type == KeyRelease) {
            KeySym keysym = XkbKeycodeToKeysym(display, event.xkey.keycode, 0, 0);
            if (keysym == XK_Super_L || keysym == XK_Super_R) {
                if (super_pressed) {
                    printf("Super\n");
                    fflush(stdout);
                    super_pressed = 0;
                }
            }
        }
    }
}

void signal_handler(int signo) {
    if (signo == SIGINT || signo == SIGTERM) {
        printf("Received signal %d. Cleaning up...\n", signo);
        // Release resources like lockfile and pidfile
        if (lockfile_fd != -1) {
            close(lockfile_fd);
            unlink(LOCKFILE); // Remove the lockfile upon exit
        }
        exit(EXIT_SUCCESS);
    }
}

int acquire_lockfile() {
    // Check if there's a stale lockfile
    struct stat lockfile_stat;
    if (stat(LOCKFILE, &lockfile_stat) == 0) {
        // Lockfile exists, check if it's a stale lock
        FILE *lockfile = fopen(LOCKFILE, "r");
        if (lockfile) {
            pid_t pid;
            fscanf(lockfile, "%d", &pid);
            fclose(lockfile);
            if (pid > 0 && (kill(pid, 0) == 0 || errno == EPERM)) {
                // Process with PID is still running or access denied
                fprintf(stderr, "Another instance is already running with PID %d\n", pid);
                return 0; // Lockfile is not stale, another instance is running
            } else {
                // Process with PID is not running, remove stale lockfile
                fprintf(stderr, "Removing stale lock file.\n");
                unlink(LOCKFILE);
            }
        } else {
            perror("Failed to open lockfile");
            return -1;
        }
    }

    // Create new lockfile and write current process ID
    lockfile_fd = open(LOCKFILE, O_CREAT | O_EXCL | O_WRONLY, 0644);
    if (lockfile_fd == -1) {
        if (errno == EEXIST) {
            perror("Another instance is already running");
            return 0; // Lockfile already exists
        } else {
            perror("Failed to create lockfile");
            return -1;
        }
    }

    // Write current process ID to lockfile
    dprintf(lockfile_fd, "%d\n", getpid());

    return 1; // Lockfile created successfully
}
int main() {
    signal(SIGINT, signal_handler);
    signal(SIGTERM, signal_handler);

    // Try to acquire the lockfile
    if (acquire_lockfile() <= 0) {
        exit(EXIT_FAILURE);
    }

    Display *display;
    Window root;
    char keycode_map[256][MAX_LINE_LENGTH] = {0};

    display = XOpenDisplay(NULL);
    if (display == NULL) {
        fprintf(stderr, "Cannot open display\n");
        exit(EXIT_FAILURE);
    }

    root = DefaultRootWindow(display);

    // Get the keycode map
    get_keycode_map(display, keycode_map);

    // Grab the Super_L key
    KeyCode super_l_keycode = XKeysymToKeycode(display, XStringToKeysym("Super_L"));
    XGrabKey(display, super_l_keycode, AnyModifier, root, True, GrabModeAsync, GrabModeAsync);
    XGrabKey(display, super_l_keycode, Mod2Mask, root, True, GrabModeAsync, GrabModeAsync);

    // Grab the Super_R key
    KeyCode super_r_keycode = XKeysymToKeycode(display, XStringToKeysym("Super_R"));
    XGrabKey(display, super_r_keycode, AnyModifier, root, True, GrabModeAsync, GrabModeAsync);
    XGrabKey(display, super_r_keycode, Mod2Mask, root, True, GrabModeAsync, GrabModeAsync);

    // Handle key events
    handle_key_events(display, root, keycode_map);

    XCloseDisplay(display);

    return 0;
}

Requires X11 dev lib, libX11-dev on debian.
Compiles with

gcc -o super_combo_daemon super_combo_daemon.c -lX11

Runs with:

./super_combo_daemon

Should output Super on Super_L and Super_R
And Super+s,Super+c ...
It will always open only one instance and uses a lockfile.
So far It's light on resources.


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#8 2024-06-16 10:06:17

ToZ
Administrator
From: Canada
Registered: 2011-06-02
Posts: 11,313

Re: Super key to open up menu but also be used as a modifier key

This is interesting, thanks Misko. Isn't it funny how a something starts out as a simple script suddenly becomes a full-blown C program? Next step is a kernel hack.

I haven't had the chance to try this, but does this code allow you to use the super key as a toggle? Click once to display menu (via xfce4-popup-whiskermenu) and the next press to close the menu?


Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki  |  Community | Contribute ---

Offline

#9 2024-06-18 08:41:59

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

ToZ wrote:

This is interesting, thanks Misko. Isn't it funny how a something starts out as a simple script suddenly becomes a full-blown C program? Next step is a kernel hack.

I haven't had the chance to try this, but does this code allow you to use the super key as a toggle? Click once to display menu (via xfce4-popup-whiskermenu) and the next press to close the menu?

No this was grabbing all the super keys which was not a good idea because applications can't use the super key combos.

But I managed to make it launch with a different method.
Had to start all over again.
To run whisker menu on super key release without interfering with other key combos I had to resort to X11's record extension.
You can check if your X11 supports this extension with.

xdpyinfo | grep RECORD

App checks if other key is pressed so it doesn't interfere with other key combos.

Along the way, I found out that /usr/bin/xfce4-popup-whiskermenu isn't a very reliable method in launching whisker menu, sometimes it fails.
At least the Whisker menu 2.7.2 I'm running.
So I had to make sure it waits for some time and repeat it in case Whisker menu's window id isn't found.
To close it I resorted to giving focus to the root window.
It was either that or sending Escape key to the whisker menu's window and the first one was easier. And I like easy ways. big_smile

I also found out that whisker menu, when shown, creates a child window of it's own.
That's why not only there is a need to loop through root's children windows but also children of the children to find the window.

Whisker menu's window has these properties:
wm_class="wrapper-2.0"
wm_icon_name="Whisker Menu"
net_wm_icon_name="Whisker Menu"
wm_name="Whisker Menu"
net_wm_name="Whisker Menu"
So if a window has a all of these Xwindow properties it is Whisker menu.
I assume wrapper-2.0 WM_CLASS is set by the panel plugin.

So this needs to run as a daemon.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <getopt.h>
#include <pthread.h>
#include <unistd.h>
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
#include <X11/Xlibint.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/extensions/record.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>

/* gcc -o whisker_launcher whisker_launcher.c -lX11 -lXtst -lXi -pthread */

/* for this struct, refer to libxnee */
typedef union {
    unsigned char    type ;
    xEvent           event ;
    xResourceReq     req   ;
    xGenericReply    reply ;
    xError           error ;
    xConnSetupPrefix setup;
} XRecordDatum;

// Data structure to track key presses
typedef struct {
    int key_code;
    int pressed; // 1 if key is pressed, 0 if released
} KeyState;

KeyState super_l_state = {0, 0};
KeyState super_r_state = {0, 0};

// Global variables for displays and XRecordContext
Display *d = NULL;
Display *d_control = NULL;
XRecordContext context = 0;

static int super_l_pressed = 0;
static int super_r_pressed = 0;

/* Function declarations */
void log_message(const char *message);
int check_window_property(Window window, Atom property, const char *value);
Window find_whiskermenu_window_recursive(Window window, Atom wm_class, Atom wm_icon_name,
                                         Atom net_wm_icon_name, Atom wm_name, Atom net_wm_name);
Window find_whiskermenu_window();
int is_window_hidden(Window window);
void run_whiskermenu_and_wait();
void *manage_whiskermenu(void *data);
void event_callback(XPointer priv, XRecordInterceptData *hook);
void cleanup(void);
XRecordRange* create_record_range(void);
void setup_record_context(void);

// Function to log messages to stderr
void log_message(const char *message) {
    fprintf(stderr, "%s\n", message);
}

// Function to check if a window has the specified property with a specific value
int check_window_property(Window window, Atom property, const char *value) {
    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytes_after;
    unsigned char *prop;
    int result = 0;

    if (XGetWindowProperty(d, window, property, 0, 1024, False, AnyPropertyType,
                           &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
        if (prop) {
            if (strcmp((char *)prop, value) == 0) {
                result = 1;
            }
            XFree(prop);
        }
    }

    return result;
}

// Function to recursively search for the Whisker Menu window in the window's children
Window find_whiskermenu_window_recursive(Window window, Atom wm_class, Atom wm_icon_name, Atom net_wm_icon_name, Atom wm_name, Atom net_wm_name) {
    // Check if the current window is the Whisker Menu window
    if (check_window_property(window, wm_class, "wrapper-2.0") &&
        check_window_property(window, wm_icon_name, "Whisker Menu") &&
        check_window_property(window, net_wm_icon_name, "Whisker Menu") &&
        check_window_property(window, wm_name, "Whisker Menu") &&
        check_window_property(window, net_wm_name, "Whisker Menu")) {
        return window;  // Found the Whisker Menu window
    }

    // Recursively search for the Whisker Menu window in the window's children
    Window parent;
    Window *children = NULL;
    unsigned int nchildren;
    if (XQueryTree(d, window, &window, &parent, &children, &nchildren) != 0) {
        for (unsigned int i = 0; i < nchildren; i++) {
            Window whisker_menu_id = find_whiskermenu_window_recursive(children[i], wm_class, wm_icon_name, net_wm_icon_name, wm_name, net_wm_name);
            if (whisker_menu_id != 0) {
                XFree(children);  // Free the memory allocated by XQueryTree
                return whisker_menu_id;  // Found the Whisker Menu window
            }
        }
        XFree(children);  // Free the memory allocated by XQueryTree
    }

    return 0;  // Not found
}

// Function to find the Whisker Menu window
Window find_whiskermenu_window() {
    Window root = DefaultRootWindow(d);
    Window parent;
    Window *children = NULL;
    unsigned int nchildren;
    Atom wm_class = XInternAtom(d, "WM_CLASS", False);
    Atom wm_icon_name = XInternAtom(d, "WM_ICON_NAME", False);
    Atom net_wm_icon_name = XInternAtom(d, "_NET_WM_ICON_NAME", False);
    Atom wm_name = XInternAtom(d, "WM_NAME", False);
    Atom net_wm_name = XInternAtom(d, "_NET_WM_NAME", False);

    // Query the tree to get all windows
    if (XQueryTree(d, root, &root, &parent, &children, &nchildren) != 0) {
        // Recursively search for the Whisker Menu window
        Window whisker_menu_id = 0;
        for (unsigned int i = 0; i < nchildren; i++) {
            whisker_menu_id = find_whiskermenu_window_recursive(children[i], wm_class, wm_icon_name, net_wm_icon_name, wm_name, net_wm_name);
            if (whisker_menu_id != 0) {
                break;  // Found the Whisker Menu window, exit loop
            }
        }
        XFree(children);  // Free the memory allocated by XQueryTree
        return whisker_menu_id;
    } else {
        fprintf(stderr, "XQueryTree failed\n");
        return 0;
    }
}

// Function to check if the window is hidden
int is_window_hidden(Window window) {
    Atom wm_state = XInternAtom(d, "WM_STATE", False);
    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytes_after;
    unsigned char *prop;
    int is_hidden = 0;

    if (XGetWindowProperty(d, window, wm_state, 0, 1, False, wm_state,
                           &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
        if (nitems > 0 && actual_format == 32 && prop != NULL) {
            long state = *(long *)prop;
            is_hidden = (state == WithdrawnState);
        }
        XFree(prop);
    }
    return is_hidden;
}

// Function to run xfce4-popup-whiskermenu and wait for it to show up
void run_whiskermenu_and_wait() {
    int max_attempts = 20;
    int sleep_duration = 10000; // 10 milliseconds

    system("/usr/bin/xfce4-popup-whiskermenu");
    for (int attempt = 0; attempt < max_attempts; attempt++) {
        usleep(sleep_duration);
        Window whisker_menu_id = find_whiskermenu_window();
        if (whisker_menu_id != 0 && !is_window_hidden(whisker_menu_id)) {
            log_message("Whisker Menu is now visible");
            return;
        }
    }
    // Failed to find or show Whisker Menu after running xfce4-popup-whiskermenu"
    system("/usr/bin/xfce4-popup-whiskermenu");  // Last attempt
}

/* Manage Whisker Menu window */
void *manage_whiskermenu(void *data) {
    // Find the Whisker Menu window
    Window whisker_menu_id = find_whiskermenu_window();
    if (whisker_menu_id != 0) {
        if (is_window_hidden(whisker_menu_id)) {
            run_whiskermenu_and_wait();
        } else {
            // Focus the root window and give it focus so that Whisker Menu loses focus
            Window root_window = RootWindow(d, DefaultScreen(d));
            XSetInputFocus(d, root_window, RevertToPointerRoot, CurrentTime);
        }
    } else {
        // Whisker Menu window ID not found, launching Whisker Menu...
        run_whiskermenu_and_wait();
    }
    return NULL;
}

/* Callback function for XRecordContext */
void event_callback(XPointer priv, XRecordInterceptData *hook) {
    /* log_message("Event callback triggered"); */

    if (hook->category != XRecordFromServer) {
        XRecordFreeData(hook);
        return;
    }

    XRecordDatum *data = (XRecordDatum*) hook->data;
    int event_type = data->type;
    BYTE keycode = data->event.u.u.detail;

    if (event_type == KeyPress || event_type == KeyRelease) {
        KeySym keysym = XkbKeycodeToKeysym(d, keycode, 0, 0);

        /*char buffer[256];
        snprintf(buffer, sizeof(buffer), "Key event: type=%d, keycode=%u, keysym=%lu",
                 data->event.u.u.type, keycode, keysym);
        log_message(buffer); */

        if (keysym == XK_Super_L) {
            if (event_type == KeyPress) {
                super_l_state.key_code = keycode;
                super_l_state.pressed = 1;
            } else if (event_type == KeyRelease) {
                if (super_l_state.pressed && super_l_state.key_code == keycode) {
                    log_message("Super_L key released (after press)");
                    pthread_t thread_id;
                    pthread_create(&thread_id, NULL, manage_whiskermenu, NULL);
                    pthread_detach(thread_id); // Automatically reclaim thread resources
                }
            }
        } else if (keysym == XK_Super_R) {
            if (event_type == KeyPress) {
                super_r_state.key_code = keycode;
                super_r_state.pressed = 1;
            } else if (event_type == KeyRelease) {
                if (super_r_state.pressed && super_r_state.key_code == keycode) {
                    log_message("Super_R key released (after press)");
                    pthread_t thread_id;
                    pthread_create(&thread_id, NULL, manage_whiskermenu, NULL);
                    pthread_detach(thread_id); // Automatically reclaim thread resources
                }
            }
        } else {
            printf("Other key pressed\n");
            super_l_state.pressed = 0;
            super_r_state.pressed = 0;
            super_l_state.key_code = keycode;
            super_r_state.key_code = keycode;

        }
    } /*else {
        char buffer[256];
        snprintf(buffer, sizeof(buffer), "Non-key event: type=%d", data->type);
        log_message(buffer);
    }*/

    XRecordFreeData(hook);
}

void cleanup(void) {
    log_message("Cleaning up resources...");
    if (context) {
        XRecordDisableContext(d_control, context);
        XRecordFreeContext(d_control, context);
        context = 0;
    }
    if (d_control) {
        XCloseDisplay(d_control);
        d_control = NULL;
    }
    if (d) {
        XCloseDisplay(d);
        d = NULL;
    }
    log_message("Done...");
}

/* Create an XRecordRange for recording key events */
XRecordRange* create_record_range(void) {
    XRecordRange *range = XRecordAllocRange();
    if (!range) {
        log_message("Unable to allocate XRecordRange");
        cleanup();
        exit(1);
    }
    range->device_events.first = KeyPress;
    range->device_events.last = KeyRelease;
    return range;
}

/* Set up the XRecord context for capturing key events */
void setup_record_context(void) {
    XRecordClientSpec clients = XRecordAllClients;
    XRecordRange *range = create_record_range();
    context = XRecordCreateContext(d_control, 0, &clients, 1, &range, 1);

    if (!context) {
        log_message("Unable to create XRecordContext");
        cleanup();
        exit(1);
    } else {
        log_message("XRecordContext created successfully");
    }

    if (!XRecordEnableContextAsync(d_control, context, event_callback, NULL)) {
        log_message("Unable to enable XRecordContext");
        cleanup();
        exit(1);
    } else {
        log_message("XRecordContext enabled successfully");
    }

    XFree(range);
}

/* Main function */
int main(int argc, char **argv) {
    log_message("Starting Super key monitor...");

    d = XOpenDisplay(NULL);
    if (d == NULL) {
        log_message("Cannot open display");
        exit(1);
    }

    d_control = XOpenDisplay(NULL);
    if (d_control == NULL) {
        log_message("Cannot open control display");
        exit(1);
    }

    XSynchronize(d, True); // Ensure synchronized operation on display
    XSynchronize(d_control, True); // Ensure synchronized operation on control display

    setup_record_context();

    while (1) {
        if (XPending(d_control)) {
            XRecordProcessReplies(d_control);
        }
        usleep(100000);  // Sleep for 100 milliseconds to reduce CPU usage
    }

    cleanup();
    return 0;
}

Development libraries needed:
libX11, libXtst, libXi
compiles with:

gcc -o whisker_launcher whisker_launcher.c -lX11 -lXtst -lXi -pthread

When running press Super_L or Super_R.
On super key release it will launch or close whisker menu.
Will not interfere with Super_L+e.

P.S. It won't work on Wayland only X11.

Last edited by Misko_2083 (2024-06-18 09:09:17)


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#10 2024-06-18 10:36:22

ToZ
Administrator
From: Canada
Registered: 2011-06-02
Posts: 11,313

Re: Super key to open up menu but also be used as a modifier key

This works perfectly - thanks.

Can this be adapted to work with any other 3 menu apps available is Xfce: whisker, applications menu, and application finder? Something like:
- if whiskermenu is present on the panel, use it
- or if applicationsmenu is present on panel, use it
- else popup the application finder (not in collapsed mode)


Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki  |  Community | Contribute ---

Offline

#11 2024-06-18 20:59:02

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

Yes it can be done.
If I can find a way to identify which window is applicationsmenu when it opens and it's XWindow properties.
What's the command to run applicationsmenu from the terminal?
OK found it /usr/bin/xfce4-popup-applicationsmenu

Last edited by Misko_2083 (2024-06-18 21:05:35)


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#12 2024-06-19 20:15:43

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

ToZ wrote:

- if whiskermenu is present on the panel, use it
- or if applicationsmenu is present on panel, use it
- else popup the application finder (not in collapsed mode)

Here:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <getopt.h>
#include <glib.h>
#include <pthread.h>
#include <regex.h>
#include <unistd.h>
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
#include <X11/Xlibint.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/extensions/record.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <xfconf/xfconf.h>

#define WHISKER "/usr/bin/xfce4-popup-whiskermenu"
#define APPLICATIONS "/usr/bin/xfce4-popup-applicationsmenu"
#define FINDER "/usr/bin/xfce4-appfinder"
#define QUEUE_SIZE 10

/* gcc -o whisker_launcher whisker_launcher.c `pkg-config --cflags libxfconf-0` -lX11 -lXtst -lXi  -lxfconf-0 -lglib-2.0 -pthread */

typedef struct {
    void (*function)(void*);
    void *argument;
} task_t;

typedef struct {
    pthread_mutex_t lock;
    pthread_cond_t cond;
    pthread_t thread;
    task_t task_queue[QUEUE_SIZE];
    int head;
    int tail;
    int count;
    int shutdown;
} single_thread_pool_t;

/* for this struct, refer to libxnee */
typedef union {
    unsigned char    type ;
    xEvent           event ;
    xResourceReq     req   ;
    xGenericReply    reply ;
    xError           error ;
    xConnSetupPrefix setup;
} XRecordDatum;

// Data structure to track key presses
typedef struct {
    int key_code;
    int pressed; // 1 if key is pressed, 0 if released
} KeyState;

pthread_mutex_t lock;
single_thread_pool_t pool;

KeyState super_l_state = {0, 0};
KeyState super_r_state = {0, 0};

// Global variables for displays and XRecordContext
Display *d = NULL;
Display *d_control = NULL;
XRecordContext context = 0;
char *menu = NULL;
static int super_l_pressed = 0;
static int super_r_pressed = 0;
static int keep_runing = 1;

/* Function declarations */
void log_message(const char *message);
int check_window_property(Window window, Atom property, const char *value);
Window find_window_recursive(Window window, Atom wm_class, Atom wm_icon_name,
                                         Atom net_wm_icon_name, Atom wm_name, Atom net_wm_name);
/* Function declarations */
void log_message(const char *message);
int check_window_property(Window window, Atom property, const char *value);
Window find_window_recursive(Window window, Atom wm_class, Atom wm_icon_name,
                                         Atom net_wm_icon_name, Atom wm_name, Atom net_wm_name);
Window find_window();
int is_window_hidden(Window window);
void run_menu_and_wait();
gboolean is_plugin_key(const char *key);
void send_key(KeySym keysym);
char *find_menus();
void manage_menu(void *data);
void event_callback(XPointer priv, XRecordInterceptData *hook);
void cleanup(void);
XRecordRange* create_record_range(void);
void setup_record_context(void);

void* thread_worker(void *arg) {
    while (1) {
        pthread_mutex_lock(&pool.lock);
        while (pool.count == 0 && !pool.shutdown) {
            pthread_cond_wait(&pool.cond, &pool.lock);
        }
        if (pool.shutdown) {
            pthread_mutex_unlock(&pool.lock);
            pthread_exit(NULL);
        }
        task_t task = pool.task_queue[pool.head];
        pool.head = (pool.head + 1) % QUEUE_SIZE;
        pool.count--;
        pthread_mutex_unlock(&pool.lock);

        if (task.function != NULL) {
            task.function(task.argument);
        }
    }
}

void single_thread_pool_init() {
    pthread_mutex_init(&pool.lock, NULL);
    pthread_cond_init(&pool.cond, NULL);
    pool.head = 0;
    pool.tail = 0;
    pool.count = 0;
    pool.shutdown = 0;
    pthread_create(&pool.thread, NULL, thread_worker, NULL);
    printf("Single-thread pool initialized.\n");
}

void single_thread_pool_add_task(void (*function)(void*), void *argument) {
    if (pthread_mutex_trylock(&lock) == 0) {
        pthread_mutex_unlock(&lock);
        pthread_mutex_lock(&pool.lock);
        if (pool.count < QUEUE_SIZE) {
            pool.task_queue[pool.tail].function = function;
            pool.task_queue[pool.tail].argument = argument;
            pool.tail = (pool.tail + 1) % QUEUE_SIZE;
            pool.count++;
            pthread_cond_signal(&pool.cond);
        } else {
            printf("Task queue is full. Task not added.\n");
        }
        pthread_mutex_unlock(&pool.lock);
    } else {
        printf("Task is already running. Task not added.\n");
    }
}

void single_thread_pool_destroy() {
    pthread_mutex_lock(&pool.lock);
    pool.shutdown = 1;
    pthread_cond_broadcast(&pool.cond);
    pthread_mutex_unlock(&pool.lock);
    pthread_join(pool.thread, NULL);
    pthread_mutex_destroy(&pool.lock);
    pthread_cond_destroy(&pool.cond);
    printf("Single-thread pool destroyed.\n");
}

// Function to log messages to stderr
void log_message(const char *message) {
    fprintf(stderr, "%s\n", message);
}

// Function to check if a window has the specified property with a specific value
int check_window_property(Window window, Atom property, const char *value) {
    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytes_after;
    unsigned char *prop;
    int result = 0;

    if (XGetWindowProperty(d, window, property, 0, 1024, False, AnyPropertyType,
                           &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
        if (actual_type != None) {
         if (prop) {
            if (strcmp((char *)prop, value) == 0) {
                result = 1;
            }
            XFree(prop);
        }
       }
    }
    return result;
}

// Function to recursively search for the Menu window in the window's children
Window find_window_recursive(Window window, Atom wm_class, Atom wm_icon_name, Atom net_wm_icon_name, Atom wm_name, Atom net_wm_name) {
    // Check if the current window is the Menu window
    if (strcmp(menu, "whiskermenu") == 0) {
        if (check_window_property(window, wm_class, "wrapper-2.0") &&
            check_window_property(window, wm_icon_name, "Whisker Menu") &&
            check_window_property(window, net_wm_icon_name, "Whisker Menu") &&
            check_window_property(window, wm_name, "Whisker Menu") &&
            check_window_property(window, net_wm_name, "Whisker Menu")) {
            return window;  // Found the Whisker Menu window
        }
    } else if (strcmp(menu, "finder") == 0) {
        if (check_window_property(window, wm_class, "xfce4-appfinder") &&
            check_window_property(window, wm_icon_name, "Application Finder") &&
            check_window_property(window, net_wm_icon_name, "Application Finder") &&
            check_window_property(window, wm_name, "Application Finder") &&
            check_window_property(window, net_wm_name, "Application Finder")) {
            return window;  // Found the Application Finder Menu window
        }
    } else if (strcmp(menu, "applicationsmenu") == 0) {
        if ((check_window_property(window, wm_class, "xfce4-panel") == 0) &&
            check_window_property(window, wm_icon_name, "xfce4-panel") &&
            check_window_property(window, net_wm_icon_name, "xfce4-panel") &&
            check_window_property(window, wm_name, "xfce4-panel") &&
            check_window_property(window, net_wm_name, "xfce4-panel")) {
            return window;  // Found the Applications Menu window
        }
    }

    // Recursively search for the Menu window in the window's children
    Window parent;
    Window *children = NULL;
    unsigned int nchildren;
    if (XQueryTree(d, window, &window, &parent, &children, &nchildren) != 0) {
        for (unsigned int i = 0; i < nchildren; i++) {
            Window menu_id = find_window_recursive(children[i], wm_class, wm_icon_name, net_wm_icon_name, wm_name, net_wm_name);
            if (menu_id != 0) {
                XFree(children);  // Free the memory allocated by XQueryTree
                return menu_id;  // Found the Whisker Menu window
            }
        }
        XFree(children);  // Free the memory allocated by XQueryTree
    }

    return 0;  // Not found
}

// Function to find the Whisker Menu window
Window find_window() {
    Window root = DefaultRootWindow(d);
    Window parent;
    Window *children = NULL;
    unsigned int nchildren;
    Atom wm_class = XInternAtom(d, "WM_CLASS", False);
    Atom wm_icon_name = XInternAtom(d, "WM_ICON_NAME", False);
    Atom net_wm_icon_name = XInternAtom(d, "_NET_WM_ICON_NAME", False);
    Atom wm_name = XInternAtom(d, "WM_NAME", False);
    Atom net_wm_name = XInternAtom(d, "_NET_WM_NAME", False);

    // Query the tree to get all windows
    if (XQueryTree(d, root, &root, &parent, &children, &nchildren) != 0) {
        // Recursively search for the Menu window
        Window menu_id = 0;
        for (unsigned int i = 0; i < nchildren; i++) {
            menu_id = find_window_recursive(children[i], wm_class, wm_icon_name, net_wm_icon_name, wm_name, net_wm_name);
            if (menu_id != 0) {
                break;  // Found the Whisker Menu window, exit loop
            }
        }
        XFree(children);  // Free the memory allocated by XQueryTree
        return menu_id;
    } else {
        fprintf(stderr, "XQueryTree failed\n");
        return 0;
    }
}

// Function to check if the window is hidden
int is_window_hidden(Window window) {
    Atom wm_state = XInternAtom(d, "WM_STATE", False);
    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytes_after;
    unsigned char *prop;
    int is_hidden = 0;

    if (XGetWindowProperty(d, window, wm_state, 0, 1, False, wm_state,
                           &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
        if (nitems > 0 && actual_format == 32 && prop != NULL) {
            long state = *(long *)prop;
            is_hidden = (state == WithdrawnState);
        }
        XFree(prop);
    }
    return is_hidden;
}

// Function to run xfce4-popup-whiskermenu and wait for it to show up
void run_menu_and_wait() {
    int max_attempts = 20;
    int sleep_duration = 10000; // 10 milliseconds

    if (menu == "whiskermenu") {
        system(WHISKER);
    } else if (menu == "applicationsmenu") {
        system(APPLICATIONS); 
    } else {
        system(FINDER);
    }

    for (int attempt = 0; attempt < max_attempts; attempt++) {
        usleep(sleep_duration);
        Window menu_id = find_window();
        if (menu_id != 0 && !is_window_hidden(menu_id)) {
                //printf("0x%8x\n", menu_id);
            log_message("Menu is now visible");
            return;
        }
    }
    /* Failed to find or show Whisker Menu after running xfce4-popup-whiskermenu"
    /  Tries one more time  */
    if (menu == "whiskermenu") {
        system(WHISKER);
    }
}

/* function to simulate both the key press and release events */
void send_key(KeySym keysym) {
    KeyCode keycode = XKeysymToKeycode(d, keysym);
    
    // Simulate key press
    XTestFakeKeyEvent(d, keycode, True, CurrentTime);
    XFlush(d);
    XFlush(d_control);
    
    // Simulate key release
    XTestFakeKeyEvent(d, keycode, False, CurrentTime);
    XFlush(d);
    XFlush(d_control);
}

// Function to check if the key matches the pattern "plugin-<number>"
gboolean is_plugin_key(const char *key) {
    regex_t regex;
    int ret;

    // Compile the regular expression
    ret = regcomp(&regex, "^/plugins/plugin-[0-9]+$", REG_EXTENDED);
    if (ret) {
        fprintf(stderr, "Could not compile regex\n");
        return FALSE;
    }

    // Execute the regular expression
    ret = regexec(&regex, key, 0, NULL, 0);
    regfree(&regex);

    return ret == 0;
}

// Funtion to check if whisker and applications menu are present in the panel
char *find_menus () {
    GError *error = NULL;
    GHashTable *properties = NULL;
    GHashTableIter iter;
    gboolean whisker_menu_present = FALSE;
    gboolean applications_menu_present = FALSE;
    gpointer key, value;

    // Initialize xfconf
    if (!xfconf_init(&error)) {
        fprintf(stderr, "Failed to initialize xfconf: %s\n", error->message);
        g_error_free(error);
        return NULL;
    }

    // Open the xfce4-panel channel
    XfconfChannel *channel = xfconf_channel_get("xfce4-panel");
    if (!channel) {
        fprintf(stderr, "Failed to open xfce4-panel channel\n");
        return NULL;
    }

    // Get the list of properties under /plugins
    properties = xfconf_channel_get_properties(channel, "/plugins");
    if (!properties) {
        fprintf(stderr, "Error getting properties or no properties found\n");
        return NULL;
    }

    // Iterate over the properties to check for plugin presence
    g_hash_table_iter_init(&iter, properties);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        if (is_plugin_key((char *)key)) {
            gchar *v = xfconf_channel_get_string(channel, key, NULL);
            if (v) {
                // Check for Whisker Menu
                if (g_strrstr(v, "whiskermenu") != NULL) {
                    whisker_menu_present = TRUE;
                }
                // Check for Applications Menu
                if (g_strrstr(v, "applicationsmenu") != NULL) {
                    applications_menu_present = TRUE;
                }
                g_free(v);
            }
        }
    }

    xfconf_shutdown();

    // Clean up
    if (properties) {
        g_hash_table_unref(properties);
    }

    if (whisker_menu_present) {
        return "whiskermenu";
    } else if (applications_menu_present) {
        return "applicationsmenu";
    } else {
        return NULL;
    }
}

/* Manage Menu window */
void manage_menu(void *data) {
    // Find the Menu in the xfce4-panel
    pthread_mutex_lock(&lock);
    menu = find_menus();
    if (menu == NULL) {
        menu = "finder";
    }

    // Find the Menu window
    Window menu_id = find_window();
    if (menu_id != 0) {
        if (is_window_hidden(menu_id)) {
            run_menu_and_wait();
        } else {
            // Send escape key
            if (strcmp(menu, "finder") == 0) {
                send_key(XK_Escape);
            } else {
                send_key(XK_Escape);
            }
        }
    } else {
        // Menu window ID not found, launching Menu...
        run_menu_and_wait();
    }
    menu = NULL;
    pthread_mutex_unlock(&lock);
}

/* Callback function for XRecordContext */
void event_callback(XPointer priv, XRecordInterceptData *hook) {
    /* log_message("Event callback triggered"); */

    if (hook->category != XRecordFromServer) {
        XRecordFreeData(hook);
        return;
    }

    XRecordDatum *data = (XRecordDatum*) hook->data;
    int event_type = data->type;
    BYTE keycode = data->event.u.u.detail;

    if (event_type == KeyPress || event_type == KeyRelease) {
        KeySym keysym = XkbKeycodeToKeysym(d, keycode, 0, 0);

        /*char buffer[256];
        snprintf(buffer, sizeof(buffer), "Key event: type=%d, keycode=%u, keysym=%lu",
                 data->event.u.u.type, keycode, keysym);
        log_message(buffer); */

        if (keysym == XK_Super_L) {
            if (event_type == KeyPress) {
                super_l_state.key_code = keycode;
                super_l_state.pressed = 1;
            } else if (event_type == KeyRelease) {
                if (super_l_state.pressed && super_l_state.key_code == keycode) {
                    //log_message("Super_L key released (after press)");
                    single_thread_pool_add_task(manage_menu, NULL);
                }
            }
        } else if (keysym == XK_Super_R) {
            if (event_type == KeyPress) {
                super_r_state.key_code = keycode;
                super_r_state.pressed = 1;
            } else if (event_type == KeyRelease) {
                if (super_r_state.pressed && super_r_state.key_code == keycode) {
                    //log_message("Super_R key released (after press)");
                    single_thread_pool_add_task(manage_menu, NULL);
                }
            }
        } else {
            //printf("Other key pressed\n");
            super_l_state.pressed = 0;
            super_r_state.pressed = 0;
            super_l_state.key_code = keycode;
            super_r_state.key_code = keycode;

        }
    } /*else {
        char buffer[256];
        snprintf(buffer, sizeof(buffer), "Non-key event: type=%d", data->type);
        log_message(buffer);
    }*/

    XRecordFreeData(hook);
}

void cleanup(void) {
    log_message("Cleaning up resources...");
    if (context) {
        XRecordDisableContext(d_control, context);
        XRecordFreeContext(d_control, context);
        context = 0;
    }
    log_message("D_control...");
    if (d_control) {
        XCloseDisplay(d_control);
        d_control = NULL;
    }
    if (d) {
        XCloseDisplay(d);
        d = NULL;
    }
    if (menu) {
        free(menu);
    }
    single_thread_pool_destroy();
    pthread_mutex_destroy(&lock);
    log_message("Done...");
}

/* Create an XRecordRange for recording key events */
XRecordRange* create_record_range(void) {
    XRecordRange *range = XRecordAllocRange();
    if (!range) {
        log_message("Unable to allocate XRecordRange");
        cleanup();
        exit(1);
    }
    range->device_events.first = KeyPress;
    range->device_events.last = KeyRelease;
    return range;
}

/* Set up the XRecord context for capturing key events */
void setup_record_context(void) {
    XRecordClientSpec clients = XRecordAllClients;
    XRecordRange *range = create_record_range();
    context = XRecordCreateContext(d_control, 0, &clients, 1, &range, 1);

    if (!context) {
        log_message("Unable to create XRecordContext");
        cleanup();
        exit(1);
    } else {
        log_message("XRecordContext created successfully");
    }

    if (!XRecordEnableContextAsync(d_control, context, event_callback, NULL)) {
        log_message("Unable to enable XRecordContext");
        cleanup();
        exit(1);
    } else {
        log_message("XRecordContext enabled successfully");
    }

    XFree(range);
}

/* Main function */
int main(int argc, char **argv) {
    d = XOpenDisplay(NULL);
    if (d == NULL) {
        log_message("Cannot open display");
        exit(1);
    }

    d_control = XOpenDisplay(NULL);
    if (d_control == NULL) {
        log_message("Cannot open control display");
        exit(1);
    }

    XSynchronize(d, True); // Ensure synchronized operation on display
    XSynchronize(d_control, True); // Ensure synchronized operation on control display

    setup_record_context();

    single_thread_pool_init();
    pthread_mutex_init(&lock, NULL);

    while (keep_runing) {
        if (XPending(d_control)) {
            XRecordProcessReplies(d_control);
        }
        usleep(100000);  // Sleep for 100 milliseconds to reduce CPU usage
    }

    single_thread_pool_destroy();
    pthread_mutex_destroy(&lock);

    if (menu) {
        menu = NULL;
        free(menu);
    }

    XFlush(d);
    XSync(d, True);

    if (context) {
        XRecordDisableContext(d_control, context);
        XRecordFreeContext(d_control, context);
        context = 0;
    }
    if (d_control) {
        XCloseDisplay(d_control);
        d_control = NULL;
    }
    if (d) {
        XCloseDisplay(d);
        d = NULL;
    }

    return 0;
}

Development libraries needed:
libX11, libXtst, libXi, libxfconf-0, libglib-2.0
compiles with:

gcc -o whisker_launcher whisker_launcher.c `pkg-config --cflags libxfconf-0` -lX11 -lXtst -lXi  -lxfconf-0 -lglib-2.0 -pthread

It was very tricky with applications menu since it's not a stand alone application but an actual menu from xfce4-panel.
If there is another menu open and Super key is pressed it will close it (Directory Menu, menu that opens when right-clicking panel).
If not it will open Applications Menu.

Test removing and adding menus from the panel.

I get panel plugins from xfconf lib.
It was the easiest part.
P.S. Here's a demo app that will list all panel plugins, from all the menus and check if whisker menu and applications:

#include <stdio.h>
#include <xfconf/xfconf.h>
#include <glib.h>
#include <regex.h>

/*  gcc check_xfce_plugins.c -o check_xfce_plugins `pkg-config --cflags libxfconf-0` -lxfconf-0 -lglib-2.0 */

// Function to check if the key matches the pattern "plugin-<number>"
gboolean is_plugin_key(const char *key) {
    regex_t regex;
    int ret;

    // Compile the regular expression
    ret = regcomp(&regex, "^/plugins/plugin-[0-9]+$", REG_EXTENDED);
    if (ret) {
        fprintf(stderr, "Could not compile regex\n");
        return FALSE;
    }

    // Execute the regular expression
    ret = regexec(&regex, key, 0, NULL, 0);
    regfree(&regex);

    return ret == 0;
}

int main(void) {
    GError *error = NULL;
    gboolean whisker_menu_present = FALSE;
    gboolean applications_menu_present = FALSE;
    GHashTable *properties = NULL;
    GHashTableIter iter;
    gpointer key, value;

    // Initialize xfconf
    if (!xfconf_init(&error)) {
        fprintf(stderr, "Failed to initialize xfconf: %s\n", error->message);
        g_error_free(error);
        return 1;
    }

    // Open the xfce4-panel channel
    XfconfChannel *channel = xfconf_channel_get("xfce4-panel");
    if (!channel) {
        fprintf(stderr, "Failed to open xfce4-panel channel\n");
        return 1;
    }

    // Get the list of properties under /plugins
    properties = xfconf_channel_get_properties(channel, "/plugins");
    if (!properties) {
        fprintf(stderr, "Error getting properties or no properties found\n");
        return 1;
    }

    // Iterate over the properties to check for plugin presence
    g_hash_table_iter_init(&iter, properties);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        if (is_plugin_key((char *)key)) {
            gchar *v = xfconf_channel_get_string(channel, key, NULL);
            if (v) {
                printf("Key: %s, Value: %s\n", (char *)key, v);  // Debug: print each key and value
                // Check for Whisker Menu
                if (g_strrstr(v, "whiskermenu") != NULL) {
                    whisker_menu_present = TRUE;
                }
                // Check for Applications Menu
                if (g_strrstr(v, "applicationsmenu") != NULL) {
                    applications_menu_present = TRUE;
                }
                g_free(v);
            }
        }
    }
    xfconf_shutdown();

    // Print the results
    if (whisker_menu_present) {
        printf("Whisker Menu is present in the XFCE panel.\n");
    } else {
        printf("Whisker Menu is not present in the XFCE panel.\n");
    }

    if (applications_menu_present) {
        printf("Applications Menu is present in the XFCE panel.\n");
    } else {
        printf("Applications Menu is not present in the XFCE panel.\n");
    }

    // Clean up
    if (properties) {
        g_hash_table_unref(properties);
    }

    return 0;
}

Last edited by Misko_2083 (2024-06-19 20:32:53)


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#13 2024-06-20 02:01:20

ToZ
Administrator
From: Canada
Registered: 2011-06-02
Posts: 11,313

Re: Super key to open up menu but also be used as a modifier key

Thanks, but unfortunately it doesn't work for me. It works with the whiskermenu on the panel, but not with applicationsmenu.

Your secondary program shows:

Whisker Menu is not present in the XFCE panel.
Applications Menu is present in the XFCE panel.

I put some debug messages into your code and it finds the applicationsmenu but then it enters the find_window() function and recursively searches for but doesn't seem to find a window (which doesn't exist at this time). Hope this helps.


Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki  |  Community | Contribute ---

Offline

#14 2024-06-20 06:38:44

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

Let's see what is going on.
Open a terminal window and run:

watch -n 1 xwininfo -root -children

When you open applications menu it should show that the number of children has changed (children+1).
The first one you see on list is xfce4-panel.
The second one I see is
<window_id> "xfce4-panel": ()    10x10+-100+-100   +-100+-100
That's the one we are trying to get window_id from with xprop.
() means the class for the window isn't set, so I tried to exploit that.
Since it's not possible to run xprop and click on applications menu I open another terminal window,
then run next and quickly before sleep ends open Applications menu.

sleep 3; xwininfo -root -children | grep "()" | awk '
    /xfce4-panel/ {
       print $1; system("xprop -id " $1)
            }'

"print $1" in awk prints the window_id and it matches to the one in the other window.
then xprop prints out properties.

Those properties may be different.
So when the window with those 5 properties exists it sends escape key to close it. If there is no window with those properties window ID isn't found and it should run the command to launch the menu.

    } else if (strcmp(menu, "applicationsmenu") == 0) {
        if ((check_window_property(window, wm_class, "xfce4-panel") == 0) &&
            check_window_property(window, wm_icon_name, "xfce4-panel") &&
            check_window_property(window, net_wm_icon_name, "xfce4-panel") &&
            check_window_property(window, wm_name, "xfce4-panel") &&
            check_window_property(window, net_wm_name, "xfce4-panel")) {
            return window;  // Found the Applications Menu window
        }
    }

If there is a window ID when application menu is shown then there is another window with the same properties we are searching for and it won't run the command to show the menu.

By the way, this panel version is 4.18.2.

Last edited by Misko_2083 (2024-06-20 07:42:42)


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#15 2024-06-20 14:21:06

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

Application finder definitely need to run in the background.
So change the constant to:

#define FINDER "/usr/bin/xfce4-appfinder &"

Now it wont block when App Finder is running.

Overall this is a hacky solution but it works for me.
https://youtu.be/ps16YP1Ue7Y

Last edited by Misko_2083 (2024-06-20 14:30:33)


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#16 2024-06-20 22:34:22

ToZ
Administrator
From: Canada
Registered: 2011-06-02
Posts: 11,313

Re: Super key to open up menu but also be used as a modifier key

My apologies but its working now. I initially tested on xfce4-panel 4.19 - not sure why it wasn't working, but now it works fine. All 3 options - even appfinder once I change the define to execute in the background. Thanks.


Please remember to mark your thread [SOLVED] to make it easier for others to find
--- How To Ask For Help | FAQ | Developer Wiki  |  Community | Contribute ---

Offline

#17 2024-06-25 20:54:33

advice1010
Member
Registered: 2023-02-19
Posts: 101

Re: Super key to open up menu but also be used as a modifier key

Wow, lot of activity.

@ToZ
Thank you again for your response.
When I mentioned about Whisker Menu, I didn't mean does it have the “-c” center option, I meant do you know if options can be added to the command used to open panel menu?
For example, you can link your panel menu to use Whisker Menu, but can you add the -c option to that command to then load up your Whisker Menu in center of screen when hitting super key?

Can you or anyone confirm that this is possible?
Also can any command be linked to the super key, such as a 3rd party panel menu such as ROFI?  I still plan on using Whisker Menu, but was just curious if this is possible with this new feature added to newer XFCE releases.

Nice that there can be a script to toggle (seems a little too complex for me), but as mentioned am hoping for these items to be built into XFCE itself.  I might mention this.

Thank you guys again for your responses

Last edited by advice1010 (2024-06-25 20:55:10)

Offline

#18 2024-06-28 22:01:17

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

ToZ wrote:

My apologies but its working now. I initially tested on xfce4-panel 4.19 - not sure why it wasn't working, but now it works fine. All 3 options - even appfinder once I change the define to execute in the background. Thanks.

This is quite a hacky way of doing this.
Should be fixed in the plugins.
So I pulled the source code for the xfce4-panel and went for 4.18 branch.
First I made the application menu close on key by connecting to the key release event.
At first it did not work if a submenu is open.
So the submenus have to do that too, and I added another function to do that.
Simple as that https://gist.github.com/Misko-2083/aceb … e971e25142
However super key closing applications menu would be embedded in the code.
What would be the best way to set the that closes the plugin?
Let the user set it? Or check in the setting via xfconf or by plugin event via the panel?

https://gitlab.xfce.org/xfce/xfce4-panel/-/issues/847


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

#19 2024-07-01 20:10:49

advice1010
Member
Registered: 2023-02-19
Posts: 101

Re: Super key to open up menu but also be used as a modifier key

@Misko_2083
Thank you for taking the time to present this on Gitlab site.

Offline

#20 2024-07-02 14:48:59

Misko_2083
Member
Registered: 2015-10-13
Posts: 211
Website

Re: Super key to open up menu but also be used as a modifier key

Made it more resilient to crashes on rapid superkey press (abuse).
Now a pool works I've set it to 5 to remember 5 keypresses for those that like to play with menus. smile
Application Finder is closed softly with xlib now instead of Escape key. So even if it is minimizes or looses focus it will be closed.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <getopt.h>
#include <glib.h>
#include <pthread.h>
#include <regex.h>
#include <unistd.h>
#include <X11/Xatom.h>
#include <X11/XKBlib.h>
#include <X11/Xlibint.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/extensions/record.h>
#include <X11/extensions/XTest.h>
#include <X11/keysym.h>
#include <X11/keysymdef.h>
#include <xfconf/xfconf.h>

#define WHISKER "/usr/bin/xfce4-popup-whiskermenu"
#define APPLICATIONS "/usr/bin/xfce4-popup-applicationsmenu"
#define FINDER "xfce4-appfinder &"
#define QUEUE_SIZE 5

/* gcc -o whisker_launcher whisker_launcher.c `pkg-config --cflags libxfconf-0` -lX11 -lXtst -lXi  -lxfconf-0 -lglib-2.0 -pthread */

typedef struct {
    void (*function)(void*);
    void *argument;
} task_t;

typedef struct {
    pthread_mutex_t lock;
    pthread_cond_t cond;
    pthread_t thread;
    task_t task_queue[QUEUE_SIZE];
    int head;
    int tail;
    int count;
    int shutdown;
} single_thread_pool_t;

/* for this struct, refer to libxnee */
typedef union {
    unsigned char    type ;
    xEvent           event ;
    xResourceReq     req   ;
    xGenericReply    reply ;
    xError           error ;
    xConnSetupPrefix setup;
} XRecordDatum;

// Data structure to track key presses
typedef struct {
    int key_code;
    int pressed; // 1 if key is pressed, 0 if released
} KeyState;

pthread_mutex_t lock;
single_thread_pool_t pool;

KeyState super_l_state = {0, 0};
KeyState super_r_state = {0, 0};

// Global variables for displays and XRecordContext
Display *d = NULL;
Display *d_control = NULL;
XRecordContext context = 0;
char *menu = NULL;
static int super_l_pressed = 0;
static int super_r_pressed = 0;
static int keep_runing = 1;

/* Function declarations */
void      log_message                       (const char            *message);
int       check_window_property             (Window                window,
                                             Atom                  property,
                                             const char            *value);
Window    find_window_recursive             (Window                window,
                                             Atom                  wm_class,
                                             Atom                  wm_icon_name,
                                             Atom                  net_wm_icon_name,
                                             Atom                  wm_name,
                                             Atom                  net_wm_name);
Window    find_window                       (void);
int       is_window_hidden                  (Window                window);
void      run_menu_and_wait                 (void);
gboolean  is_plugin_key                     (const char            *key);
void      send_key                          (KeySym                keysym);
char      *find_menus                       (void);
static int window_exists_error_handler      (Display               *display,
                                             XErrorEvent           *error);
int       window_exists                     (Display               *display,
                                             Window                window);
void      manage_menu                       (void                  *data);
void      event_callback                    (XPointer              priv,
                                             XRecordInterceptData  *hook);
void      clean                             (void);
XRecordRange*    create_record_range        (void);
void      setup_record_context              (void);
void*     thread_worker                     (void *arg);
void      single_thread_pool_init           (void);
void      single_thread_pool_add_task       (void (*function)(void*),
                                             void *argument);
void      single_thread_pool_destroy        (void);
Window    get_focused_window                (void);
int       is_focused_window_app_finder      (Window                app_finder_window);

void* thread_worker(void *arg) {
    while (1) {
        pthread_mutex_lock(&pool.lock);
        while (pool.count == 0 && !pool.shutdown) {
            pthread_cond_wait(&pool.cond, &pool.lock);
        }
        if (pool.shutdown) {
            pthread_mutex_unlock(&pool.lock);
            pthread_exit(NULL);
        }
        task_t task = pool.task_queue[pool.head];
        pool.head = (pool.head + 1) % QUEUE_SIZE;
        pool.count--;
        pthread_mutex_unlock(&pool.lock);

        if (task.function != NULL) {
            task.function(task.argument);
        }
    }
}

void single_thread_pool_init() {
    pthread_mutex_init(&pool.lock, NULL);
    pthread_cond_init(&pool.cond, NULL);
    pool.head = 0;
    pool.tail = 0;
    pool.count = 0;
    pool.shutdown = 0;
    pthread_create(&pool.thread, NULL, thread_worker, NULL);
    printf("Single-thread pool initialized.\n");
}

void single_thread_pool_add_task(void (*function)(void*), void *argument) {
  /*  if (pthread_mutex_trylock(&lock) == 0)
     {*/
        pthread_mutex_unlock(&lock);
        pthread_mutex_lock(&pool.lock);
        if (pool.count < QUEUE_SIZE) {
           pool.task_queue[pool.tail].function = function;
           pool.task_queue[pool.tail].argument = argument;
           pool.tail = (pool.tail + 1) % QUEUE_SIZE;
           pool.count++;
           pthread_cond_signal(&pool.cond);
        } else {
           printf("Task queue is full. Task not added.\n");
        }
        pthread_mutex_unlock(&pool.lock);
  /* } else {
        printf("Task is already running. Task not added.\n");
     }*/
}

void single_thread_pool_destroy() {
    pthread_mutex_lock(&pool.lock);
    pool.shutdown = 1;
    pthread_cond_broadcast(&pool.cond);
    pthread_mutex_unlock(&pool.lock);
    pthread_join(pool.thread, NULL);
    pthread_mutex_destroy(&pool.lock);
    pthread_cond_destroy(&pool.cond);
    printf("Single-thread pool destroyed.\n");
}

// Function to log messages to stderr
void log_message(const char *message) {
    fprintf(stderr, "%s\n", message);
}

// Function to check if a window has the specified property with a specific value
int check_window_property(Window window, Atom property, const char *value) {
    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytes_after;
    unsigned char *prop;
    int result = 0;

    if (window_exists(d, window) && XGetWindowProperty(d, window, property, 0, 1024, False, AnyPropertyType,
                           &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
        if (actual_type != None) {
         if (prop) {
            if (strcmp((char *)prop, value) == 0) {
                result = 1;
            }
            XFree(prop);
        }
       }
    }
    return result;
}

// Function to recursively search for the Menu window in the window's children
Window find_window_recursive(Window window, Atom wm_class, Atom wm_icon_name, Atom net_wm_icon_name, Atom wm_name, Atom net_wm_name) {
    // Check if the current window is the Menu window
    if (strcmp(menu, "whiskermenu") == 0) {
        if (check_window_property(window, wm_class, "wrapper-2.0") &&
            check_window_property(window, wm_icon_name, "Whisker Menu") &&
            check_window_property(window, net_wm_icon_name, "Whisker Menu") &&
            check_window_property(window, wm_name, "Whisker Menu") &&
            check_window_property(window, net_wm_name, "Whisker Menu")) {
            return window;  // Found the Whisker Menu window
        }
    } else if (strcmp(menu, "finder") == 0) {
        if (check_window_property(window, wm_class, "xfce4-appfinder") &&
            check_window_property(window, wm_icon_name, "Application Finder") &&
            check_window_property(window, net_wm_icon_name, "Application Finder") &&
            check_window_property(window, wm_name, "Application Finder") &&
            check_window_property(window, net_wm_name, "Application Finder")) {
            return window;  // Found the Application Finder Menu window
        }
    } else if (strcmp(menu, "applicationsmenu") == 0) {
        if ((check_window_property(window, wm_class, "xfce4-panel") == 0) &&
            check_window_property(window, wm_icon_name, "xfce4-panel") &&
            check_window_property(window, net_wm_icon_name, "xfce4-panel") &&
            check_window_property(window, wm_name, "xfce4-panel") &&
            check_window_property(window, net_wm_name, "xfce4-panel")) {
            return window;  // Found the Applications Menu window
        }
    }

    // Recursively search for the Menu window in the window's children
    Window parent;
    Window *children = NULL;
    unsigned int nchildren;
    if (window_exists(d, window) && XQueryTree(d, window, &window, &parent, &children, &nchildren) != 0) {
        for (unsigned int i = 0; i < nchildren; i++) {
            Window menu_id = find_window_recursive(children[i], wm_class, wm_icon_name, net_wm_icon_name, wm_name, net_wm_name);
            if (menu_id != 0) {
                XFree(children);  // Free the memory allocated by XQueryTree
                return menu_id;  // Found the Whisker Menu window
            }
        }
        XFree(children);  // Free the memory allocated by XQueryTree
    }

    return 0;  // Not found
}

// Function to find the Whisker Menu window
Window find_window() {
    Window root = DefaultRootWindow(d);
    Window parent;
    Window *children = NULL;
    unsigned int nchildren;
    Atom wm_class = XInternAtom(d, "WM_CLASS", False);
    Atom wm_icon_name = XInternAtom(d, "WM_ICON_NAME", False);
    Atom net_wm_icon_name = XInternAtom(d, "_NET_WM_ICON_NAME", False);
    Atom wm_name = XInternAtom(d, "WM_NAME", False);
    Atom net_wm_name = XInternAtom(d, "_NET_WM_NAME", False);

    // Query the tree to get all windows
    if (XQueryTree(d, root, &root, &parent, &children, &nchildren) != 0) {
        // Recursively search for the Menu window
        Window menu_id = 0;
        for (unsigned int i = 0; i < nchildren; i++) {
            menu_id = find_window_recursive(children[i], wm_class, wm_icon_name, net_wm_icon_name, wm_name, net_wm_name);
            if (menu_id != 0) {
                break;  // Found the Whisker Menu window, exit loop
            }
        }
        XFree(children);  // Free the memory allocated by XQueryTree
        return menu_id;
    } else {
        fprintf(stderr, "XQueryTree failed\n");
        return 0;
    }
}

// Function to check if the window is hidden
int is_window_hidden(Window window) {
    Atom wm_state_hidden = XInternAtom(d, "_NET_WM_STATE_HIDDEN", False);
    Atom wm_state = XInternAtom(d, "WM_STATE", False);

    Atom actual_type;
    int actual_format;
    unsigned long nitems, bytes_after;
    unsigned char *prop;
    int is_hidden = 0;

    if (window != 0 && window_exists(d, window) && XGetWindowProperty(d, window, wm_state, 0, 1, False, wm_state,
                           &actual_type, &actual_format, &nitems, &bytes_after, &prop) == Success) {
        if (nitems > 0 && actual_format == 32 && prop != NULL) {
            long state = *(long *)prop;
            is_hidden = (state == WithdrawnState);
        }
        XFree(prop);
    }
    return is_hidden;
}

// Function to run xfce4-popup-whiskermenu and wait for it to show up
void run_menu_and_wait() {
    int max_attempts = 20;
    int sleep_duration = 10000; // 10 milliseconds

    if (menu == "whiskermenu") {
        system(WHISKER);
    } else if (menu == "applicationsmenu") {
        system(APPLICATIONS); 
    } else {
        system(FINDER);
    }

    for (int attempt = 0; attempt < max_attempts; attempt++) {
        usleep(sleep_duration);
        Window menu_id = find_window();
        if (menu_id != 0 && !is_window_hidden(menu_id)) {
            //printf("0x%8x\n", menu_id);
            log_message("Menu is now visible");
            return;
        }
    }
    if (menu == "whiskermenu") {
        system(WHISKER);
    }
}

/* function to simulate both the key press and release events */
void send_key(KeySym keysym) {
    KeyCode keycode = XKeysymToKeycode(d, keysym);
    
    // Simulate key press
    XTestFakeKeyEvent(d, keycode, True, CurrentTime);
    XFlush(d);

    
    // Simulate key release
    XTestFakeKeyEvent(d, keycode, False, CurrentTime);
    XFlush(d);

}

// Function to check if the key matches the pattern "plugin-<number>"
gboolean is_plugin_key(const char *key) {
    regex_t regex;
    int ret;

    // Compile the regular expression
    ret = regcomp(&regex, "^/plugins/plugin-[0-9]+$", REG_EXTENDED);
    if (ret) {
        fprintf(stderr, "Could not compile regex\n");
        return FALSE;
    }

    // Execute the regular expression
    ret = regexec(&regex, key, 0, NULL, 0);
    regfree(&regex);

    return ret == 0;
}

// Funtion to check if whisker and applications menu are present in the panel
char *find_menus () {
    GError *error = NULL;
    GHashTable *properties = NULL;
    GHashTableIter iter;
    gboolean whisker_menu_present = FALSE;
    gboolean applications_menu_present = FALSE;
    gpointer key, value;

    // Initialize xfconf
    if (!xfconf_init(&error)) {
        fprintf(stderr, "Failed to initialize xfconf: %s\n", error->message);
        g_error_free(error);
        return NULL;
    }

    // Open the xfce4-panel channel
    XfconfChannel *channel = xfconf_channel_get("xfce4-panel");
    if (!channel) {
        fprintf(stderr, "Failed to open xfce4-panel channel\n");
        return NULL;
    }

    // Get the list of properties under /plugins
    properties = xfconf_channel_get_properties(channel, "/plugins");
    if (!properties) {
        fprintf(stderr, "Error getting properties or no properties found\n");
        return NULL;
    }

    // Iterate over the properties to check for plugin presence
    g_hash_table_iter_init(&iter, properties);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        if (is_plugin_key((char *)key)) {
            gchar *v = xfconf_channel_get_string(channel, key, NULL);
            if (v) {
                // Check for Whisker Menu
                if (g_strrstr(v, "whiskermenu") != NULL) {
                    whisker_menu_present = TRUE;
                }
                // Check for Applications Menu
                if (g_strrstr(v, "applicationsmenu") != NULL) {
                    applications_menu_present = TRUE;
                }
                g_free(v);
            }
        }
    }

    xfconf_shutdown();

    // Clean up
    if (properties) {
        g_hash_table_unref(properties);
    }

    if (whisker_menu_present) {
        return "whiskermenu";
    } else if (applications_menu_present) {
        return "applicationsmenu";
    } else {
        return NULL;
    }
}

// Custom error handler to catch BadWindow errors
static int window_exists_error_handler(Display *display, XErrorEvent *error) {
    if (error->error_code == BadWindow) {
        return 0; // Indicates that the window does not exist
    }
    return 1; // Indicates an error other than BadWindow occurred
}

// Function to check if a window exists
int window_exists(Display *display, Window window) {
    XWindowAttributes attributes;
    int (*old_handler)(Display *, XErrorEvent *);

    // Set the custom error handler
    old_handler = XSetErrorHandler(window_exists_error_handler);

    // Try to get the window attributes
    if (XGetWindowAttributes(display, window, &attributes)) {
        // Restore the old error handler
        XSetErrorHandler(old_handler);
        return 1; // The window exists
    }

    // Restore the old error handler
    XSetErrorHandler(old_handler);
    return 0; // The window does not exist
}

/* Manage Menu window */
void manage_menu(void *data) {
    // Find the Menu in the xfce4-panel
    pthread_mutex_lock(&lock);
    menu = find_menus();
    if (menu == NULL) {
        menu = "finder";
    }

    // Find the Menu window
    Window menu_id = find_window();
    if (menu_id != 0) {
        if (is_window_hidden(menu_id)) {
            run_menu_and_wait();
        } else {
            if (strcmp(menu, "finder") == 0) {
                XEvent event;
                event.xclient.type = ClientMessage;
                event.xclient.window = menu_id;
                event.xclient.message_type = XInternAtom(d, "WM_PROTOCOLS", TRUE);
                event.xclient.format = 32;
                event.xclient.data.l[0] = XInternAtom(d, "WM_DELETE_WINDOW", FALSE);
                event.xclient.data.l[1] = CurrentTime;
                XSendEvent(d, menu_id, False, NoEventMask, &event);  // soft close appfinder
                XFlush(d);
                    int max_attempts = 20;
                    int sleep_duration = 10000; // 10 milliseconds
                    for (int attempt = 0; attempt < max_attempts; attempt++) {
                        usleep(sleep_duration);
                        if (window_exists(d, menu_id)) {
                            //printf("0x%lx\n", menu_id);
                            break;
                        }
                   }
                   log_message("Application Finder is now hidden");
            } else if (strcmp(menu, "whiskermenu") == 0) {
                    // Send escape key
                    send_key(XK_Escape);
                    int max_attempts = 20;
                    int sleep_duration = 10000; // 10 milliseconds
                    for (int attempt = 0; attempt < max_attempts; attempt++) {
                        usleep(sleep_duration);
                        if (is_window_hidden(menu_id)) {
                            //printf("0x%lx\n", menu_id);
                            break;
                        }
                   }
                   log_message("Whisker Menu is now hidden");
            } else {
                // Send escape key
                    send_key(XK_Escape);
                    int max_attempts = 20;
                    int sleep_duration = 10000; // 10 milliseconds
                    for (int attempt = 0; attempt < max_attempts; attempt++) {
                        usleep(sleep_duration);
                        if (is_window_hidden(menu_id)) {
                            //printf("0x%lx\n", menu_id);
                            break;
                        }
                   }
                   log_message("Applications Menu is now hidden");
            }
        }
    } else {
        // Menu window ID not found, launching Menu...
        run_menu_and_wait();
    }
    menu = 0;
    pthread_mutex_unlock(&lock);
}

/* Callback function for XRecordContext */
void event_callback(XPointer priv, XRecordInterceptData *hook) {
    /* log_message("Event callback triggered"); */

    if (hook->category != XRecordFromServer) {
        XRecordFreeData(hook);
        return;
    }

    XRecordDatum *data = (XRecordDatum*) hook->data;
    int event_type = data->type;
    BYTE keycode = data->event.u.u.detail;

    if (event_type == KeyPress || event_type == KeyRelease) {
        KeySym keysym = XkbKeycodeToKeysym(d, keycode, 0, 0);

        /*char buffer[256];
        snprintf(buffer, sizeof(buffer), "Key event: type=%d, keycode=%u, keysym=%lu",
                 data->event.u.u.type, keycode, keysym);
        log_message(buffer); */

        if (keysym == XK_Super_L) {
            if (event_type == KeyPress) {
                super_l_state.key_code = keycode;
                super_l_state.pressed = 1;
            } else if (event_type == KeyRelease) {
                if (super_l_state.pressed && super_l_state.key_code == keycode) {
                    //log_message("Super_L key released (after press)");
                    single_thread_pool_add_task(manage_menu, NULL);
                }
            }
        } else if (keysym == XK_Super_R) {
            if (event_type == KeyPress) {
                super_r_state.key_code = keycode;
                super_r_state.pressed = 1;
            } else if (event_type == KeyRelease) {
                if (super_r_state.pressed && super_r_state.key_code == keycode) {
                    //log_message("Super_R key released (after press)");
                    single_thread_pool_add_task(manage_menu, NULL);
                }
            }
        } else {
            //printf("Other key pressed\n");
            super_l_state.pressed = 0;
            super_r_state.pressed = 0;
            super_l_state.key_code = keycode;
            super_r_state.key_code = keycode;

        }
    } /*else {
        char buffer[256];
        snprintf(buffer, sizeof(buffer), "Non-key event: type=%d", data->type);
        log_message(buffer);
    }*/

    XRecordFreeData(hook);
}

void cleanup(void) {
    log_message("Cleaning up resources...");
    if (context) {
        XRecordDisableContext(d_control, context);
        XRecordFreeContext(d_control, context);
        context = 0;
    }
    log_message("D_control...");
    if (d_control) {
        XCloseDisplay(d_control);
        d_control = NULL;
    }
    if (d) {
        XCloseDisplay(d);
        d = NULL;
    }
    if (menu) {
        free(menu);
    }
    single_thread_pool_destroy();
    pthread_mutex_destroy(&lock);
    log_message("Done...");
}

/* Create an XRecordRange for recording key events */
XRecordRange* create_record_range(void) {
    XRecordRange *range = XRecordAllocRange();
    if (!range) {
        log_message("Unable to allocate XRecordRange");
        cleanup();
        exit(1);
    }
    range->device_events.first = KeyPress;
    range->device_events.last = KeyRelease;
    return range;
}

/* Set up the XRecord context for capturing key events */
void setup_record_context(void) {
    XRecordClientSpec clients = XRecordAllClients;
    XRecordRange *range = create_record_range();
    context = XRecordCreateContext(d_control, 0, &clients, 1, &range, 1);

    if (!context) {
        log_message("Unable to create XRecordContext");
        cleanup();
        exit(1);
    } else {
        log_message("XRecordContext created successfully");
    }

    if (!XRecordEnableContextAsync(d_control, context, event_callback, NULL)) {
        log_message("Unable to enable XRecordContext");
        cleanup();
        exit(1);
    } else {
        log_message("XRecordContext enabled successfully");
    }

    XFree(range);
}

/* Main function */
int main(int argc, char **argv) {
    d = XOpenDisplay(NULL);
    if (d == NULL) {
        log_message("Cannot open display");
        exit(1);
    }

    d_control = XOpenDisplay(NULL);
    if (d_control == NULL) {
        log_message("Cannot open control display");
        exit(1);
    }

    XSynchronize(d, True); // Ensure synchronized operation on display
    XSynchronize(d_control, True); // Ensure synchronized operation on control display

    setup_record_context();

    single_thread_pool_init();
    pthread_mutex_init(&lock, NULL);

    while (keep_runing) {
        if (XPending(d_control)) {
            XRecordProcessReplies(d_control);
        }
        usleep(100000);  // Sleep for 100 milliseconds to reduce CPU usage
    }

    single_thread_pool_destroy();
    pthread_mutex_destroy(&lock);

    if (menu) {
        menu = NULL;
        free(menu);
    }

    XFlush(d);
    XSync(d, True);

    if (context) {
        XRecordDisableContext(d_control, context);
        XRecordFreeContext(d_control, context);
        context = 0;
    }
    if (d_control) {
        XCloseDisplay(d_control);
        d_control = NULL;
    }
    if (d) {
        XCloseDisplay(d);
        d = NULL;
    }

    return 0;
}

Development Libraries: libX11, libXtst, libXi, libxfconf-0, libglib-2.0

Notes:
- Remove any shortcut  in the keyboard setting that launches whiskemenu, applications menu or application finder.
- Make sure this option in the application Finder is turned off: Keep running instance in the background
JuXFhfil.png

Last edited by Misko_2083 (2024-07-02 14:57:33)


Do you want to exit the Circus?
https://www.youtube.com/watch?v=ZJwQicZHp_c

Offline

Board footer

Powered by FluxBB