Commit 4cfb5152 authored by Soeren Sandmann's avatar Soeren Sandmann Committed by Søren Sandmann Pedersen

Fix bug 143333, support for update counter spec, and 109362, schedule

Sat Jun 19 02:21:08 2004  Soeren Sandmann  <sandmann@daimi.au.dk>

	Fix bug 143333, support for update counter spec, and 109362,
	schedule compensation events when events are ignored.

	* src/display.c (meta_display_open): Add _NET_WM_SYNC_REQUEST and
	_NET_WM_SYNC_REQUEST_COUNTER atoms. Remove the old
	METACITY_SYNC_COUNTER stuff.
	(meta_display_begin_op): Setup the sync counter

	* src/xprops.c, src/xprops.h, src/window-props.c, src/display.h:
	Add new atoms.

	* src/window.c (send_sync_request): new function.
	(meta_window_move_resize_internal): send a sync request before
	resizing.
	(check_move_resize_frequence): Rework logic to also check the SYNC
	case. If an event is ignored return the remaining time.
	(update_resize_timeout): Timeout that gets called when a
	compensation event is scheduled.
	(uddate_resize): schedule compensation events when an event is
	ignored.
	(meta_window_handle_mouse_grap_op_event): When an alarm is
	received and sync was turned off, turn it back on.

	* src/window.h (struct MetaWindow)  Add some variables
