Here it goes:

The first patch implements wait/signal, the second extends it with
lock/unlock. My goal this time was to make the code small and simple
as possible, let me know if you think anything needs to be refactored.
---
 Makefile.am               |   1 +
 cmd-wait-for.c            | 145 ++++++++++++++++++++++++++++++++++++++++++++++
 cmd.c                     |   1 +
 examples/tmux-wait-for.sh | 109 ++++++++++++++++++++++++++++++++++
 tmux.h                    |   1 +
 5 files changed, 257 insertions(+)
 create mode 100644 cmd-wait-for.c
 create mode 100755 examples/tmux-wait-for.sh
diff --git a/Makefile.am b/Makefile.am
index 5caa498..19220d8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -135,6 +135,7 @@ dist_tmux_SOURCES = \
        cmd-switch-client.c \
        cmd-unbind-key.c \
        cmd-unlink-window.c \
+       cmd-wait-for.c \
        cmd.c \
        colour.c \
        control.c \
diff --git a/cmd-wait-for.c b/cmd-wait-for.c
new file mode 100644
index 0000000..658109b
--- /dev/null
+++ b/cmd-wait-for.c
@@ -0,0 +1,145 @@
+/* $Id$ */
+
+/*
+ * Copyright (c) 2013 Thiago de Arruda<tpadilh...@gmail.com>
+ *
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
+ * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+struct cmdq_node {
+       struct cmd_q *cmdq;
+       TAILQ_ENTRY(cmdq_node) node;
+};
+
+struct channel_node {
+       char *name;
+       TAILQ_HEAD(, cmdq_node) waiting_cmdqs;
+       RB_ENTRY(channel_node) node;
+};
+RB_HEAD(channels, channel_node) channels_head = RB_INITIALIZER(&channels_head);
+
+enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *);
+enum cmd_retval channel_wait(const char *, struct cmd_q *);
+enum cmd_retval channel_signal(const char *, struct cmd_q *);
+struct channel_node *channels_find(const char *);
+struct channel_node *channels_find_or_create(const char *);
+int channel_cmp(struct channel_node *, struct channel_node *);
+RB_PROTOTYPE(channels, channel_node, node, channel_cmp);
+
+const struct cmd_entry cmd_wait_for_entry = {
+       "wait-for", "wait",
+       "S", 1, 1,
+       "[-S] channel",
+       0,
+       NULL,
+       NULL,
+       cmd_wait_for_exec
+};
+
+enum cmd_retval
+cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq)
+{
+       struct args     *args = self->args;
+       const char *name = args->argv[0];
+
+       if (args_has(args, 'S'))
+               return channel_signal(name, cmdq);
+
+       return channel_wait(name, cmdq);
+}
+
+RB_GENERATE(channels, channel_node, node, channel_cmp);
+
+int
+channel_cmp(struct channel_node *c1, struct channel_node *c2)
+{
+       return (strcmp(c1->name, c2->name));
+}
+
+struct channel_node *
+channels_find(const char *name)
+{
+       struct channel_node c_name;
+       c_name.name = (char *)name;
+       return RB_FIND(channels, &channels_head, &c_name);
+}
+
+struct channel_node *
+channels_find_or_create(const char *name)
+{
+       struct channel_node *c;
+
+       c = channels_find(name);
+
+       if (c == NULL) {
+               c = xmalloc(sizeof *c);
+               c->name = xstrdup(name);
+               TAILQ_INIT(&c->waiting_cmdqs);
+               RB_INSERT(channels, &channels_head, c);
+       }
+
+       return c;
+}
+
+enum cmd_retval
+channel_wait(const char *name, struct cmd_q *cmdq)
+{
+       struct cmdq_node *wq;
+       struct channel_node *c;
+
+       if (cmdq->client == NULL || cmdq->client->session != NULL) {
+               cmdq_error(cmdq, "Not able to wait");
+               return (CMD_RETURN_ERROR);
+       }
+
+       c = channels_find_or_create(name);
+       wq = xmalloc(sizeof *wq);
+       wq->cmdq = cmdq;
+       TAILQ_INSERT_HEAD(&c->waiting_cmdqs, wq, node);
+       cmdq->references++;
+
+       return (CMD_RETURN_WAIT);
+}
+
+enum cmd_retval
+channel_signal(const char *name, struct cmd_q *cmdq)
+{
+       struct cmdq_node *wq;
+       struct channel_node *c;
+
+       c = channels_find(name);
+
+       if (c == NULL || TAILQ_EMPTY(&c->waiting_cmdqs)) {
+               cmdq_error(cmdq, "No waiting clients");
+               return (CMD_RETURN_ERROR);
+       }
+
+       while ((wq = TAILQ_FIRST(&c->waiting_cmdqs)) != NULL) {
+               TAILQ_REMOVE(&c->waiting_cmdqs, wq, node);
+               if (!cmdq_free(wq->cmdq))
+                       cmdq_continue(wq->cmdq);
+               free(wq);
+       }
+
+       RB_REMOVE(channels, &channels_head, c);
+       free(c->name);
+       free(c);
+
+       return (CMD_RETURN_NORMAL);
+}
diff --git a/cmd.c b/cmd.c
index 0d6a85f..20484ed 100644
--- a/cmd.c
+++ b/cmd.c
@@ -112,6 +112,7 @@ const struct cmd_entry *cmd_table[] = {
        &cmd_switch_client_entry,
        &cmd_unbind_key_entry,
        &cmd_unlink_window_entry,
+       &cmd_wait_for_entry,
        NULL
 };

diff --git a/examples/tmux-wait-for.sh b/examples/tmux-wait-for.sh
new file mode 100755
index 0000000..94baa90
--- /dev/null
+++ b/examples/tmux-wait-for.sh
@@ -0,0 +1,109 @@
+#!/bin/bash
+
+# Shows how one can synchronize work using the 'wait' command
+
+if [ -z $TMUX ]; then
+       echo "Start tmux first" >&2
+       exit 1
+fi
+
+kill_child_panes() {
+       tmux kill-pane -t $pane1
+       tmux kill-pane -t $pane2
+       tmux kill-pane -t $pane3
+       exit
+}
+abspath=$(cd ${0%/*} && echo $PWD/${0##*/})
+
+case $1 in
+       pane1)
+               tmux setw -q @pane1id $TMUX_PANE
+               tmux wait -S pane1
+               tmux wait pane1
+               tmux split-window -d -h "$abspath pane3"
+               tmux split-window -d -h "$abspath pane2"
+               tmux wait pane1
+               columns=$(tput cols)
+               n=1
+               while true; do
+                       for i in $(seq 1 $columns); do
+                               sleep "0.1"
+                               echo -n $n
+                       done
+                       echo
+                       tmux wait -S pane2
+                       tmux wait pane1
+                       n=$(( ($n + 3) % 9 ))
+               done
+               ;;
+       pane2)
+               tmux setw -q @pane2id $TMUX_PANE
+               tmux wait -S pane2
+               tmux wait pane2
+               columns=$(tput cols)
+               n=2
+               while true; do
+                       for i in $(seq 1 $columns); do
+                               sleep "0.1"
+                               echo -n $n
+                       done
+                       echo
+                       tmux wait -S pane3
+                       tmux wait pane2
+                       n=$(( ($n + 3) % 9 ))
+               done
+               ;;
+       pane3)
+               tmux setw -q @pane3id $TMUX_PANE
+               tmux wait -S pane3
+               tmux wait pane3
+               columns=$(tput cols)
+               n=3
+               while true; do
+                       for i in $(seq 1 $columns); do
+                               sleep "0.1"
+                               echo -n $n
+                       done
+                       echo
+                       tmux wait -S pane1
+                       tmux wait pane3
+                       n=$(( ($n + 3) % 9 ))
+               done
+               ;;
+       *)
+               columns=$(tput cols)
+               trap kill_child_panes SIGINT SIGTERM
+               clear
+               echo "This is a simple script that shows how tmux can synchronize"
+         echo "code running in different panes."
+               echo
+               echo "Besides recursively spliting panes, it will run a simple animation"
+               echo "demonstrating the coordination between panes"
+               echo
+               sleep 1
+               echo "First split horizontally"
+               tmux split-window "$abspath pane1"
+               tmux wait pane1
+               pane1=`tmux showw -v @pane1id`
+               sleep 1
+               echo "Now we split the child pane 2 times"
+               tmux wait -S pane1
+               tmux wait pane3
+               tmux wait pane2
+               pane2=`tmux showw -v @pane2id`
+               pane3=`tmux showw -v @pane3id`
+               column_width=$(($columns / 3))
+               sleep 1
+               echo "Resize equally"
+               tmux resize-pane -t $pane1 -x $column_width
+               tmux resize-pane -t $pane2 -x $column_width
+               tmux resize-pane -t $pane3 -x $column_width
+               sleep 1
+               echo "Start animation"
+               tmux wait -S pane1
+               tmux select-pane -t $TMUX_PANE
+               while true; do
+                       sleep 1000
+               done
+               ;;
+esac
diff --git a/tmux.h b/tmux.h
index c1ad662..95dd97c 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1835,6 +1835,7 @@ extern const struct cmd_entry cmd_switch_client_entry;
 extern const struct cmd_entry cmd_unbind_key_entry;
 extern const struct cmd_entry cmd_unlink_window_entry;
 extern const struct cmd_entry cmd_up_pane_entry;
