commit f98435b

Michael Forney  ·  2014-01-25 23:38:56 +0000 UTC
parent 23254f5
Add example window manager
4 files changed,  +319, -1
+1, -1
1@@ -19,7 +19,7 @@ VERSION_MINOR   := 0
2 VERSION         := $(VERSION_MAJOR).$(VERSION_MINOR)
3 
4 TARGETS         := swc.pc
5-SUBDIRS         := launch libswc protocol cursor
6+SUBDIRS         := launch libswc protocol cursor example
7 CLEAN_FILES     := $(TARGETS)
8 
9 include config.mk
+3, -0
 1@@ -69,6 +69,9 @@ Finally, run the main event loop.
 2 wl_display_run(display);
 3 ```
 4 
 5+An example window manager that arranges it's windows in a grid can be found in
 6+example/, and can be built with `make example`.
 7+
 8 Why not write a Weston shell plugin?
 9 ------------------------------------
10 In my opinion the goals of Weston and swc are rather orthogonal. Weston seeks to
+15, -0
 1@@ -0,0 +1,15 @@
 2+# swc: example/local.mk
 3+
 4+dir := example
 5+
 6+$(dir)_PACKAGES = wayland-server
 7+
 8+$(dir): $(dir)/wm
 9+
10+$(dir)/wm: $(dir)/wm.o libswc/libswc.so
11+	$(link) $(example_PACKAGE_LIBS) -lm
12+
13+CLEAN_FILES += $(dir)/wm.o $(dir)/wm
14+
15+include common.mk
16+
+300, -0
  1@@ -0,0 +1,300 @@
  2+/* swc: example/wm.c
  3+ *
  4+ * Copyright (c) 2014 Michael Forney
  5+ *
  6+ * Permission is hereby granted, free of charge, to any person obtaining a copy
  7+ * of this software and associated documentation files (the "Software"), to deal
  8+ * in the Software without restriction, including without limitation the rights
  9+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10+ * copies of the Software, and to permit persons to whom the Software is
 11+ * furnished to do so, subject to the following conditions:
 12+ *
 13+ * The above copyright notice and this permission notice shall be included in
 14+ * all copies or substantial portions of the Software.
 15+ *
 16+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 22+ * SOFTWARE.
 23+ */
 24+
 25+#include <stdlib.h>
 26+#include <swc.h>
 27+#include <unistd.h>
 28+#include <wayland-server.h>
 29+#include <xkbcommon/xkbcommon.h>
 30+
 31+struct screen
 32+{
 33+    struct swc_screen * swc;
 34+    struct wl_listener event_listener;
 35+    struct wl_list windows;
 36+    unsigned num_windows;
 37+};
 38+
 39+struct window
 40+{
 41+    struct swc_window * swc;
 42+    struct wl_listener event_listener;
 43+    struct screen * screen;
 44+    struct wl_list link;
 45+};
 46+
 47+static const char * terminal_command[] = { "st-wl", NULL };
 48+static const char * dmenu_command[] = { "dmenu-wl", NULL };
 49+static const uint32_t border_width = 1;
 50+static const uint32_t border_color_active = 0xff333388;
 51+static const uint32_t border_color_normal = 0xff888888;
 52+
 53+static struct screen * active_screen;
 54+static struct window * focused_window;
 55+static struct wl_event_loop * event_loop;
 56+static struct wl_event_source * focus_idle_source;
 57+
 58+/* This is a basic grid arrange function that tries to give each window an
 59+ * equal space. */
 60+static void arrange(struct screen * screen)
 61+{
 62+    struct window * window = NULL;
 63+    unsigned num_columns, num_rows, column_index, row_index;
 64+    struct swc_rectangle geometry;
 65+    struct swc_rectangle * screen_geometry = &screen->swc->usable_geometry;
 66+
 67+    if (screen->num_windows == 0) return;
 68+
 69+    num_columns = round(sqrt(screen->num_windows));
 70+    num_rows = screen->num_windows / num_columns + 1;
 71+    window = wl_container_of(screen->windows.next, window, link);
 72+
 73+    for (column_index = 0; &window->link != &screen->windows; ++column_index)
 74+    {
 75+        geometry.x = screen_geometry->x + border_width
 76+            + screen_geometry->width * column_index / num_columns + border_width;
 77+        geometry.width = screen_geometry->width / num_columns
 78+            - 2 * border_width;
 79+
 80+        if (column_index == screen->num_windows % num_columns)
 81+            --num_rows;
 82+
 83+        for (row_index = 0; row_index < num_rows; ++row_index)
 84+        {
 85+            geometry.y = screen_geometry->y + border_width
 86+                + screen_geometry->height * row_index / num_rows;
 87+            geometry.height = screen_geometry->height / num_rows
 88+                - 2 * border_width;
 89+
 90+            swc_window_set_geometry(window->swc, &geometry);
 91+            window = wl_container_of(window->link.next, window, link);
 92+        }
 93+    }
 94+}
 95+
 96+static void screen_add_window(struct screen * screen, struct window * window)
 97+{
 98+    window->screen = screen;
 99+    wl_list_insert(&screen->windows, &window->link);
100+    ++screen->num_windows;
101+    swc_window_show(window->swc);
102+    arrange(screen);
103+}
104+
105+static void screen_remove_window(struct screen * screen, struct window * window)
106+{
107+    window->screen = NULL;
108+    wl_list_remove(&window->link);
109+    --screen->num_windows;
110+    swc_window_hide(window->swc);
111+    arrange(screen);
112+}
113+
114+/*
115+ * We can't immediately change the focus when the currently focused window is
116+ * being destroyed. This is because the keyboard registers a destroy listener
117+ * for the current focus so it can correctly keep track of it's focus
118+ * internally and set it to NULL. However, now we have two separate destroy
119+ * listeners which want to change the focus to two different things, and it
120+ * isn't defined which order they happen (and if they are adjacent, we end up
121+ * breaking the linked list traversal).
122+ *
123+ * To prevent this, instead of changing the focus immediately, we register an
124+ * idle event so that the focus is changed in the next iteration of the event
125+ * loop.
126+ *
127+ * Another way to solve this issue is internally in swc by requiring that all
128+ * window managers maintain a valid focus at all times. This way, the keyboard
129+ * does not need to register a destroy listener for the focus because it knows
130+ * that the window manager will provide a new focus before the old one becomes
131+ * invalid.
132+ */
133+static void commit_focus(void * data)
134+{
135+    swc_window_focus(focused_window ? focused_window->swc : NULL);
136+    focus_idle_source = NULL;
137+}
138+
139+static void focus(struct window * window)
140+{
141+    /* Add an idle source (if one doesn't already exist) to the event loop to
142+     * actually change the keyboard focus. */
143+    if (!focus_idle_source)
144+    {
145+        focus_idle_source = wl_event_loop_add_idle(event_loop,
146+                                                   &commit_focus, NULL);
147+    }
148+
149+    if (focused_window)
150+    {
151+        swc_window_set_border(focused_window->swc,
152+                              border_color_normal, border_width);
153+    }
154+
155+    if (window)
156+        swc_window_set_border(window->swc, border_color_active, border_width);
157+
158+    focused_window = window;
159+}
160+
161+static void window_event(struct wl_listener * listener, void * data)
162+{
163+    struct swc_event * event = data;
164+    struct window * window = NULL, * next_focus;
165+
166+
167+    window = wl_container_of(listener, window, event_listener);
168+
169+    switch (event->type)
170+    {
171+        case SWC_WINDOW_DESTROYED:
172+            if (focused_window == window)
173+            {
174+                /* Try to find a new focus nearby the old one. */
175+                next_focus = wl_container_of(window->link.next, window, link);
176+
177+                if (&next_focus->link == &window->screen->windows)
178+                {
179+                    next_focus = wl_container_of(window->link.prev,
180+                                                 window, link);
181+
182+                    if (&next_focus->link == &window->screen->windows)
183+                        next_focus = NULL;
184+                }
185+
186+                focus(next_focus);
187+            }
188+
189+            screen_remove_window(window->screen, window);
190+            free(window);
191+            break;
192+        case SWC_WINDOW_STATE_CHANGED:
193+            /* When the window changes state to TOPLEVEL, we can add it to the
194+             * current screen and then rearrange the windows on that screen. */
195+            if (window->swc->state == SWC_WINDOW_STATE_TOPLEVEL)
196+            {
197+                screen_add_window(active_screen, window);
198+                focus(window);
199+            }
200+            break;
201+        case SWC_WINDOW_ENTERED:
202+            focus(window);
203+            break;
204+    }
205+}
206+
207+static void new_window(struct swc_window * swc)
208+{
209+    struct window * window;
210+
211+    window = malloc(sizeof *window);
212+
213+    if (!window)
214+        return;
215+
216+    window->swc = swc;
217+    window->event_listener.notify = &window_event;
218+    window->screen = NULL;
219+
220+    /* Register a listener for the window's event signal. */
221+    wl_signal_add(&swc->event_signal, &window->event_listener);
222+}
223+
224+static void screen_event(struct wl_listener * listener, void * data)
225+{
226+    struct swc_event * event = data;
227+    struct screen * screen = NULL;
228+
229+    screen = wl_container_of(listener, screen, event_listener);
230+
231+    switch (event->type)
232+    {
233+        case SWC_SCREEN_USABLE_GEOMETRY_CHANGED:
234+            /* If the usable geometry of the screen changes, for example when a
235+             * panel is docked to the edge of the screen, we need to rearrange
236+             * the windows to ensure they are all within the new usable
237+             * geometry. */
238+            arrange(screen);
239+            break;
240+    }
241+}
242+
243+static void new_screen(struct swc_screen * swc)
244+{
245+    struct screen * screen;
246+
247+    screen = malloc(sizeof *screen);
248+
249+    if (!screen)
250+        return;
251+
252+    screen->swc = swc;
253+    screen->event_listener.notify = &screen_event;
254+    screen->num_windows = 0;
255+    wl_list_init(&screen->windows);
256+
257+    /* Register a listener for the screen's event signal. */
258+    wl_signal_add(&swc->event_signal, &screen->event_listener);
259+    active_screen = screen;
260+}
261+
262+const struct swc_manager manager = { &new_window, &new_screen };
263+
264+static void spawn(uint32_t time, uint32_t value, void * data)
265+{
266+    char * const * command = data;
267+
268+    if (fork() == 0)
269+    {
270+        execvp(command[0], command);
271+        exit(EXIT_FAILURE);
272+    }
273+}
274+
275+int main(int argc, char * argv[])
276+{
277+    struct wl_display * display;
278+
279+    display = wl_display_create();
280+
281+    if (!display)
282+        return EXIT_FAILURE;
283+
284+    if (wl_display_add_socket(display, NULL) != 0)
285+        return EXIT_FAILURE;
286+
287+    if (wl_display_init_shm(display) != 0)
288+        return EXIT_FAILURE;
289+
290+    if (!swc_initialize(display, NULL, &manager))
291+        return EXIT_FAILURE;
292+
293+    swc_add_key_binding(SWC_MOD_LOGO, XKB_KEY_Return, &spawn, terminal_command);
294+    swc_add_key_binding(SWC_MOD_LOGO, XKB_KEY_r, &spawn, dmenu_command);
295+
296+    event_loop = wl_display_get_event_loop(display);
297+    wl_display_run(display);
298+
299+    return EXIT_SUCCESS;
300+}
301+