Ok this looks pretty good. I've made a few changes but mainly style/layout nits:

- I don't think we need find and find_and_create functions, the find
  will only be done once and the create twice (when there is lock too).

- A single cmdq can only be waiting once so no need to have a wrapper
  struct, just put the TAILQ_ENTRY in struct cmd_q itself.

- channel_node -> wait_channel and similar renaming.

- We always include sys/types.h, needed or not. Also errors always start
  with lowercase.

- Move things about to sort of vaguely fit where they go in other
  commands.

- Add to man page.

I considered putting structs in tmux.h but I can't see how they would be
needed outside this file so let's leave them local anyway. tmux.h is too
big already.

Please take a look and make sure there isn't anything stupid and then I
will commit and we can modify to add locking.


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..6313358
--- /dev/null
+++ b/cmd-wait-for.c
@@ -0,0 +1,124 @@
+/* $Id$ */
+
+/*
+ * Copyright (c) 2013 Nicholas Marriott <n...@users.sourceforge.net>
+ * 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 <sys/types.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "tmux.h"
+
+/*
+ * Block or wake a client on a named wait channel.
+ */
+
+enum cmd_retval cmd_wait_for_exec(struct cmd *, struct cmd_q *);
+
+const struct cmd_entry cmd_wait_for_entry = {
+       "wait-for", "wait",
+       "S", 1, 1,
+       "[-S] channel",
+       0,
+       NULL,
+       NULL,
+       cmd_wait_for_exec
+};
+
+struct wait_channel {
+       const char             *name;
+       TAILQ_HEAD(, cmd_q)     waiters;
+
+       RB_ENTRY(wait_channel)  entry;
+};
+RB_HEAD(wait_channels, wait_channel);
+struct wait_channels wait_channels = RB_INITIALIZER(wait_channels);
+
+int    wait_channel_cmp(struct wait_channel *, struct wait_channel *);
+RB_PROTOTYPE(wait_channels, wait_channel, entry, wait_channel_cmp);
+RB_GENERATE(wait_channels, wait_channel, entry, wait_channel_cmp);
+
+int
+wait_channel_cmp(struct wait_channel *wc1, struct wait_channel *wc2)
+{
+       return (strcmp(wc1->name, wc2->name));
+}
+
+enum cmd_retval        cmd_wait_for_signal(struct cmd_q *, const char *,
+                   struct wait_channel *);
+enum cmd_retval        cmd_wait_for_wait(struct cmd_q *, const char *,
+                   struct wait_channel *);
+
+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];
+       struct wait_channel     *wc, wc0;
+
+       wc0.name = name;
+       wc = RB_FIND(wait_channels, &wait_channels, &wc0);
+
+       if (args_has(args, 'S'))
+               return (cmd_wait_for_signal(cmdq, name, wc));
+       return (cmd_wait_for_wait(cmdq, name, wc));
+}
+
+enum cmd_retval
+cmd_wait_for_signal(struct cmd_q *cmdq, const char *name,
+    struct wait_channel *wc)
+{
+       struct cmd_q    *wq, *wq1;
+
+       if (wc == NULL || TAILQ_EMPTY(&wc->waiters)) {
+               cmdq_error(cmdq, "no waiting clients on %s", name);
+               return (CMD_RETURN_ERROR);
+       }
+
+       TAILQ_FOREACH_SAFE(wq, &wc->waiters, waitentry, wq1) {
+               TAILQ_REMOVE(&wc->waiters, wq, waitentry);
+               if (!cmdq_free(wq))
+                       cmdq_continue(wq);
+       }
+       RB_REMOVE(wait_channels, &wait_channels, wc);
+       free((void*) wc->name);
+       free(wc);
+
+       return (CMD_RETURN_NORMAL);
+}
+
+enum cmd_retval
+cmd_wait_for_wait(struct cmd_q *cmdq, const char *name,
+    struct wait_channel *wc)
+{
+       if (cmdq->client == NULL || cmdq->client->session != NULL) {
+               cmdq_error(cmdq, "not able to wait");
+               return (CMD_RETURN_ERROR);
+       }
+
+       if (wc == NULL) {
+               wc = xmalloc(sizeof *wc);
+               wc->name = xstrdup(name);
+               TAILQ_INIT(&wc->waiters);
+               RB_INSERT(wait_channels, &wait_channels, wc);
+       }
+       TAILQ_INSERT_TAIL(&wc->waiters, cmdq, waitentry);
+       cmdq->references++;
+
+       return (CMD_RETURN_WAIT);
+}
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/tmux.1 b/tmux.1
index 1a9c058..8df7975 100644
--- a/tmux.1
+++ b/tmux.1
@@ -3553,6 +3553,19 @@ If the command doesn't return success, the exit status 
is also displayed.
 .It Ic server-info
 .D1 (alias: Ic info )
 Show server information and terminal details.
+.It Xo Ic wait-for
+.Fl S
+.Ar channel
+.Xc
+.D1 (alias: Ic wait )
+When used without
+.Fl S ,
+prevents the client from exiting until woken using
+.Ic wait-for
+.Fl S
+with the same channel.
+This command only works from outside
+.Nm .
 .El
 .Sh TERMINFO EXTENSIONS
 .Nm
diff --git a/tmux.h b/tmux.h
index c1ad662..e58c1de 100644
--- a/tmux.h
+++ b/tmux.h
@@ -1416,6 +1416,8 @@ struct cmd_q {
        void                    *data;
 
        struct msg_command_data *msgdata;
+
+       TAILQ_ENTRY(cmd_q)       waitentry;
 };
 
 /* Command definition. */
@@ -1835,6 +1837,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);




On Tue, Mar 05, 2013 at 10:55:05PM -0300, Thiago Padilha wrote:
> 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


------------------------------------------------------------------------------
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