+extern const struct cmd_entry cmd_wait_for_entry;

 /* cmd-attach-session.c */
 enum cmd_retval         cmd_attach_session(struct cmd_q *, const char*, int, int);
diff --git a/cmd-wait-for.c b/cmd-wait-for.c
index 658109b..58e53c6 100644
--- a/cmd-wait-for.c
+++ b/cmd-wait-for.c
@@ -29,7 +29,9 @@ struct cmdq_node {

 struct channel_node {
        char *name;
+       int locked;
        TAILQ_HEAD(, cmdq_node) waiting_cmdqs;
+       TAILQ_HEAD(, cmdq_node) locking_cmdqs;
        RB_ENTRY(channel_node) node;
 };
 RB_HEAD(channels, channel_node) channels_head = RB_INITIALIZER(&channels_head);
@@ -37,6 +39,8 @@ RB_HEAD(channels, channel_node) channels_head = RB_INITIALIZER(&channels_head);
 enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *);
 enum cmd_retval channel_wait(const char *, struct cmd_q *);
 enum cmd_retval channel_signal(const char *, struct cmd_q *);
+enum cmd_retval channel_lock(const char *, struct cmd_q *);
+enum cmd_retval channel_unlock(const char *, struct cmd_q *);
 struct channel_node *channels_find(const char *);
 struct channel_node *channels_find_or_create(const char *);
 int channel_cmp(struct channel_node *, struct channel_node *);
