Ophir Carmi
Ophir Carmi

Reputation: 2921

How to set the position of a wayland window on the screen?

I don't know how to set the position of the wayland window on the screen.

It seems like it is being set somehow, because on my PC the window seems to be in a repetitive location.

I tried the solution in How to set position of a Wayland client's surface in Weston background?

But it crashes my Ubuntu 18.04 (logs me out).

I wrote an example of displaying an image in a window. The image is displayed but I want to be able to set its position on the screen programmatically. Raw image is here.

compile with gcc wayland_example.cpp -lwayland-client -o wayland_example

run with ./wayland_example <path_to_image>

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <memory.h>
#include <wayland-client.h>

static struct wl_display *display = nullptr;
static struct wl_compositor *compositor = nullptr;
static struct wl_surface *surface;
static struct wl_shell *shell;
static struct wl_shell_surface *shell_surface;
static struct wl_shm *shm;
static struct wl_buffer *buffer;
static struct wl_callback *frame_callback;
static struct wl_registry *registry;

void *shm_data;
uint32_t *im_ptr;

static int w = 0;
static int h = 0;

static void handle_ping(__attribute__((unused)) void *data,
                        struct wl_shell_surface *in_shell_surface, uint32_t serial) {
  wl_shell_surface_pong(in_shell_surface, serial);
}

static void handle_configure(__attribute__((unused)) void *data,
                             __attribute__((unused)) struct wl_shell_surface *in_shell_surface,
                             __attribute__((unused)) uint32_t edges,
                             __attribute__((unused)) int width,
                             __attribute__((unused)) int height) {}

static void handle_popup_done(__attribute__((unused)) void *data,
                              __attribute__((unused)) struct wl_shell_surface *in_shell_surface) {}

static const struct wl_shell_surface_listener kShellSurfaceListener = {
    handle_ping, handle_configure, handle_popup_done};

static int set_cloexec_or_close(int fd) {
  int flags;

  if (fd == -1) {
    return -1;
  }

  flags = fcntl(fd, F_GETFD);
  if (flags == -1) {
    goto err;
  }

  if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) {
    goto err;
  }

  return fd;

err:
  close(fd);
  return -1;
}

static int create_tmpfile_cloexec(char *tmpname) {
  int fd;

#ifdef HAVE_MKOSTEMP
  fd = mkostemp(tmpname, O_CLOEXEC);
  if (fd >= 0) {
    unlink(tmpname);
  }
#else  /* HAVE_MKOSTEMP */
  fd = mkstemp(tmpname);
  if (fd >= 0) {
    fd = set_cloexec_or_close(fd);
    unlink(tmpname);
  }
#endif /* WAYLAND */

  return fd;
}

/*
 * Create a new, unique, anonymous file of the given size, and
 * return the file descriptor for it. The file descriptor is set
 * CLOEXEC. The file is immediately suitable for mmap()'ing
 * the given size at offset zero.
 *
 * The file should not have a permanent backing store like a disk,
 * but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
 *
 * The file name is deleted from the file system.
 *
 * The file is suitable for buffer sharing between processes by
 * transmitting the file descriptor over Unix sockets using the
 * SCM_RIGHTS methods.
 */
static int os_create_anonymous_file(off_t size) {
  static const char kTemplate1[] = "/weston-shared-XXXXXX";

  const char *path = getenv("XDG_RUNTIME_DIR");
  if (!path) {
    errno = ENOENT;
    return -1;
  }

  size_t total_len = strlen(path) + sizeof(kTemplate1);
  char *name = reinterpret_cast<char *>(malloc(total_len));
  if (!name) {
    return -1;
  }
  snprintf(name, total_len, "%s%s", path, kTemplate1);

  int fd = create_tmpfile_cloexec(name);

  free(name);

  if (fd < 0) {
    return -1;
  }

  if (ftruncate(fd, size) < 0) {
    close(fd);
    return -1;
  }

  return fd;
}

static void paint_pixels() {
  uint32_t *pixel = reinterpret_cast<uint32_t *>(shm_data);

  memcpy(pixel, im_ptr, w * h * 4);
}

static void redraw(__attribute__((unused)) void *data,
                   __attribute__((unused)) struct wl_callback *callback,
                   __attribute__((unused)) uint32_t time);

static const struct wl_callback_listener kFrameListener = {redraw};

static void redraw(__attribute__((unused)) void *data,
                   __attribute__((unused)) struct wl_callback *callback,
                   __attribute__((unused)) uint32_t time) {
  wl_callback_destroy(frame_callback);
  wl_surface_damage(surface, 0, 0, w, h);
  paint_pixels();
  frame_callback = wl_surface_frame(surface);
  wl_surface_attach(surface, buffer, 0, 0);
  wl_callback_add_listener(frame_callback, &kFrameListener, nullptr);
  wl_surface_commit(surface);
}

static struct wl_buffer *create_buffer() {
  struct wl_shm_pool *pool;
  int stride = w * 4;  // 4 bytes per pixel
  int size = stride * h;
  int fd;
  struct wl_buffer *buff;

  fd = os_create_anonymous_file(size);
  if (fd < 0) {
    printf("creating a buffer file for %d B failed\n", size);
    exit(1);
  }

  shm_data = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (shm_data == MAP_FAILED) {
    printf("mmap failed\n");
    close(fd);
    exit(1);
  }

  pool = wl_shm_create_pool(shm, fd, size);
  buff = wl_shm_pool_create_buffer(pool, 0, w, h, stride, WL_SHM_FORMAT_XRGB8888);
  wl_shm_pool_destroy(pool);
  return buff;
}

static void create_window() {
  buffer = create_buffer();

  wl_surface_attach(surface, buffer, 0, 0);
  // wl_surface_damage(surface, 0, 0, WIDTH, HEIGHT);
  wl_surface_commit(surface);
}

static void shm_format(__attribute__((unused)) void *data,
                       __attribute__((unused)) struct wl_shm *wl_shm,
                       __attribute__((unused)) uint32_t format) {}

struct wl_shm_listener shm_listener = {shm_format};

static void global_registry_handler(__attribute__((unused)) void *data,
                                    struct wl_registry *in_registry, uint32_t id,
                                    const char *interface,
                                    __attribute__((unused)) uint32_t version) {
  if (strcmp(interface, "wl_compositor") == 0) {
    compositor =
        (struct wl_compositor *)wl_registry_bind(in_registry, id, &wl_compositor_interface, 1);
  } else if (strcmp(interface, "wl_shell") == 0) {
    shell = (struct wl_shell *)wl_registry_bind(in_registry, id, &wl_shell_interface, 1);
  } else if (strcmp(interface, "wl_shm") == 0) {
    shm = (struct wl_shm *)wl_registry_bind(in_registry, id, &wl_shm_interface, 1);
    wl_shm_add_listener(shm, &shm_listener, nullptr);
  }
}

static void global_registry_remover(__attribute__((unused)) void *data,
                                    __attribute__((unused)) struct wl_registry *in_registry,
                                    __attribute__((unused)) uint32_t id) {}

static const struct wl_registry_listener registry_listener = {global_registry_handler,
                                                              global_registry_remover};

int main(int argc, char **argv) {
  w = 640;
  h = 480;

  im_ptr = (uint32_t *)malloc(w * h * 4);

  FILE *f = fopen(argv[1], "rb");
  fread(im_ptr, w * h * 4, 1, f);
  fclose(f);

  display = wl_display_connect(nullptr);
  if (nullptr == display) {
    printf("Can't connect to display\n");
    exit(1);
  }
  printf("connected to display\n");

  registry = wl_display_get_registry(display);
  wl_registry_add_listener(registry, &registry_listener, nullptr);

  wl_display_dispatch(display);
  wl_display_roundtrip(display);

  if (nullptr == compositor) {
    printf("Can't find compositor\n");
    exit(1);
  } else {
    printf("Found compositor\n");
  }

  surface = wl_compositor_create_surface(compositor);
  if (nullptr == surface) {
    printf("Can't create surface\n");
    exit(1);
  } else {
    printf("Created surface\n");
  }

  shell_surface = wl_shell_get_shell_surface(shell, surface);
  if (nullptr == shell_surface) {
    printf("Can't create shell surface\n");
    exit(1);
  } else {
    printf("Created shell surface\n");
  }
  wl_shell_surface_set_toplevel(shell_surface);

  wl_shell_surface_add_listener(shell_surface, &kShellSurfaceListener, nullptr);

  frame_callback = wl_surface_frame(surface);
  wl_callback_add_listener(frame_callback, &kFrameListener, nullptr);

  create_window();
  redraw(nullptr, nullptr, 0);

  if (wl_display_dispatch(display) == -1) {
    return 1;
  }
  getchar();

  wl_display_disconnect(display);
  free(registry);
  registry = nullptr;

  free(im_ptr);

  return 0;
}

Upvotes: 9

Views: 13989

Answers (3)

Lead Vaxeral
Lead Vaxeral

Reputation: 303

wl_shell is deprecated. Use xdg_toplevel instead. It is apart of a separate protocol.

  1. sudo apt install wayland-protocol will install the protocol.
  2. Check /usr/share/wayland-protocol/stable/xdg-shell for xdg-shell.xml.
  3. Use wayland-scanner to generate source and header files from xdg-shell.xml.
  4. Compile your program with the generated files.
wayland-scanner private-code   < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml   > xdg-shell-protocol.c
wayland-scanner client-header   < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml   > xdg-shell-client-protocol.h

To setup a window using this protocol I recommend reading the wayland-book.

Allowing the window to move is as simple as calling xdg_toplevel_move inside your pointer button callback.

static void pointer_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
{
    struct client_state *client_state = data;
    xdg_toplevel_move(client_state->xdg_toplevel, client_state->wl_seat, serial);
}

Upvotes: 2

Juan
Juan

Reputation: 61

This should work (at least it does on Centos, tested on Centos Stream 8 and 9). You can trivially write the steps in a script, or even bind it to a key in case you use to move it to a given position (top-left, top-right, …, as I do):

  1. Install the gnome-extension "Window Calls" (it can be done via Web browser)

  2. Find the windowId under focus and store it in the $wid variable, for instance:

    window_list=$(gdbus call --session --dest org.gnome.Shell --object-path /org/gnome/Shell/Extensions/Windows --method org.gnome.Shell.Extensions.Windows.List)
    wid=$(echo "${window_list:2:-3}" | jq -c '.[] | select (.focus == true) | .id ')
    
  3. Now move the window to any desired position as follows (example):

    x=500
    y=275
    gdbus call --session --dest org.gnome.Shell --object-path /org/gnome/Shell/Extensions/Windows --method org.gnome.Shell.Extensions.Windows.Move ${wid} $x $y
    

Upvotes: 6

TheEagle
TheEagle

Reputation: 5992

Acccording to this, toplevel windows cannot be moved in Wayland, only subsurfaces can be moved. So : What you are trying to do is impossible. If you really have to move the window, you'll have to switch to Xlib, or, even better, use a GUI framework as Qt5 or Gtk+ 3. The only option you have with Wayland is to make the toplevel as big as the screen, create a subsurface with the image and move that subsurface in the toplevel, as that is possible in Wayland. You can find an example on how to create subsurfaces in the subsurface-test.c file from the Weston compositor.

Upvotes: 5

Related Questions