diff --git a/include/wlr/xwayland.h b/include/wlr/xwayland.h
index e7598d4c..6198ff22 100644
--- a/include/wlr/xwayland.h
+++ b/include/wlr/xwayland.h
@@ -96,6 +96,11 @@ struct wlr_xwayland_surface {
 	uint32_t hints_urgency;
 	struct wlr_xwayland_surface_size_hints *size_hints;
 
+	// _NET_WM_STATE
+	bool fullscreen;
+	bool maximized_vert;
+	bool maximized_horz;
+
 	struct {
 		struct wl_signal destroy;
 		struct wl_signal request_configure;
diff --git a/xwayland/xwm.c b/xwayland/xwm.c
index 01d09768..b52dfcf7 100644
--- a/xwayland/xwm.c
+++ b/xwayland/xwm.c
@@ -36,6 +36,9 @@ const char *atom_map[ATOM_LAST] = {
 	"_NET_WM_MOVERESIZE",
 	"_NET_WM_NAME",
 	"_NET_SUPPORTING_WM_CHECK",
+	"_NET_WM_STATE_FULLSCREEN",
+	"_NET_WM_STATE_MAXIMIZED_VERT",
+	"_NET_WM_STATE_MAXIMIZED_HORZ",
 };
 
 /* General helpers */
@@ -150,6 +153,30 @@ void xwm_surface_activate(struct wlr_xwm *xwm,
 	xcb_flush(xwm->xcb_conn);
 }
 
+static void xsurface_set_net_wm_state(struct wlr_xwayland_surface *xsurface) {
+	struct wlr_xwm *xwm = xsurface->xwm;
+	uint32_t property[3];
+	int i;
+
+	i = 0;
+	if (xsurface->fullscreen) {
+		property[i++] = xwm->atoms[_NET_WM_STATE_FULLSCREEN];
+	}
+	if (xsurface->maximized_vert) {
+		property[i++] = xwm->atoms[_NET_WM_STATE_MAXIMIZED_VERT];
+	}
+	if (xsurface->maximized_horz) {
+		property[i++] = xwm->atoms[_NET_WM_STATE_MAXIMIZED_HORZ];
+	}
+
+	xcb_change_property(xwm->xcb_conn,
+		XCB_PROP_MODE_REPLACE,
+		xsurface->window_id,
+		xwm->atoms[NET_WM_STATE],
+		XCB_ATOM_ATOM,
+		32, // format
+		i, property);
+}
 
 static void wlr_xwayland_surface_destroy(struct wlr_xwayland_surface *surface) {
 	wl_signal_emit(&surface->events.destroy, surface);
@@ -271,44 +298,6 @@ static void read_surface_parent(struct wlr_xwm *xwm,
 	wl_signal_emit(&surface->events.set_parent, surface);
 }
 
-static void handle_surface_state(struct wlr_xwm *xwm,
-		struct wlr_xwayland_surface *surface, xcb_atom_t *state,
-		size_t state_len, enum net_wm_state_action action) {
-	for (size_t i = 0; i < state_len; i++) {
-		xcb_atom_t atom = state[i];
-		bool found = false;
-		for (size_t j = 0; j < surface->state->length; j++) {
-			xcb_atom_t *cur = surface->state->items[j];
-			if (atom == *cur) {
-				found = true;
-				if (action == NET_WM_STATE_REMOVE ||
-						action == NET_WM_STATE_TOGGLE) {
-					free(surface->state->items[j]);
-					wlr_list_del(surface->state, j);
-				}
-				break;
-			}
-		}
-
-		if (!found && (action == NET_WM_STATE_ADD ||
-				action == NET_WM_STATE_TOGGLE)) {
-			xcb_atom_t *atom_ptr = malloc(sizeof(xcb_atom_t));
-			*atom_ptr = atom;
-			wlr_list_add(surface->state, atom_ptr);
-		}
-	}
-
-	wlr_log(L_DEBUG, "NET_WM_STATE (%zu)", state_len);
-	wl_signal_emit(&surface->events.set_state, surface);
-}
-
-static void read_surface_state(struct wlr_xwm *xwm,
-		struct wlr_xwayland_surface *surface, xcb_get_property_reply_t *reply) {
-	// reply->type == XCB_ATOM_ANY
-	handle_surface_state(xwm, surface, xcb_get_property_value(reply),
-		reply->value_len, NET_WM_STATE_ADD);
-}
-
 static void read_surface_pid(struct wlr_xwm *xwm,
 		struct wlr_xwayland_surface *surface, xcb_get_property_reply_t *reply) {
 	if (reply->type != XCB_ATOM_CARDINAL) {
@@ -480,7 +469,7 @@ static void read_surface_property(struct wlr_xwm *xwm,
 	} else if (property == xwm->atoms[WM_PROTOCOLS]) {
 		read_surface_protocols(xwm, surface, reply);
 	} else if (property == xwm->atoms[NET_WM_STATE]) {
-		read_surface_state(xwm, surface, reply);
+		wlr_log(L_DEBUG, "TODO: read _NET_WM_STATE property");
 	} else if (property == xwm->atoms[WM_HINTS]) {
 		read_surface_hints(xwm, surface, reply);
 	} else if (property == xwm->atoms[WM_NORMAL_HINTS]) {
@@ -645,6 +634,7 @@ static void handle_map_request(struct wlr_xwm *xwm,
 	}
 
 	xsurface_set_wm_state(xsurface, ICCCM_NORMAL_STATE);
+	xsurface_set_net_wm_state(xsurface);
 	xcb_map_window(xwm->xcb_conn, ev->window);
 }
 
@@ -716,16 +706,6 @@ static void handle_surface_id_message(struct wlr_xwm *xwm,
 	}
 }
 
-static void handle_net_wm_state_message(struct wlr_xwm *xwm,
-		xcb_client_message_event_t *ev) {
-	struct wlr_xwayland_surface *xsurface = lookup_surface(xwm, ev->window);
-	if (xsurface == NULL) {
-		return;
-	}
-	handle_surface_state(xwm, xsurface, &ev->data.data32[1], 2,
-		ev->data.data32[0]);
-}
-
 static void handle_net_wm_moveresize_message(struct wlr_xwm *xwm,
 		xcb_client_message_event_t *ev) {
 	wlr_log(L_DEBUG, "TODO: handle moveresize");
@@ -738,7 +718,7 @@ static void handle_client_message(struct wlr_xwm *xwm,
 	if (ev->type == xwm->atoms[WL_SURFACE_ID]) {
 		handle_surface_id_message(xwm, ev);
 	} else if (ev->type == xwm->atoms[NET_WM_STATE]) {
-		handle_net_wm_state_message(xwm, ev);
+		wlr_log(L_DEBUG, "TODO: handle _NET_WM_STATE client message");
 	} else if (ev->type == xwm->atoms[_NET_WM_MOVERESIZE]) {
 		handle_net_wm_moveresize_message(xwm, ev);
 	} else {
@@ -1075,6 +1055,8 @@ struct wlr_xwm *xwm_create(struct wlr_xwayland *wlr_xwayland) {
 		sizeof(supported)/sizeof(*supported),
 		supported);
 
+	xcb_flush(xwm->xcb_conn);
+
 	xwm_set_net_active_window(xwm, XCB_WINDOW_NONE);
 
 	xwm->compositor_surface_create.notify = handle_compositor_surface_create;
diff --git a/xwayland/xwm.h b/xwayland/xwm.h
index 05fcfa7e..4f8c20a2 100644
--- a/xwayland/xwm.h
+++ b/xwayland/xwm.h
@@ -25,6 +25,9 @@ enum atom_name {
 	_NET_WM_MOVERESIZE,
 	_NET_WM_NAME,
 	_NET_SUPPORTING_WM_CHECK,
+	_NET_WM_STATE_FULLSCREEN,
+	_NET_WM_STATE_MAXIMIZED_VERT,
+	_NET_WM_STATE_MAXIMIZED_HORZ,
 	ATOM_LAST,
 };