@@ -44,8 +48,8 @@ RB_PROTOTYPE(channels, channel_node, node, channel_cmp);

 const struct cmd_entry cmd_wait_for_entry = {
        "wait-for", "wait",
-       "S", 1, 1,
-       "[-S] channel",
+       "LSU", 1, 1,
+       "[-L | -S | -U] channel",
        0,
        NULL,
        NULL,
@@ -61,6 +65,12 @@ cmd_wait_for_exec(struct cmd *self, struct cmd_q *cmdq)
        if (args_has(args, 'S'))
                return channel_signal(name, cmdq);

+       if (args_has(args, 'L'))
+               return channel_lock(name, cmdq);
+
+       if (args_has(args, 'U'))
+               return channel_unlock(name, cmdq);
+
        return channel_wait(name, cmdq);
 }

@@ -90,7 +100,9 @@ channels_find_or_create(const char *name)
        if (c == NULL) {
                c = xmalloc(sizeof *c);
                c->name = xstrdup(name);
+               c->locked = 0;
                TAILQ_INIT(&c->waiting_cmdqs);
+               TAILQ_INIT(&c->locking_cmdqs);
                RB_INSERT(channels, &channels_head, c);
        }

@@ -137,9 +149,67 @@ channel_signal(const char *name, struct cmd_q *cmdq)
                free(wq);
        }

-       RB_REMOVE(channels, &channels_head, c);
-       free(c->name);
-       free(c);
+       if (!c->locked) {
+               RB_REMOVE(channels, &channels_head, c);
+               free(c->name);
+               free(c);
+       }

        return (CMD_RETURN_NORMAL);
 }
+
+enum cmd_retval
+channel_lock(const char *name, struct cmd_q *cmdq)
+{
+       struct cmdq_node *lq;
+       struct channel_node *c;
+
+       if (cmdq->client == NULL || cmdq->client->session != NULL) {
+               cmdq_error(cmdq, "Not able to lock");
+               return (CMD_RETURN_ERROR);
+       }
+
+       c = channels_find_or_create(name);
+
+       if (c->locked) {
+               lq = xmalloc(sizeof *lq);
+               lq->cmdq = cmdq;
+               TAILQ_INSERT_TAIL(&c->locking_cmdqs, lq, node);
+               cmdq->references++;
+               return (CMD_RETURN_WAIT);
+       }
+
+       c->locked = 1;
+       return (CMD_RETURN_NORMAL);
+}
+
+enum cmd_retval
+channel_unlock(const char *name, struct cmd_q *cmdq)
+{
+       struct cmdq_node *lq;
+       struct channel_node *c;
+
+       c = channels_find(name);
+
+       if (c == NULL || !c->locked) {
+               cmdq_error(cmdq, "Channel not locked");
+               return (CMD_RETURN_ERROR);
+       }
+
+       if ((lq = TAILQ_FIRST(&c->locking_cmdqs)) != NULL) {
+               TAILQ_REMOVE(&c->locking_cmdqs, lq, node);
+               if (!cmdq_free(lq->cmdq))
+                       cmdq_continue(lq->cmdq);
+               free(lq);
+       } else {
+               c->locked = 0;
+               if (TAILQ_EMPTY(&c->waiting_cmdqs)) {
+                       RB_REMOVE(channels, &channels_head, c);
+                       free(c->name);
+                       free(c);
+               }
+       }
+
+       return (CMD_RETURN_NORMAL);
+}
+
------------------------------------------------------------------------------
Symantec Endpoint Protection 12 positioned as A LEADER in The Forrester  
Wave(TM): Endpoint Security, Q1 2013 and "remains a good choice" in the  
endpoint security space. For insight on selecting the right partner to 
tackle endpoint security challenges, access the full report. 
http://p.sf.net/sfu/symantec-dev2dev
_______________________________________________
tmux-users mailing list
tmux-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tmux-users

Reply via email to