summaryrefslogtreecommitdiffstats
path: root/x11vnc
diff options
context:
space:
mode:
authorrunge <runge>2004-12-23 04:05:08 +0000
committerrunge <runge>2004-12-23 04:05:08 +0000
commit36181297d07b25c82c3764c6ae8f0fcfca6c29a7 (patch)
tree69f972cfff7ce22fc3d6c4b9730b58c4618ea73e /x11vnc
parent42adf572663ae3f33ee67689b7a2eb9a0d303ad6 (diff)
downloadlibtdevnc-36181297d07b25c82c3764c6ae8f0fcfca6c29a7.tar.gz
libtdevnc-36181297d07b25c82c3764c6ae8f0fcfca6c29a7.zip
x11vnc: minor tweaks for x11vnc 0.7 file release
Diffstat (limited to 'x11vnc')
-rw-r--r--x11vnc/ChangeLog4
-rw-r--r--x11vnc/README30
-rwxr-xr-xx11vnc/tkx11vnc93
-rw-r--r--x11vnc/tkx11vnc.h93
-rw-r--r--x11vnc/x11vnc.12
-rw-r--r--x11vnc/x11vnc.c184
6 files changed, 358 insertions, 48 deletions
diff --git a/x11vnc/ChangeLog b/x11vnc/ChangeLog
index ee31689..42b6aef 100644
--- a/x11vnc/ChangeLog
+++ b/x11vnc/ChangeLog
@@ -1,3 +1,7 @@
+2004-12-22 Karl Runge <runge@karlrunge.com>
+ * final polishing for 0.7 release, tkx11vnc tweaks
+ * more careful rfbPE in pick_window, start check_user_input4()
+
2004-12-19 Karl Runge <runge@karlrunge.com>
* cleanup putenv, snprint, other string manip.
* add -sync mode to remote control for better control
diff --git a/x11vnc/README b/x11vnc/README
index 8e9a89b..b424684 100644
--- a/x11vnc/README
+++ b/x11vnc/README
@@ -1,5 +1,5 @@
-x11vnc README file Date: Mon Dec 20 11:34:56 EST 2004
+x11vnc README file Date: Wed Dec 22 23:18:56 EST 2004
The following information is taken from these URLs:
@@ -895,31 +895,15 @@ int srandom(unsigned int seed);
#undef LIBVNCSERVER_HAVE_LIBPTHREAD
#define SHUT_RDWR 2
typedef unsigned int in_addr_t;
-#define XConvertCase(sym, lower, upper) \
-*(lower) = sym; \
-*(upper) = sym; \
-if (sym >> 8 == 0) { \
- if ((sym >= XK_A) && (sym <= XK_Z)) \
- *(lower) += (XK_a - XK_A); \
- else if ((sym >= XK_a) && (sym <= XK_z)) \
- *(upper) -= (XK_a - XK_A); \
- else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis)) \
- *(lower) += (XK_agrave - XK_Agrave); \
- else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis)) \
- *(upper) -= (XK_agrave - XK_Agrave); \
- else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn)) \
- *(lower) += (XK_oslash - XK_Ooblique); \
- else if ((sym >= XK_oslash) && (sym <= XK_thorn)) \
- *(upper) -= (XK_oslash - XK_Ooblique); \
-}
-
- You will also have to change all the snprint() references to sprintf()
- calls x11vnc.c (sorry I don't know how to do this in a macro since
- snprint() is varargs).
+#ifndef snprintf
+#define snprintf(a, n, args...) sprintf((a), ## args)
+#endif
Then run make with the Solaris build script environment, everything
should compile without problems, and the resulting x11vnc binary
- should work OK (but note the above XConvertCase only covers Latin 1).
+ should work OK (but note the workaround for XConvertCase in x11vnc.c
+ only covers Latin 1). If some non-x11vnc related programs fail (e.g.
+ test programs) and the x11vnc binary is not created try "make -k".
Similar sorts of kludges can be done on other older OS (Solaris,
Linux, ...) releases.
diff --git a/x11vnc/tkx11vnc b/x11vnc/tkx11vnc
index b32598f..d2e3782 100755
--- a/x11vnc/tkx11vnc
+++ b/x11vnc/tkx11vnc
@@ -73,6 +73,7 @@ Actions
=RA update-all
=GA clear-all
--
+ =RA stop+quit
=GA Quit
Help
@@ -286,6 +287,10 @@ the Entry box when prompted. Use the prefix \"Q:\" to indicate
a -Q query. Examples: \"zero:20,20,100,100\", \"Q:ext_xfixes\"
"
+ set helptext(stop+quit) "
+Send the stop command to the x11vnc server, then terminate the tkx11vnc gui.
+"
+
set helptext(Quit) "
Terminate the tkx11vnc gui. Any x11vnc servers will be left running.
"
@@ -778,10 +783,16 @@ proc menus_state {state} {
}
proc menus_enable {} {
+ global menus_disabled
+
menus_state "normal"
+ set menus_disabled 0
}
proc menus_disable {} {
+ global menus_disabled
+
+ set menus_disabled 1
menus_state "disabled"
}
@@ -1183,6 +1194,8 @@ proc see_if_ok {query item expected} {
set query_result_list [split_query $query]
foreach q $query_result_list {
+ # XXX following will crash if $item is not a good regexp
+ # need to protect it \Q$item\E style...
# if {[regexp "^$item:" $q]} {
# set found $q
# }
@@ -1409,6 +1422,12 @@ proc do_action {item} {
} elseif {$item == "all-settings"} {
show_all_settings
return
+ } elseif {$item == "stop+quit"} {
+ push_new_value "stop" "stop" 1 0
+ set_connected no
+ update
+ after 500
+ destroy .
}
if {[value_is_string $item]} {
@@ -1656,12 +1675,45 @@ proc disconnect_dialog {client} {
}
}
+proc update_clients_and_repost {} {
+ global item_cascade menu_m menu_b
+
+ append_text "Refreshing connected clients list... "
+ query_all 1
+ update
+
+ set saw 0
+ set casc $item_cascade(current)
+ set last [$casc index end]
+ for {set i 0} {$i <= $last} {incr i} {
+ if {[$casc type $i] == "separator"} {
+ continue
+ }
+ set name [$casc entrycget $i -label]
+ if {[regexp {^#} $name]} {
+ continue
+ }
+ if {[regexp {^refresh-list} $name]} {
+ continue
+ }
+ if {! $saw} {
+ append_text "\n"
+ }
+ set saw 1
+ append_text "client: $name\n"
+ }
+ if {! $saw} {
+ append_text "done.\n"
+ }
+}
+
proc update_clients_menu {list} {
global item_cascade
set subm $item_cascade(current);
catch {destroy $subm}
menu $subm -tearoff 0
$subm add command
+ $subm add command -label "refresh-list" -command "update_clients_and_repost"
$subm add separator
set count 0
foreach client [split $list ","] {
@@ -1762,7 +1814,16 @@ set v 0
set menu "$colf.menu$case.menu";
set menu_b($case) $menub
set menu_m($case) $menu
- menubutton $menub -text "$case" -underline 0 \
+ set ul 0
+ foreach char [split $case ""] {
+ set char [string tolower $char]
+ if {![info exists underlined($char)]} {
+ set underlined($char) 1
+ break
+ }
+ incr ul
+ }
+ menubutton $menub -text "$case" -underline $ul \
-anchor w -menu $menu -background $fbg \
-font $bfont
pack $menub -side top -fill x
@@ -2036,16 +2097,31 @@ MenuSelect>>
}
proc key_bindings {} {
- global env
+ global env menus_disabled
if {[info exists env(USER)] && $env(USER) == "runge"} {
# quick restart
bind . <Control-KeyPress-c> {exec $argv0 $argv &; destroy .}
}
- bind . <Control-KeyPress-p> {try_connect_and_query_all}
- bind . <Control-KeyPress-u> {query_all 0}
- bind . <Control-KeyPress-r> {query_all 0}
- bind . <Control-KeyPress-d> {detach_from_display}
- bind . <Control-KeyPress-a> {try_connect_and_query_all}
+ bind . <Control-KeyPress-p> { \
+ global menus_disabled; \
+ if {!$menus_disabled} {try_connect_and_query_all} \
+ }
+ bind . <Control-KeyPress-u> { \
+ global menus_disabled; \
+ if {!$menus_disabled} {query_all 0} \
+ }
+ bind . <Control-KeyPress-r> { \
+ global menus_disabled; \
+ if {!$menus_disabled} {query_all 0} \
+ }
+ bind . <Control-KeyPress-d> { \
+ global menus_disabled; \
+ if {!$menus_disabled} {detach_from_display} \
+ }
+ bind . <Control-KeyPress-a> { \
+ global menus_disabled; \
+ if {!$menus_disabled} {try_connect_and_query_all} \
+ }
}
proc stop_watch {onoff} {
@@ -2337,7 +2413,7 @@ global env x11vnc_prog x11vnc_cmdline x11vnc_xdisplay x11vnc_connect;
global helpall helptext helpremote helplabel hostname;
global all_settings reply_xdisplay always_update
global max_text_height max_text_width
-global menu_var unset_str
+global menu_var unset_str menus_disabled
global bfont
global connected_to_x11vnc
global delay_sleep extra_sleep extra_sleep_split
@@ -2345,6 +2421,7 @@ global cache_all_query_vars
set unset_str "(unset)"
set connected_to_x11vnc 0
+set menus_disabled 0
set max_text_height 40
set max_text_width 90
set bfont -adobe-helvetica-bold-r-*-*-*-120-*-*-*-*-*-*;
diff --git a/x11vnc/tkx11vnc.h b/x11vnc/tkx11vnc.h
index 0f442a2..ddd5f23 100644
--- a/x11vnc/tkx11vnc.h
+++ b/x11vnc/tkx11vnc.h
@@ -79,6 +79,7 @@
" =RA update-all\n"
" =GA clear-all\n"
" --\n"
+" =RA stop+quit \n"
" =GA Quit \n"
"\n"
"Help\n"
@@ -292,6 +293,10 @@
"a -Q query. Examples: \\\"zero:20,20,100,100\\\", \\\"Q:ext_xfixes\\\" \n"
"\"\n"
"\n"
+" set helptext(stop+quit) \"\n"
+"Send the stop command to the x11vnc server, then terminate the tkx11vnc gui.\n"
+"\"\n"
+"\n"
" set helptext(Quit) \"\n"
"Terminate the tkx11vnc gui. Any x11vnc servers will be left running.\n"
"\"\n"
@@ -784,10 +789,16 @@
"}\n"
"\n"
"proc menus_enable {} {\n"
+" global menus_disabled\n"
+"\n"
" menus_state \"normal\"\n"
+" set menus_disabled 0\n"
"}\n"
"\n"
"proc menus_disable {} {\n"
+" global menus_disabled\n"
+"\n"
+" set menus_disabled 1\n"
" menus_state \"disabled\"\n"
"}\n"
"\n"
@@ -1189,6 +1200,8 @@
" set query_result_list [split_query $query]\n"
"\n"
" foreach q $query_result_list {\n"
+" # XXX following will crash if $item is not a good regexp\n"
+" # need to protect it \\Q$item\\E style...\n"
"# if {[regexp \"^$item:\" $q]} {\n"
"# set found $q\n"
"# }\n"
@@ -1415,6 +1428,12 @@
" } elseif {$item == \"all-settings\"} {\n"
" show_all_settings\n"
" return\n"
+" } elseif {$item == \"stop+quit\"} {\n"
+" push_new_value \"stop\" \"stop\" 1 0\n"
+" set_connected no\n"
+" update\n"
+" after 500\n"
+" destroy .\n"
" }\n"
"\n"
" if {[value_is_string $item]} {\n"
@@ -1662,12 +1681,45 @@
" }\n"
"}\n"
"\n"
+"proc update_clients_and_repost {} {\n"
+" global item_cascade menu_m menu_b\n"
+"\n"
+" append_text \"Refreshing connected clients list... \"\n"
+" query_all 1\n"
+" update\n"
+"\n"
+" set saw 0\n"
+" set casc $item_cascade(current)\n"
+" set last [$casc index end]\n"
+" for {set i 0} {$i <= $last} {incr i} {\n"
+" if {[$casc type $i] == \"separator\"} {\n"
+" continue\n"
+" }\n"
+" set name [$casc entrycget $i -label]\n"
+" if {[regexp {^#} $name]} {\n"
+" continue\n"
+" }\n"
+" if {[regexp {^refresh-list} $name]} {\n"
+" continue\n"
+" }\n"
+" if {! $saw} {\n"
+" append_text \"\\n\"\n"
+" }\n"
+" set saw 1\n"
+" append_text \"client: $name\\n\"\n"
+" }\n"
+" if {! $saw} {\n"
+" append_text \"done.\\n\"\n"
+" }\n"
+"}\n"
+"\n"
"proc update_clients_menu {list} {\n"
" global item_cascade\n"
" set subm $item_cascade(current);\n"
" catch {destroy $subm}\n"
" menu $subm -tearoff 0\n"
" $subm add command\n"
+" $subm add command -label \"refresh-list\" -command \"update_clients_and_repost\"\n"
" $subm add separator\n"
" set count 0\n"
" foreach client [split $list \",\"] {\n"
@@ -1768,7 +1820,16 @@
" set menu \"$colf.menu$case.menu\";\n"
" set menu_b($case) $menub\n"
" set menu_m($case) $menu\n"
-" menubutton $menub -text \"$case\" -underline 0 \\\n"
+" set ul 0\n"
+" foreach char [split $case \"\"] {\n"
+" set char [string tolower $char]\n"
+" if {![info exists underlined($char)]} {\n"
+" set underlined($char) 1\n"
+" break\n"
+" }\n"
+" incr ul\n"
+" }\n"
+" menubutton $menub -text \"$case\" -underline $ul \\\n"
" -anchor w -menu $menu -background $fbg \\\n"
" -font $bfont\n"
" pack $menub -side top -fill x\n"
@@ -2042,16 +2103,31 @@
"}\n"
"\n"
"proc key_bindings {} {\n"
-" global env\n"
+" global env menus_disabled\n"
" if {[info exists env(USER)] && $env(USER) == \"runge\"} {\n"
" # quick restart\n"
" bind . <Control-KeyPress-c> {exec $argv0 $argv &; destroy .}\n"
" }\n"
-" bind . <Control-KeyPress-p> {try_connect_and_query_all}\n"
-" bind . <Control-KeyPress-u> {query_all 0}\n"
-" bind . <Control-KeyPress-r> {query_all 0}\n"
-" bind . <Control-KeyPress-d> {detach_from_display}\n"
-" bind . <Control-KeyPress-a> {try_connect_and_query_all}\n"
+" bind . <Control-KeyPress-p> { \\\n"
+" global menus_disabled; \\\n"
+" if {!$menus_disabled} {try_connect_and_query_all} \\\n"
+" }\n"
+" bind . <Control-KeyPress-u> { \\\n"
+" global menus_disabled; \\\n"
+" if {!$menus_disabled} {query_all 0} \\\n"
+" }\n"
+" bind . <Control-KeyPress-r> { \\\n"
+" global menus_disabled; \\\n"
+" if {!$menus_disabled} {query_all 0} \\\n"
+" }\n"
+" bind . <Control-KeyPress-d> { \\\n"
+" global menus_disabled; \\\n"
+" if {!$menus_disabled} {detach_from_display} \\\n"
+" }\n"
+" bind . <Control-KeyPress-a> { \\\n"
+" global menus_disabled; \\\n"
+" if {!$menus_disabled} {try_connect_and_query_all} \\\n"
+" }\n"
"}\n"
"\n"
"proc stop_watch {onoff} {\n"
@@ -2343,7 +2419,7 @@
"global helpall helptext helpremote helplabel hostname;\n"
"global all_settings reply_xdisplay always_update\n"
"global max_text_height max_text_width\n"
-"global menu_var unset_str\n"
+"global menu_var unset_str menus_disabled\n"
"global bfont\n"
"global connected_to_x11vnc\n"
"global delay_sleep extra_sleep extra_sleep_split\n"
@@ -2351,6 +2427,7 @@
"\n"
"set unset_str \"(unset)\"\n"
"set connected_to_x11vnc 0\n"
+"set menus_disabled 0\n"
"set max_text_height 40\n"
"set max_text_width 90\n"
"set bfont -adobe-helvetica-bold-r-*-*-*-120-*-*-*-*-*-*;\n"
diff --git a/x11vnc/x11vnc.1 b/x11vnc/x11vnc.1
index abf7273..2f15efc 100644
--- a/x11vnc/x11vnc.1
+++ b/x11vnc/x11vnc.1
@@ -2,7 +2,7 @@
.TH X11VNC "1" "December 2004" "x11vnc " "User Commands"
.SH NAME
x11vnc - allow VNC connections to real X11 displays
- version: 0.7pre, lastmod: 2004-12-20
+ version: 0.7pre, lastmod: 2004-12-23
.SH SYNOPSIS
.B x11vnc
[OPTION]...
diff --git a/x11vnc/x11vnc.c b/x11vnc/x11vnc.c
index 0a23219..96781a5 100644
--- a/x11vnc/x11vnc.c
+++ b/x11vnc/x11vnc.c
@@ -256,7 +256,7 @@ static int xdamage_base_event_type;
#endif
/* date +'lastmod: %Y-%m-%d' */
-char lastmod[] = "0.7pre lastmod: 2004-12-20";
+char lastmod[] = "0.7pre lastmod: 2004-12-23";
/* X display info */
@@ -732,13 +732,21 @@ int pick_windowid(unsigned long *num) {
if (screen && screen->clientHead) {
/* they may be doing the pointer-pick thru vnc: */
+ int nfds;
tv.tv_sec = 0;
tv.tv_usec = msec * 1000;
FD_ZERO(&set);
FD_SET(fileno(p), &set);
- if (select(fileno(p)+1, &set, NULL, NULL, &tv) == 0) {
- /* note that rfbPE takes about 30ms too */
+
+ nfds = select(fileno(p)+1, &set, NULL, NULL, &tv);
+
+ if (nfds == 0 || nfds < 0) {
+ /*
+ * select timedout or error.
+ * note this rfbPE takes about 30ms too:
+ */
rfbPE(screen, -1);
+ XFlush(dpy);
continue;
}
}
@@ -12585,7 +12593,7 @@ static void check_user_input3(double dt, int tile_diffs) {
int spun_out, missed_out, allowed_misses, g, g_in;
double spin, spin_max, tm, to, dtm, rpe_last;
static int rfb_wait_ms = 2;
- static double grind_spin_time = 0.30, dt_min = 0.075;
+ static double grind_spin_time = 0.30, dt_cut = 0.075;
static double quick_spin_fac = 0.65, spin_max_fac = 2.0;
static double rpe_wait = 0.15;
int grinding, gcnt, ms, split = 200;
@@ -12593,12 +12601,13 @@ static void check_user_input3(double dt, int tile_diffs) {
if (first) {
char *p = getenv("SPIN");
if (p) {
- sscanf(p, "%lf,%lf,%lf", &grind_spin_time, &dt_min, &quick_spin_fac);
+ sscanf(p, "%lf,%lf,%lf", &grind_spin_time, &dt_cut, &quick_spin_fac);
}
first = 0;
}
+
/*
* Try for some "quick" pointer input processing.
*
@@ -12615,8 +12624,8 @@ static void check_user_input3(double dt, int tile_diffs) {
* should continue, if we do so we say we are "grinding"
*/
- if (dt < dt_min) {
- dt = dt_min; /* this is to try to avoid early exit */
+ if (dt < dt_cut) {
+ dt = dt_cut; /* this is to try to avoid early exit */
}
/* max spin time in 1st pass, comparable to last dt */
spin_max = quick_spin_fac * dt;
@@ -12723,8 +12732,167 @@ static void check_user_input3(double dt, int tile_diffs) {
drag_in_progress = 0;
}
+/* quick-n-dirty copy of check_user_input3, merge later... */
+
static void check_user_input4(double dt, int tile_diffs) {
- return;
+
+ int spun_out, missed_out, allowed_misses, g, g_in;
+ double spin, spin_max, tm, to, dtm, rpe_last;
+ static int rfb_wait_ms = 2;
+ static double grind_spin_time = 0.30, dt_cut = 0.075;
+ static double quick_spin_fac = 0.65, spin_max_fac = 2.0;
+ static double rpe_wait = 0.15;
+ int grinding, gcnt, ms, split = 200;
+ static int first = 1;
+
+ int Btile = tile_x * tile_y * bpp/8;
+ double Ttile;
+ double screen_rate = 5000000.; /* 5 MB/sec */
+ double client_rate = 80 * 100000.; /* 20 KB/sec @ 80X compression */
+ static double Tfac = 1.0;
+ static double dt_min = -1.0, dt_max = -1.0;
+
+ if (first) {
+ char *p = getenv("SPIN");
+ if (p) {
+ sscanf(p, "%lf,%lf,%lf,%lf", &grind_spin_time,
+ &dt_cut, &quick_spin_fac, &Tfac);
+ }
+ first = 0;
+ }
+
+ if (dt_min < 0 || dt < dt_min) {
+ dt_min = dt;
+ }
+ if (dt_max < 0 || dt > dt_max) {
+ dt_max = dt;
+ }
+
+ /*
+ * when we first enter we require some pointer input
+ */
+ if (!got_pointer_input) {
+ drag_in_progress = 0;
+ return;
+ }
+
+ Ttile = Btile * (1.0/screen_rate + 1.0/client_rate);
+ Ttile = Tfac * Ttile;
+
+ if (dt < dt_cut) {
+ dt = dt_cut; /* this is to try to avoid early exit */
+ }
+
+ /* max spin time in 1st pass, comparable to last dt */
+ spin_max = quick_spin_fac * dt;
+
+ grinding = 0; /* 1st pass is "not grinding" */
+ spin = 0.0; /* amount of time spinning */
+ spun_out = 0; /* whether we spun out of time */
+ missed_out = 0; /* whether we received no ptr input */
+ allowed_misses = 3; /* number of ptr inputs we can miss */
+ gcnt = 0;
+
+ tm = 0.0; /* timer variable */
+ dtime(&tm);
+ rpe_last = to = tm; /* last time we did rfbPE() */
+ g = g_in = got_pointer_input;
+
+
+ while (1) {
+ int got_input = 0;
+
+ gcnt++;
+ if (grinding) {
+ if (gcnt >= split) {
+ break;
+ }
+ usleep(ms * 1000);
+ }
+
+ if (button_mask) {
+ drag_in_progress = 1;
+ }
+
+ if (show_multiple_cursors && tm > rpe_last + rpe_wait) {
+ rfbPE(screen, rfb_wait_ms * 1000);
+ rpe_last = tm;
+ } else {
+ rfbCFD(screen, rfb_wait_ms * 1000);
+ }
+
+ dtm = dtime(&tm);
+ spin += dtm;
+
+ if (spin > spin_max) {
+ /* get out if spin time over limit */
+ spun_out = 1;
+
+ } else if (tile_diffs > 200 && spin > Ttile * tile_diffs) {
+ /* XXX not finished. */
+ /* we think we can push the frame */
+ break;
+
+ } else if (got_pointer_input > g) {
+ /* received some input, flush to display. */
+ got_input = 1;
+ g = got_pointer_input;
+ XFlush(dpy);
+
+ } else if (--allowed_misses <= 0) {
+ /* too many misses */
+ missed_out = 1;
+
+ } else {
+ /* these are misses */
+ int wms = 0;
+ if (! grinding && gcnt == 1 && button_mask) {
+ /*
+ * missed our first input, wait for
+ * a defer time. (e.g. on slow link)
+ * hopefully client will batch them.
+ */
+ wms = 1000 * (0.5 * (spin_max - spin));
+
+ } else if (button_mask) {
+ wms = 10;
+ }
+ if (wms) {
+ usleep(wms * 1000);
+ }
+ }
+ if (spun_out && ! grinding) {
+ /* set parameters for grinding mode. */
+
+ grinding = 1;
+
+ if (spin > grind_spin_time || button_mask) {
+ spin_max = spin +
+ grind_spin_time * spin_max_fac;
+ } else {
+ spin_max = spin + dt * spin_max_fac;
+ }
+ ms = (int) (1000 * ((spin_max - spin)/split));
+ if (ms < 1) {
+ ms = 1;
+ }
+
+ /* reset for second pass */
+ spun_out = 0;
+ missed_out = 0;
+ allowed_misses = 3;
+ g = got_pointer_input;
+ gcnt = 0;
+
+ } else if (spun_out && grinding) {
+ /* done in 2nd pass */
+ break;
+ } else if (missed_out) {
+ /* done in either pass */
+ break;
+ }
+ }
+ drag_in_progress = 0;
}
static int check_user_input(double dt, int tile_diffs, int *cnt) {