parent 952c1f41
Sat Jun 19 02:21:08 2004 Soeren Sandmann <sandmann@daimi.au.dk>
Fix bug 143333, support for update counter spec, and 109362,
schedule compensation events when events are ignored.
* src/display.c (meta_display_open): Add _NET_WM_SYNC_REQUEST and
_NET_WM_SYNC_REQUEST_COUNTER atoms. Remove the old
METACITY_SYNC_COUNTER stuff.
(meta_display_begin_op): Setup the sync counter
* src/xprops.c, src/xprops.h, src/window-props.c, src/display.h:
Add new atoms.
* src/window.c (send_sync_request): new function.
(meta_window_move_resize_internal): send a sync request before
resizing.
(check_move_resize_frequence): Rework logic to also check the SYNC
case. If an event is ignored return the remaining time.
(update_resize_timeout): Timeout that gets called when a
compensation event is scheduled.
(uddate_resize): schedule compensation events when an event is
ignored.
(meta_window_handle_mouse_grap_op_event): When an alarm is
received and sync was turned off, turn it back on.
* src/window.h (struct MetaWindow) Add some variables
2004-06-16 Havoc Pennington <hp@redhat.com>
* configure.in: bump version, add the UNSTABLE note
......
......@@ -2,7 +2,7 @@
/*
* Copyright (C) 2001 Havoc Pennington
* Copyright (C) 2002, 2003 Red Hat, Inc.
* Copyright (C) 2002, 2003, 2004 Red Hat, Inc.
* Copyright (C) 2003, 2004 Rob Adams
*
* This program is free software; you can redistribute it and/or
......@@ -270,8 +270,8 @@ meta_display_open (const char *name)
"_NET_WM_STATE_BELOW",
"_NET_STARTUP_ID",
"_METACITY_TOGGLE_VERBOSE",
"_METACITY_UPDATE_COUNTER",
"SYNC_COUNTER",
"_NET_WM_SYNC_REQUEST",
"_NET_WM_SYNC_REQUEST_COUNTER",
"_GNOME_PANEL_ACTION",
"_GNOME_PANEL_ACTION_MAIN_MENU",
"_GNOME_PANEL_ACTION_RUN_DIALOG",
......@@ -323,7 +323,7 @@ meta_display_open (const char *name)
display->expected_focus_window = NULL;
#ifdef HAVE_XSYNC
display->grab_update_alarm = None;
display->grab_sync_request_alarm = None;
#endif
/* FIXME copy the checks from GDK probably */
......@@ -416,8 +416,8 @@ meta_display_open (const char *name)
display->atom_net_wm_state_below = atoms[71];
display->atom_net_startup_id = atoms[72];
display->atom_metacity_toggle_verbose = atoms[73];
display->atom_metacity_update_counter = atoms[74];
display->atom_sync_counter = atoms[75];
display->atom_net_wm_sync_request = atoms[74];
display->atom_net_wm_sync_request_counter = atoms[75];
display->atom_gnome_panel_action = atoms[76];
display->atom_gnome_panel_action_main_menu = atoms[77];
display->atom_gnome_panel_action_run_dialog = atoms[78];
......@@ -1264,7 +1264,7 @@ event_callback (XEvent *event,
#ifdef HAVE_XSYNC
if (META_DISPLAY_HAS_XSYNC (display) &&
event->type == (display->xsync_event_base + XSyncAlarmNotify) &&
((XSyncAlarmNotifyEvent*)event)->alarm == display->grab_update_alarm)
((XSyncAlarmNotifyEvent*)event)->alarm == display->grab_sync_request_alarm)
{
filter_out_event = TRUE; /* GTK doesn't want to see this really */
......@@ -1272,7 +1272,7 @@ event_callback (XEvent *event,
display->grab_window != NULL &&
event->xany.serial >= display->grab_start_serial &&
grab_op_is_mouse (display->grab_op))
meta_window_handle_mouse_grab_op_event (display->grab_window, event);
meta_window_handle_mouse_grab_op_event (display->grab_window, event);
}
#endif /* HAVE_XSYNC */
......@@ -2969,10 +2969,16 @@ meta_display_begin_grab_op (MetaDisplay *display,
display->grab_last_moveresize_time.tv_usec = 0;
display->grab_motion_notify_time = 0;
#ifdef HAVE_XSYNC
display->grab_update_alarm = None;
display->grab_sync_request_alarm = None;
#endif
display->grab_was_cancelled = FALSE;
if (display->grab_resize_timeout_id)
{
g_source_remove (display->grab_resize_timeout_id);
display->grab_resize_timeout_id = 0;
}
if (display->grab_window)
{
display->grab_initial_window_pos = display->grab_window->rect;
......@@ -3007,28 +3013,41 @@ meta_display_begin_grab_op (MetaDisplay *display,
#ifdef HAVE_XSYNC
if (!display->grab_wireframe_active &&
meta_grab_op_is_resizing (display->grab_op) &&
display->grab_window->update_counter != None)
display->grab_window->sync_request_counter != None)
{
XSyncAlarmAttributes values;
XSyncValue init;
/* trigger when we make a positive transition to a value
* one higher than the current value.
*/
values.trigger.counter = display->grab_window->update_counter;
values.trigger.value_type = XSyncRelative;
values.trigger.test_type = XSyncPositiveTransition;
XSyncIntToValue (&values.trigger.wait_value, 1);
meta_error_trap_push_with_return (display);
/* Set the counter to 0, so we know that the application's
* responses to the client messages will always trigger
* a PositiveTransition
*/
XSyncIntToValue (&init, 0);
XSyncSetCounter (display->xdisplay,
display->grab_window->sync_request_counter, init);
display->grab_window->sync_request_serial = 0;
display->grab_window->sync_request_time.tv_sec = 0;
display->grab_window->sync_request_time.tv_usec = 0;
values.trigger.counter = display->grab_window->sync_request_counter;
values.trigger.value_type = XSyncAbsolute;
values.trigger.test_type = XSyncPositiveTransition;
XSyncIntToValue (&values.trigger.wait_value,
display->grab_window->sync_request_serial + 1);
/* After triggering, increment test_value by this.
* (NOT wait_value above)
*/
XSyncIntToValue (&values.delta, 1);
/* we want events (on by default anyway) */
values.events = True;
meta_error_trap_push_with_return (display);
display->grab_update_alarm = XSyncCreateAlarm (display->xdisplay,
display->grab_sync_request_alarm = XSyncCreateAlarm (display->xdisplay,
XSyncCACounter |
XSyncCAValueType |
XSyncCAValue |
......@@ -3036,12 +3055,13 @@ meta_display_begin_grab_op (MetaDisplay *display,
XSyncCADelta |
XSyncCAEvents,
&values);
if (meta_error_trap_pop_with_return (display, FALSE) != Success)
display->grab_update_alarm = None;
display->grab_sync_request_alarm = None;
meta_topic (META_DEBUG_RESIZING,
"Created update alarm 0x%lx\n",
display->grab_update_alarm);
display->grab_sync_request_alarm);
}
#endif
}
......@@ -3138,10 +3158,11 @@ meta_display_end_grab_op (MetaDisplay *display,
}
#ifdef HAVE_XSYNC
if (display->grab_update_alarm != None)
if (display->grab_sync_request_alarm != None)
{
XSyncDestroyAlarm (display->xdisplay,
display->grab_update_alarm);
display->grab_sync_request_alarm);
display->grab_sync_request_alarm = None;
}
#endif /* HAVE_XSYNC */
......@@ -3177,6 +3198,12 @@ meta_display_end_grab_op (MetaDisplay *display,
meta_ui_resize_popup_free (display->grab_resize_popup);
display->grab_resize_popup = NULL;
}
if (display->grab_resize_timeout_id)
{
g_source_remove (display->grab_resize_timeout_id);
display->grab_resize_timeout_id = 0;
}
}
static void
......
......@@ -166,8 +166,8 @@ struct _MetaDisplay
Atom atom_net_wm_state_below;
Atom atom_net_startup_id;
Atom atom_metacity_toggle_verbose;
Atom atom_metacity_update_counter;
Atom atom_sync_counter;
Atom atom_net_wm_sync_request;
Atom atom_net_wm_sync_request_counter;
Atom atom_gnome_panel_action;
Atom atom_gnome_panel_action_main_menu;
Atom atom_gnome_panel_action_run_dialog;
......@@ -258,9 +258,10 @@ struct _MetaDisplay
int xkb_base_event_type;
#endif
#ifdef HAVE_XSYNC
/* alarm monitoring client's _METACITY_UPDATE_COUNTER */
XSyncAlarm grab_update_alarm;
/* alarm monitoring client's _NET_WM_SYNC_REQUEST_COUNTER */
XSyncAlarm grab_sync_request_alarm;
#endif
int grab_resize_timeout_id;
/* Keybindings stuff */
MetaKeyBinding *screen_bindings;
......
......@@ -385,7 +385,7 @@ init_update_counter (MetaDisplay *display,
MetaPropValue *value)
{
value->type = META_PROP_VALUE_SYNC_COUNTER;
value->atom = display->atom_metacity_update_counter;
value->atom = display->atom_net_wm_sync_request_counter;
}
static void
......@@ -397,9 +397,9 @@ reload_update_counter (MetaWindow *window,
#ifdef HAVE_XSYNC
XSyncCounter counter = value->v.xcounter;
window->update_counter = counter;
meta_verbose ("Window has _METACITY_UPDATE_COUNTER 0x%lx\n",
window->update_counter);
window->sync_request_counter = counter;
meta_verbose ("Window has _NET_WM_SYNC_REQUEST_COUNTER 0x%lx\n",
window->sync_request_counter);
#endif
}
}
......@@ -924,7 +924,7 @@ meta_display_init_window_prop_hooks (MetaDisplay *display)
hooks[i].reload_func = reload_net_startup_id;
++i;
hooks[i].property = display->atom_metacity_update_counter;
hooks[i].property = display->atom_net_wm_sync_request_counter;
hooks[i].init_func = init_update_counter;
hooks[i].reload_func = reload_update_counter;
++i;
......
......@@ -357,7 +357,10 @@ meta_window_new_with_attrs (MetaDisplay *display,
window->workspaces = NULL;
#ifdef HAVE_XSYNC
window->update_counter = None;
window->sync_request_counter = None;
window->sync_request_serial = 0;
window->sync_request_time.tv_sec = 0;
window->sync_request_time.tv_usec = 0;
#endif
window->screen = NULL;
......@@ -446,6 +449,7 @@ meta_window_new_with_attrs (MetaDisplay *display,
window->calc_placement = FALSE;
window->shaken_loose = FALSE;
window->have_focus_click_grab = FALSE;
window->disable_sync = FALSE;
window->unmaps_pending = 0;
......@@ -528,7 +532,7 @@ meta_window_new_with_attrs (MetaDisplay *display,
initial_props[i++] = XA_WM_ICON_NAME;
initial_props[i++] = display->atom_net_wm_desktop;
initial_props[i++] = display->atom_net_startup_id;
initial_props[i++] = display->atom_metacity_update_counter;
initial_props[i++] = display->atom_net_wm_sync_request_counter;
initial_props[i++] = XA_WM_NORMAL_HINTS;
initial_props[i++] = display->atom_wm_protocols;
initial_props[i++] = XA_WM_HINTS;
......@@ -2297,6 +2301,36 @@ get_mouse_deltas_for_resize (MetaWindow *window,
}
}
#ifdef HAVE_XSYNC
static void
send_sync_request (MetaWindow *window)
{
XSyncValue value;
XClientMessageEvent ev;
window->sync_request_serial++;
XSyncIntToValue (&value, window->sync_request_serial);
ev.type = ClientMessage;
ev.window = window->xwindow;
ev.message_type = window->display->atom_wm_protocols;
ev.format = 32;
ev.data.l[0] = window->display->atom_net_wm_sync_request;
ev.data.l[1] = meta_display_get_current_time (window->display);
ev.data.l[2] = XSyncValueLow32 (value);
ev.data.l[3] = XSyncValueHigh32 (value);
/* We don't need to trap errors here as we are already
* inside an error_trap_push()/pop() pair.
*/
XSendEvent (window->display->xdisplay,
window->xwindow, False, 0, (XEvent*) &ev);
g_get_current_time (&window->sync_request_time);
}
#endif
static void
meta_window_move_resize_internal (MetaWindow *window,
MetaMoveResizeFlags flags,
......@@ -2627,10 +2661,20 @@ meta_window_move_resize_internal (MetaWindow *window,
}
meta_error_trap_push (window->display);
if (window->sync_request_counter != None &&
window->display->grab_sync_request_alarm != None &&
window->sync_request_time.tv_usec == 0 &&
window->sync_request_time.tv_sec == 0)
{
send_sync_request (window);
}
XConfigureWindow (window->display->xdisplay,
window->xwindow,
mask,
&values);
meta_error_trap_pop (window->display, FALSE);
}
......@@ -4364,12 +4408,12 @@ process_property_notify (MetaWindow *window,
meta_window_reload_property (window,
window->display->atom_net_startup_id);
}
else if (event->atom == window->display->atom_metacity_update_counter)
else if (event->atom == window->display->atom_net_wm_sync_request_counter)
{
meta_verbose ("Property notify on %s for _METACITY_UPDATE_COUNTER\n", window->desc);
meta_verbose ("Property notify on %s for _NET_WM_SYNC_REQUEST_COUNTER\n", window->desc);
meta_window_reload_property (window,
window->display->atom_metacity_update_counter);
window->display->atom_net_wm_sync_request_counter);
}
return TRUE;
......@@ -5802,59 +5846,94 @@ meta_window_show_menu (MetaWindow *window,
meta_ui_window_menu_popup (menu, root_x, root_y, button, timestamp);
}
static void
clear_moveresize_time (MetaWindow *window)
static double
timeval_to_ms (const GTimeVal *timeval)
{
/* Forces the next update to actually do something */
window->display->grab_last_moveresize_time.tv_sec = 0;
window->display->grab_last_moveresize_time.tv_usec = 0;
return (timeval->tv_sec * G_USEC_PER_SEC + timeval->tv_usec) / 1000.0;
}
static double
time_diff (const GTimeVal *first,
const GTimeVal *second)
{
double first_ms = timeval_to_ms (first);
double second_ms = timeval_to_ms (second);
return first_ms - second_ms;
}
static gboolean
check_moveresize_frequency (MetaWindow *window)
check_moveresize_frequency (MetaWindow *window,
gdouble *remaining)
{
GTimeVal current_time;
double elapsed;
double max_resizes_per_second;
g_get_current_time (&current_time);
/* use milliseconds, 1000 milliseconds/second */
elapsed =
((((double)current_time.tv_sec - window->display->grab_last_moveresize_time.tv_sec) * G_USEC_PER_SEC +
(current_time.tv_usec - window->display->grab_last_moveresize_time.tv_usec))) / 1000.0;
#ifdef HAVE_XSYNC
if (window->display->grab_update_alarm != None)
max_resizes_per_second = 1.0; /* this is max resizes without
* getting any alarms; we resize
* immediately if we get one.
* i.e. this is a timeout for the
* client getting stuck.
*/
if (!window->disable_sync &&
window->display->grab_sync_request_alarm != None)
{
if (window->sync_request_time.tv_sec != 0 ||
window->sync_request_time.tv_usec != 0)
{
double elapsed =
time_diff (&current_time, &window->sync_request_time);
if (elapsed < 1000.0)
{
/* We want to be sure that the timeout happens at
* a time where elapsed will definitely be
* greater than 1000, so we can disable sync
*/
if (remaining)
*remaining = 1000.0 - elapsed + 100;
return FALSE;
}
else
{
/* We have now waited for more than a second for the
* application to respond to the sync request
*/
window->disable_sync = TRUE;
return TRUE;
}
}
else
{
/* No outstanding sync requests. Go ahead and resize
*/
return TRUE;
}
}
else
#endif /* HAVE_XSYNC */
max_resizes_per_second = 20.0;
#define EPSILON (1e-6)
if (elapsed >= 0.0 && elapsed < (1000.0 / max_resizes_per_second))
{
const double max_resizes_per_second = 25.0;
const double ms_between_resizes = 1000.0 / max_resizes_per_second;
double elapsed;
elapsed = time_diff (&current_time, &window->display->grab_last_moveresize_time);
if (elapsed >= 0.0 && elapsed < ms_between_resizes)
{
meta_topic (META_DEBUG_RESIZING,
"Delaying move/resize as only %g of %g ms elapsed\n",
elapsed, ms_between_resizes);
if (remaining)
*remaining = (ms_between_resizes - elapsed);
return FALSE;
}
meta_topic (META_DEBUG_RESIZING,
"Delaying move/resize as only %g of %g seconds elapsed\n",
elapsed / 1000.0, 1.0 / max_resizes_per_second);
return FALSE;
" Checked moveresize freq, allowing move/resize now (%g of %g seconds elapsed)\n",
elapsed / 1000.0, 1.0 / max_resizes_per_second);
return TRUE;
}
else if (elapsed < (0.0 - EPSILON)) /* handle clock getting set backward */
clear_moveresize_time (window);
/* store latest time */
window->display->grab_last_moveresize_time = current_time;
meta_topic (META_DEBUG_RESIZING,
" Checked moveresize freq, allowing move/resize now (%g of %g seconds elapsed)\n",
elapsed / 1000.0, 1.0 / max_resizes_per_second);
return TRUE;
}
static void
......@@ -6005,15 +6084,34 @@ update_move (MetaWindow *window,
}
}
static void update_resize (MetaWindow *window,
int x,
int y,
gboolean force);
static gboolean
update_resize_timeout (gpointer data)
{
MetaWindow *window = data;
update_resize (window,
window->display->grab_latest_motion_x,
window->display->grab_latest_motion_y,
TRUE);
return FALSE;
}
static void
update_resize (MetaWindow *window,
int x, int y)
int x, int y,
gboolean force)
{
int dx, dy;
int new_w, new_h;
int gravity;
MetaRectangle old;
int new_x, new_y;
double remaining;
window->display->grab_latest_motion_x = x;
window->display->grab_latest_motion_y = y;
......@@ -6077,8 +6175,28 @@ update_resize (MetaWindow *window,
break;
}
if (!check_moveresize_frequency (window))
return;
if (!check_moveresize_frequency (window, &remaining) && !force)
{
/* we are ignoring an event here, so we schedule a
* compensation event when we would otherwise not ignore
* an event. Otherwise we can become stuck if the user never
* generates another event.
*/
if (!window->display->grab_resize_timeout_id)
{
window->display->grab_resize_timeout_id =
g_timeout_add ((int)remaining, update_resize_timeout, window);
}
return;
}
/* Remove any scheduled compensation events */
if (window->display->grab_resize_timeout_id)
{
g_source_remove (window->display->grab_resize_timeout_id);
window->display->grab_resize_timeout_id = 0;
}
old = window->rect;
......@@ -6118,14 +6236,12 @@ update_resize (MetaWindow *window,
meta_window_resize_with_gravity (window, TRUE, new_w, new_h, gravity);
}
/* If we don't actually resize the window, we clear the timestamp,
* so we'll quickly try again. Otherwise you get "stuck" because
* the window doesn't increment its _METACITY_UPDATE_COUNTER when
* nothing happens.
*/
if (window->rect.width == old.width &&
window->rect.height == old.height)
clear_moveresize_time (window);
/* Store the latest resize time, if we actually resized. */
if (window->rect.width != old.width &&
window->rect.height != old.height)
{
g_get_current_time (&window->display->grab_last_moveresize_time);
}
}
typedef struct
......@@ -6216,6 +6332,14 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
"Alarm event received last motion x = %d y = %d\n",
window->display->grab_latest_motion_x,
window->display->grab_latest_motion_y);
/* If sync was previously disabled, turn it back on and hope
* the application has come to its senses (maybe it was just
* busy with a pagefault or a long computation).
*/
window->disable_sync = FALSE;
window->sync_request_time.tv_sec = 0;
window->sync_request_time.tv_usec = 0;
/* This means we are ready for another configure. */
switch (window->display->grab_op)
......@@ -6236,12 +6360,11 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
case META_GRAB_OP_KEYBOARD_RESIZING_NE:
case META_GRAB_OP_KEYBOARD_RESIZING_SW:
case META_GRAB_OP_KEYBOARD_RESIZING_NW:
clear_moveresize_time (window); /* force update to do something */
/* no pointer round trip here, to keep in sync */
update_resize (window,
window->display->grab_latest_motion_x,
window->display->grab_latest_motion_y);
window->display->grab_latest_motion_y,
TRUE);
break;
default:
......@@ -6255,16 +6378,17 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
case ButtonRelease:
if (meta_grab_op_is_moving (window->display->grab_op))
{
clear_moveresize_time (window);
if (event->xbutton.root == window->screen->xroot)
update_move (window, event->xbutton.state,
event->xbutton.x_root, event->xbutton.y_root);
}
else if (meta_grab_op_is_resizing (window->display->grab_op))
{
clear_moveresize_time (window);
if (event->xbutton.root == window->screen->xroot)
update_resize (window, event->xbutton.x_root, event->xbutton.y_root);
update_resize (window,
event->xbutton.x_root,
event->xbutton.y_root,
TRUE);
}
meta_display_end_grab_op (window->display, event->xbutton.time);
......@@ -6291,7 +6415,8 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
event))
update_resize (window,
event->xmotion.x_root,
event->xmotion.y_root);
event->xmotion.y_root,
FALSE);
}
}
break;
......@@ -6311,7 +6436,8 @@ meta_window_handle_mouse_grab_op_event (MetaWindow *window,
if (event->xcrossing.root == window->screen->xroot)
update_resize (window,
event->xcrossing.x_root,
event->xcrossing.y_root);
event->xcrossing.y_root,
FALSE);
}
break;
default:
......
......@@ -243,10 +243,15 @@ struct _MetaWindow
/* if TRUE we have a grab on the focus click buttons */
guint have_focus_click_grab : 1;
/* if TRUE, application is buggy and SYNC resizing is turned off */
guint disable_sync : 1;
#ifdef HAVE_XSYNC
/* XSync update counter */
XSyncCounter update_counter;
XSyncCounter sync_request_counter;
guint sync_request_serial;
GTimeVal sync_request_time;
#endif
/* Number of UnmapNotify that are caused by us, if
......
......@@ -544,7 +544,7 @@ counter_from_results (GetPropertyResults *results,
XSyncCounter *counter_p)
{
if (!validate_or_free_results (results, 32,
results->display->atom_sync_counter,
XA_CARDINAL,
TRUE))
return FALSE;
......@@ -986,7 +986,7 @@ meta_prop_get_values (MetaDisplay *display,
values[i].required_type = XA_WM_SIZE_HINTS;
break;
case META_PROP_VALUE_SYNC_COUNTER:
values[i].required_type = display->atom_sync_counter;
values[i].required_type = XA_CARDINAL;
break;
}
}
......
......@@ -145,7 +145,7 @@ typedef enum
META_PROP_VALUE_WM_HINTS,
META_PROP_VALUE_CLASS_HINT,
META_PROP_VALUE_SIZE_HINTS,
META_PROP_VALUE_SYNC_COUNTER
META_PROP_VALUE_SYNC_COUNTER /* comes back as CARDINAL */
} MetaPropValueType;
/* used to request/return/store property values */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment