Merge branch 'startup-notification' into next
This commit is contained in:
commit
819980ce2b
2
DEPENDS
2
DEPENDS
|
@ -21,7 +21,9 @@
|
||||||
│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │
|
│ libxcursor │ 1.1.11 │ 1.1.11 │ http://ftp.x.org/pub/current/src/lib/ │
|
||||||
│ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │
|
│ Xlib │ 1.3.3 │ 1.4.3 │ http://ftp.x.org/pub/current/src/lib/ │
|
||||||
│ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │
|
│ PCRE │ 8.12 │ 8.12 │ http://www.pcre.org/ │
|
||||||
|
│ libsn¹ │ 0.12 │ 0.12 │ http://freedesktop.org/wiki/Software/startup-notification
|
||||||
└─────────────┴────────┴────────┴────────────────────────────────────────┘
|
└─────────────┴────────┴────────┴────────────────────────────────────────┘
|
||||||
|
¹ libsn = libstartup-notification
|
||||||
|
|
||||||
i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
|
i3-msg, i3-input, i3-nagbar and i3-config-wizard do not introduce any new
|
||||||
dependencies.
|
dependencies.
|
||||||
|
|
|
@ -57,6 +57,7 @@ CFLAGS += $(call cflags_for_lib, x11)
|
||||||
CFLAGS += $(call cflags_for_lib, yajl)
|
CFLAGS += $(call cflags_for_lib, yajl)
|
||||||
CFLAGS += $(call cflags_for_lib, libev)
|
CFLAGS += $(call cflags_for_lib, libev)
|
||||||
CFLAGS += $(call cflags_for_lib, libpcre)
|
CFLAGS += $(call cflags_for_lib, libpcre)
|
||||||
|
CFLAGS += $(call cflags_for_lib, libstartup-notification-1.0)
|
||||||
CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
|
CPPFLAGS += -DI3_VERSION=\"${GIT_VERSION}\"
|
||||||
CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
|
CPPFLAGS += -DSYSCONFDIR=\"${SYSCONFDIR}\"
|
||||||
CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
|
CPPFLAGS += -DTERM_EMU=\"$(TERM_EMU)\"
|
||||||
|
@ -84,6 +85,7 @@ LIBS += $(call ldflags_for_lib, x11,X11)
|
||||||
LIBS += $(call ldflags_for_lib, yajl,yajl)
|
LIBS += $(call ldflags_for_lib, yajl,yajl)
|
||||||
LIBS += $(call ldflags_for_lib, libev,ev)
|
LIBS += $(call ldflags_for_lib, libev,ev)
|
||||||
LIBS += $(call ldflags_for_lib, libpcre,pcre)
|
LIBS += $(call ldflags_for_lib, libpcre,pcre)
|
||||||
|
LIBS += $(call ldflags_for_lib, libstartup-notification-1.0,startup-notification-1)
|
||||||
|
|
||||||
# Please test if -Wl,--as-needed works on your platform and send me a patch.
|
# Please test if -Wl,--as-needed works on your platform and send me a patch.
|
||||||
# it is known not to work on Darwin (Mac OS X)
|
# it is known not to work on Darwin (Mac OS X)
|
||||||
|
|
|
@ -3,7 +3,7 @@ Section: utils
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: Michael Stapelberg <michael@stapelberg.de>
|
Maintainer: Michael Stapelberg <michael@stapelberg.de>
|
||||||
DM-Upload-Allowed: yes
|
DM-Upload-Allowed: yes
|
||||||
Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev
|
Build-Depends: debhelper (>= 6), libx11-dev, libxcb-util0-dev (>= 0.3.8), libxcb-keysyms1-dev, libxcb-xinerama0-dev (>= 1.1), libxcb-randr0-dev, libxcb-icccm4-dev, libxcursor-dev, asciidoc (>= 8.4.4), xmlto, docbook-xml, pkg-config, libev-dev, flex, bison, libyajl-dev, perl, texlive-latex-base, texlive-latex-recommended, texlive-latex-extra, libpcre3-dev, libstartup-notification0-dev (>= 0.12-1)
|
||||||
Standards-Version: 3.9.2
|
Standards-Version: 3.9.2
|
||||||
Homepage: http://i3wm.org/
|
Homepage: http://i3wm.org/
|
||||||
|
|
||||||
|
|
|
@ -66,5 +66,6 @@
|
||||||
#include "assignments.h"
|
#include "assignments.h"
|
||||||
#include "regex.h"
|
#include "regex.h"
|
||||||
#include "libi3.h"
|
#include "libi3.h"
|
||||||
|
#include "startup.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -15,6 +15,7 @@ xmacro(_NET_CLIENT_LIST_STACKING)
|
||||||
xmacro(_NET_CURRENT_DESKTOP)
|
xmacro(_NET_CURRENT_DESKTOP)
|
||||||
xmacro(_NET_ACTIVE_WINDOW)
|
xmacro(_NET_ACTIVE_WINDOW)
|
||||||
xmacro(_NET_WORKAREA)
|
xmacro(_NET_WORKAREA)
|
||||||
|
xmacro(_NET_STARTUP_ID)
|
||||||
xmacro(WM_PROTOCOLS)
|
xmacro(WM_PROTOCOLS)
|
||||||
xmacro(WM_DELETE_WINDOW)
|
xmacro(WM_DELETE_WINDOW)
|
||||||
xmacro(UTF8_STRING)
|
xmacro(UTF8_STRING)
|
||||||
|
|
|
@ -7,13 +7,18 @@
|
||||||
* include/data.h: This file defines all data structures used by i3
|
* include/data.h: This file defines all data structures used by i3
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#ifndef _DATA_H
|
||||||
|
#define _DATA_H
|
||||||
|
|
||||||
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
|
#include <libsn/sn-launcher.h>
|
||||||
|
|
||||||
#include <xcb/randr.h>
|
#include <xcb/randr.h>
|
||||||
#include <xcb/xcb_atom.h>
|
#include <xcb/xcb_atom.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <pcre.h>
|
#include <pcre.h>
|
||||||
|
|
||||||
#ifndef _DATA_H
|
|
||||||
#define _DATA_H
|
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -138,6 +143,22 @@ struct Ignore_Event {
|
||||||
SLIST_ENTRY(Ignore_Event) ignore_events;
|
SLIST_ENTRY(Ignore_Event) ignore_events;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores internal information about a startup sequence, like the workspace it
|
||||||
|
* was initiated on.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct Startup_Sequence {
|
||||||
|
/** startup ID for this sequence, generated by libstartup-notification */
|
||||||
|
char *id;
|
||||||
|
/** workspace on which this startup was initiated */
|
||||||
|
char *workspace;
|
||||||
|
/** libstartup-notification context for this launch */
|
||||||
|
SnLauncherContext *context;
|
||||||
|
|
||||||
|
TAILQ_ENTRY(Startup_Sequence) sequences;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regular expression wrapper. It contains the pattern itself as a string (like
|
* Regular expression wrapper. It contains the pattern itself as a string (like
|
||||||
* ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the
|
* ^foo[0-9]$) as well as a pointer to the compiled PCRE expression and the
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
|
|
||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
|
|
||||||
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
|
#include <libsn/sn-launcher.h>
|
||||||
|
|
||||||
#include "queue.h"
|
#include "queue.h"
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include "xcb.h"
|
#include "xcb.h"
|
||||||
|
@ -20,6 +23,12 @@
|
||||||
#define _I3_H
|
#define _I3_H
|
||||||
|
|
||||||
extern xcb_connection_t *conn;
|
extern xcb_connection_t *conn;
|
||||||
|
extern int conn_screen;
|
||||||
|
/** The last timestamp we got from X11 (timestamps are included in some events
|
||||||
|
* and are used for some things, like determining a unique ID in startup
|
||||||
|
* notification). */
|
||||||
|
extern xcb_timestamp_t last_timestamp;
|
||||||
|
extern SnDisplay *sndisplay;
|
||||||
extern xcb_key_symbols_t *keysyms;
|
extern xcb_key_symbols_t *keysyms;
|
||||||
extern char **start_argv;
|
extern char **start_argv;
|
||||||
extern Display *xlibdpy, *xkbdpy;
|
extern Display *xlibdpy, *xkbdpy;
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* vim:ts=4:sw=4:expandtab
|
||||||
|
*
|
||||||
|
* i3 - an improved dynamic tiling window manager
|
||||||
|
*
|
||||||
|
* © 2009-2011 Michael Stapelberg and contributors
|
||||||
|
*
|
||||||
|
* See file LICENSE for license information.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef _STARTUP_H
|
||||||
|
#define _STARTUP_H
|
||||||
|
|
||||||
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
|
#include <libsn/sn-monitor.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the given application by passing it through a shell. We use double
|
||||||
|
* fork to avoid zombie processes. As the started application’s parent exits
|
||||||
|
* (immediately), the application is reparented to init (process-id 1), which
|
||||||
|
* correctly handles childs, so we don’t have to do it :-).
|
||||||
|
*
|
||||||
|
* The shell is determined by looking for the SHELL environment variable. If
|
||||||
|
* it does not exist, /bin/sh is used.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void start_application(const char *command);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by libstartup-notification when something happens
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void startup_monitor_event(SnMonitorEvent *event, void *userdata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the given window belongs to a startup notification by checking if
|
||||||
|
* the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
|
||||||
|
* unset).
|
||||||
|
*
|
||||||
|
* If so, returns the workspace on which the startup was initiated.
|
||||||
|
* Returns NULL otherwise.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply);
|
||||||
|
|
||||||
|
#endif
|
|
@ -66,18 +66,6 @@ Rect rect_add(Rect a, Rect b);
|
||||||
*/
|
*/
|
||||||
bool update_if_necessary(uint32_t *destination, const uint32_t new_value);
|
bool update_if_necessary(uint32_t *destination, const uint32_t new_value);
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the given application by passing it through a shell. We use double
|
|
||||||
* fork to avoid zombie processes. As the started application’s parent exits
|
|
||||||
* (immediately), the application is reparented to init (process-id 1), which
|
|
||||||
* correctly handles childs, so we don’t have to do it :-).
|
|
||||||
*
|
|
||||||
* The shell is determined by looking for the SHELL environment variable. If
|
|
||||||
* it does not exist, /bin/sh is used.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void start_application(const char *command);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* exec()s an i3 utility, for example the config file migration script or
|
* exec()s an i3 utility, for example the config file migration script or
|
||||||
* i3-nagbar. This function first searches $PATH for the given utility named,
|
* i3-nagbar. This function first searches $PATH for the given utility named,
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#define XCB_CURSOR_LEFT_PTR 68
|
#define XCB_CURSOR_LEFT_PTR 68
|
||||||
#define XCB_CURSOR_SB_H_DOUBLE_ARROW 108
|
#define XCB_CURSOR_SB_H_DOUBLE_ARROW 108
|
||||||
#define XCB_CURSOR_SB_V_DOUBLE_ARROW 116
|
#define XCB_CURSOR_SB_V_DOUBLE_ARROW 116
|
||||||
|
#define XCB_CURSOR_WATCH 150
|
||||||
|
|
||||||
/* from X11/keysymdef.h */
|
/* from X11/keysymdef.h */
|
||||||
#define XCB_NUM_LOCK 0xff7f
|
#define XCB_NUM_LOCK 0xff7f
|
||||||
|
@ -150,4 +151,12 @@ bool xcb_reply_contains_atom(xcb_get_property_reply_t *prop, xcb_atom_t atom);
|
||||||
*/
|
*/
|
||||||
void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect);
|
void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the cursor of the root window to the given cursor id.
|
||||||
|
* This function should only be used if xcursor_supported == false.
|
||||||
|
* Otherwise, use xcursor_set_root_cursor().
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void xcb_set_root_cursor(int cursor);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,6 +10,7 @@ enum xcursor_cursor_t {
|
||||||
XCURSOR_CURSOR_POINTER = 0,
|
XCURSOR_CURSOR_POINTER = 0,
|
||||||
XCURSOR_CURSOR_RESIZE_HORIZONTAL,
|
XCURSOR_CURSOR_RESIZE_HORIZONTAL,
|
||||||
XCURSOR_CURSOR_RESIZE_VERTICAL,
|
XCURSOR_CURSOR_RESIZE_VERTICAL,
|
||||||
|
XCURSOR_CURSOR_WATCH,
|
||||||
XCURSOR_CURSOR_MAX
|
XCURSOR_CURSOR_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -259,6 +259,8 @@ int handle_button_press(xcb_button_press_event_t *event) {
|
||||||
Con *con;
|
Con *con;
|
||||||
DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
|
DLOG("Button %d pressed on window 0x%08x\n", event->state, event->event);
|
||||||
|
|
||||||
|
last_timestamp = event->time;
|
||||||
|
|
||||||
const uint32_t mod = config.floating_modifier;
|
const uint32_t mod = config.floating_modifier;
|
||||||
bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
|
bool mod_pressed = (mod != 0 && (event->state & mod) == mod);
|
||||||
DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
|
DLOG("floating_mod = %d, detail = %d\n", mod_pressed, event->detail);
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* vim:ts=4:sw=4:expandtab
|
* vim:ts=4:sw=4:expandtab
|
||||||
*
|
*
|
||||||
* i3 - an improved dynamic tiling window manager
|
* i3 - an improved dynamic tiling window manager
|
||||||
* © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
|
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
@ -11,6 +11,9 @@
|
||||||
|
|
||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
|
|
||||||
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
|
#include <libsn/sn-monitor.h>
|
||||||
|
|
||||||
#include "all.h"
|
#include "all.h"
|
||||||
|
|
||||||
int randr_base = -1;
|
int randr_base = -1;
|
||||||
|
@ -80,6 +83,9 @@ bool event_is_ignored(const int sequence, const int response_type) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static int handle_key_press(xcb_key_press_event_t *event) {
|
static int handle_key_press(xcb_key_press_event_t *event) {
|
||||||
|
|
||||||
|
last_timestamp = event->time;
|
||||||
|
|
||||||
DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
|
DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
|
||||||
|
|
||||||
/* Remove the numlock bit, all other bits are modifiers we can bind to */
|
/* Remove the numlock bit, all other bits are modifiers we can bind to */
|
||||||
|
@ -156,6 +162,8 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
|
||||||
static int handle_enter_notify(xcb_enter_notify_event_t *event) {
|
static int handle_enter_notify(xcb_enter_notify_event_t *event) {
|
||||||
Con *con;
|
Con *con;
|
||||||
|
|
||||||
|
last_timestamp = event->time;
|
||||||
|
|
||||||
DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n",
|
DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n",
|
||||||
event->event, event->mode, event->detail, event->sequence);
|
event->event, event->mode, event->detail, event->sequence);
|
||||||
DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
|
DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
|
||||||
|
@ -227,6 +235,9 @@ static int handle_enter_notify(xcb_enter_notify_event_t *event) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static int handle_motion_notify(xcb_motion_notify_event_t *event) {
|
static int handle_motion_notify(xcb_motion_notify_event_t *event) {
|
||||||
|
|
||||||
|
last_timestamp = event->time;
|
||||||
|
|
||||||
/* Skip events where the pointer was over a child window, we are only
|
/* Skip events where the pointer was over a child window, we are only
|
||||||
* interested in events on the root window. */
|
* interested in events on the root window. */
|
||||||
if (event->child != 0)
|
if (event->child != 0)
|
||||||
|
@ -619,20 +630,25 @@ static int handle_expose_event(xcb_expose_event_t *event) {
|
||||||
* Handle client messages (EWMH)
|
* Handle client messages (EWMH)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
static int handle_client_message(xcb_client_message_event_t *event) {
|
static void handle_client_message(xcb_client_message_event_t *event) {
|
||||||
|
/* If this is a startup notification ClientMessage, the library will handle
|
||||||
|
* it and call our monitor_event() callback. */
|
||||||
|
if (sn_xcb_display_process_event(sndisplay, (xcb_generic_event_t*)event))
|
||||||
|
return;
|
||||||
|
|
||||||
LOG("ClientMessage for window 0x%08x\n", event->window);
|
LOG("ClientMessage for window 0x%08x\n", event->window);
|
||||||
if (event->type == A__NET_WM_STATE) {
|
if (event->type == A__NET_WM_STATE) {
|
||||||
if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
|
if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
|
||||||
DLOG("atom in clientmessage is %d, fullscreen is %d\n",
|
DLOG("atom in clientmessage is %d, fullscreen is %d\n",
|
||||||
event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
|
event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
|
||||||
DLOG("not about fullscreen atom\n");
|
DLOG("not about fullscreen atom\n");
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Con *con = con_by_window_id(event->window);
|
Con *con = con_by_window_id(event->window);
|
||||||
if (con == NULL) {
|
if (con == NULL) {
|
||||||
DLOG("Could not get window for client message\n");
|
DLOG("Could not get window for client message\n");
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if the fullscreen state should be toggled */
|
/* Check if the fullscreen state should be toggled */
|
||||||
|
@ -669,10 +685,8 @@ static int handle_client_message(xcb_client_message_event_t *event) {
|
||||||
free(reply);
|
free(reply);
|
||||||
} else {
|
} else {
|
||||||
ELOG("unhandled clientmessage\n");
|
ELOG("unhandled clientmessage\n");
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
@ -978,6 +992,9 @@ static struct property_handler_t property_handlers[] = {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void property_handlers_init() {
|
void property_handlers_init() {
|
||||||
|
|
||||||
|
sn_monitor_context_new(sndisplay, conn_screen, startup_monitor_event, NULL, NULL);
|
||||||
|
|
||||||
property_handlers[0].atom = A__NET_WM_NAME;
|
property_handlers[0].atom = A__NET_WM_NAME;
|
||||||
property_handlers[1].atom = XCB_ATOM_WM_HINTS;
|
property_handlers[1].atom = XCB_ATOM_WM_HINTS;
|
||||||
property_handlers[2].atom = XCB_ATOM_WM_NAME;
|
property_handlers[2].atom = XCB_ATOM_WM_NAME;
|
||||||
|
@ -1084,6 +1101,7 @@ void handle_event(int type, xcb_generic_event_t *event) {
|
||||||
case XCB_PROPERTY_NOTIFY:
|
case XCB_PROPERTY_NOTIFY:
|
||||||
DLOG("Property notify\n");
|
DLOG("Property notify\n");
|
||||||
xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
|
xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
|
||||||
|
last_timestamp = e->time;
|
||||||
property_notify(e->state, e->window, e->atom);
|
property_notify(e->state, e->window, e->atom);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
31
src/main.c
31
src/main.c
|
@ -19,6 +19,16 @@ extern Con *focused;
|
||||||
char **start_argv;
|
char **start_argv;
|
||||||
|
|
||||||
xcb_connection_t *conn;
|
xcb_connection_t *conn;
|
||||||
|
/* The screen (0 when you are using DISPLAY=:0) of the connection 'conn' */
|
||||||
|
int conn_screen;
|
||||||
|
|
||||||
|
/* Display handle for libstartup-notification */
|
||||||
|
SnDisplay *sndisplay;
|
||||||
|
|
||||||
|
/* The last timestamp we got from X11 (timestamps are included in some events
|
||||||
|
* and are used for some things, like determining a unique ID in startup
|
||||||
|
* notification). */
|
||||||
|
xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME;
|
||||||
|
|
||||||
xcb_screen_t *root_screen;
|
xcb_screen_t *root_screen;
|
||||||
xcb_window_t root;
|
xcb_window_t root;
|
||||||
|
@ -175,7 +185,6 @@ static void i3_exit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
int screens;
|
|
||||||
char *override_configpath = NULL;
|
char *override_configpath = NULL;
|
||||||
bool autostart = true;
|
bool autostart = true;
|
||||||
char *layout_path = NULL;
|
char *layout_path = NULL;
|
||||||
|
@ -357,10 +366,12 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
LOG("i3 (tree) version " I3_VERSION " starting\n");
|
LOG("i3 (tree) version " I3_VERSION " starting\n");
|
||||||
|
|
||||||
conn = xcb_connect(NULL, &screens);
|
conn = xcb_connect(NULL, &conn_screen);
|
||||||
if (xcb_connection_has_error(conn))
|
if (xcb_connection_has_error(conn))
|
||||||
errx(EXIT_FAILURE, "Cannot open display\n");
|
errx(EXIT_FAILURE, "Cannot open display\n");
|
||||||
|
|
||||||
|
sndisplay = sn_xcb_display_new(conn, NULL, NULL);
|
||||||
|
|
||||||
/* Initialize the libev event loop. This needs to be done before loading
|
/* Initialize the libev event loop. This needs to be done before loading
|
||||||
* the config file because the parser will install an ev_child watcher
|
* the config file because the parser will install an ev_child watcher
|
||||||
* for the nagbar when config errors are found. */
|
* for the nagbar when config errors are found. */
|
||||||
|
@ -368,7 +379,7 @@ int main(int argc, char *argv[]) {
|
||||||
if (main_loop == NULL)
|
if (main_loop == NULL)
|
||||||
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
|
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
|
||||||
|
|
||||||
root_screen = xcb_aux_get_screen(conn, screens);
|
root_screen = xcb_aux_get_screen(conn, conn_screen);
|
||||||
root = root_screen->root;
|
root = root_screen->root;
|
||||||
root_depth = root_screen->root_depth;
|
root_depth = root_screen->root_depth;
|
||||||
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
|
xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root);
|
||||||
|
@ -431,17 +442,9 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
/* Set a cursor for the root window (otherwise the root window will show no
|
/* Set a cursor for the root window (otherwise the root window will show no
|
||||||
cursor until the first client is launched). */
|
cursor until the first client is launched). */
|
||||||
if (xcursor_supported) {
|
if (xcursor_supported)
|
||||||
xcursor_set_root_cursor();
|
xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
|
||||||
} else {
|
else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
|
||||||
xcb_cursor_t cursor_id = xcb_generate_id(conn);
|
|
||||||
i3Font cursor_font = load_font("cursor", false);
|
|
||||||
int xcb_cursor = xcursor_get_xcb_cursor(XCURSOR_CURSOR_POINTER);
|
|
||||||
xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
|
|
||||||
xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
|
|
||||||
xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
|
|
||||||
xcb_free_cursor(conn, cursor_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xkb_supported) {
|
if (xkb_supported) {
|
||||||
int errBase,
|
int errBase,
|
||||||
|
|
17
src/manage.c
17
src/manage.c
|
@ -84,7 +84,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||||
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
|
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
|
||||||
utf8_title_cookie, title_cookie,
|
utf8_title_cookie, title_cookie,
|
||||||
class_cookie, leader_cookie, transient_cookie,
|
class_cookie, leader_cookie, transient_cookie,
|
||||||
role_cookie;
|
role_cookie, startup_id_cookie;
|
||||||
|
|
||||||
|
|
||||||
geomc = xcb_get_geometry(conn, d);
|
geomc = xcb_get_geometry(conn, d);
|
||||||
|
@ -147,6 +147,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||||
title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128);
|
title_cookie = GET_PROPERTY(XCB_ATOM_WM_NAME, 128);
|
||||||
class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128);
|
class_cookie = GET_PROPERTY(XCB_ATOM_WM_CLASS, 128);
|
||||||
role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128);
|
role_cookie = GET_PROPERTY(A_WM_WINDOW_ROLE, 128);
|
||||||
|
startup_id_cookie = GET_PROPERTY(A__NET_STARTUP_ID, 512);
|
||||||
/* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
|
/* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
|
||||||
|
|
||||||
DLOG("reparenting!\n");
|
DLOG("reparenting!\n");
|
||||||
|
@ -175,6 +176,11 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||||
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
|
window_update_strut_partial(cwindow, xcb_get_property_reply(conn, strut_cookie, NULL));
|
||||||
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
|
window_update_role(cwindow, xcb_get_property_reply(conn, role_cookie, NULL), true);
|
||||||
|
|
||||||
|
xcb_get_property_reply_t *startup_id_reply;
|
||||||
|
startup_id_reply = xcb_get_property_reply(conn, startup_id_cookie, NULL);
|
||||||
|
char *startup_ws = startup_workspace_for_window(cwindow, startup_id_reply);
|
||||||
|
DLOG("startup workspace = %s\n", startup_ws);
|
||||||
|
|
||||||
/* check if the window needs WM_TAKE_FOCUS */
|
/* check if the window needs WM_TAKE_FOCUS */
|
||||||
cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
|
cwindow->needs_take_focus = window_supports_protocol(cwindow->id, A_WM_TAKE_FOCUS);
|
||||||
|
|
||||||
|
@ -233,6 +239,15 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
|
||||||
else nc = tree_open_con(nc->parent, cwindow);
|
else nc = tree_open_con(nc->parent, cwindow);
|
||||||
}
|
}
|
||||||
/* TODO: handle assignments with type == A_TO_OUTPUT */
|
/* TODO: handle assignments with type == A_TO_OUTPUT */
|
||||||
|
} else if (startup_ws) {
|
||||||
|
/* If it’s not assigned, but was started on a specific workspace,
|
||||||
|
* we want to open it there */
|
||||||
|
DLOG("Using workspace on which this application was started (%s)\n", startup_ws);
|
||||||
|
nc = con_descend_tiling_focused(workspace_get(startup_ws, NULL));
|
||||||
|
DLOG("focused on ws %s: %p / %s\n", startup_ws, nc, nc->name);
|
||||||
|
if (nc->type == CT_WORKSPACE)
|
||||||
|
nc = tree_open_con(nc, cwindow);
|
||||||
|
else nc = tree_open_con(nc->parent, cwindow);
|
||||||
} else {
|
} else {
|
||||||
/* If not, insert it at the currently focused position */
|
/* If not, insert it at the currently focused position */
|
||||||
if (focused->type == CT_CON && con_accepts_window(focused)) {
|
if (focused->type == CT_CON && con_accepts_window(focused)) {
|
||||||
|
|
|
@ -0,0 +1,242 @@
|
||||||
|
/*
|
||||||
|
* vim:ts=4:sw=4:expandtab
|
||||||
|
*
|
||||||
|
* i3 - an improved dynamic tiling window manager
|
||||||
|
*
|
||||||
|
* © 2009-2011 Michael Stapelberg and contributors
|
||||||
|
*
|
||||||
|
* See file LICENSE for license information.
|
||||||
|
*
|
||||||
|
* startup.c: Startup notification code. Ensures a startup notification context
|
||||||
|
* is setup when launching applications. We store the current workspace to open
|
||||||
|
* windows in that startup notification context on the appropriate workspace.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
|
#include <libsn/sn-launcher.h>
|
||||||
|
|
||||||
|
#include "all.h"
|
||||||
|
|
||||||
|
static TAILQ_HEAD(startup_sequence_head, Startup_Sequence) startup_sequences =
|
||||||
|
TAILQ_HEAD_INITIALIZER(startup_sequences);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After 60 seconds, a timeout will be triggered for each startup sequence.
|
||||||
|
*
|
||||||
|
* The timeout will just trigger completion of the sequence, so the normal
|
||||||
|
* completion process takes place (startup_monitor_event will free it).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static void startup_timeout(EV_P_ ev_timer *w, int revents) {
|
||||||
|
const char *id = sn_launcher_context_get_startup_id(w->data);
|
||||||
|
DLOG("Timeout for startup sequence %s\n", id);
|
||||||
|
|
||||||
|
struct Startup_Sequence *current, *sequence = NULL;
|
||||||
|
TAILQ_FOREACH(current, &startup_sequences, sequences) {
|
||||||
|
if (strcmp(current->id, id) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sequence = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unref the context (for the timeout itself, see start_application) */
|
||||||
|
sn_launcher_context_unref(w->data);
|
||||||
|
|
||||||
|
if (!sequence) {
|
||||||
|
DLOG("Sequence already deleted, nevermind.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Complete the startup sequence, will trigger its deletion. */
|
||||||
|
sn_launcher_context_complete(w->data);
|
||||||
|
free(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Starts the given application by passing it through a shell. We use double fork
|
||||||
|
* to avoid zombie processes. As the started application’s parent exits (immediately),
|
||||||
|
* the application is reparented to init (process-id 1), which correctly handles
|
||||||
|
* childs, so we don’t have to do it :-).
|
||||||
|
*
|
||||||
|
* The shell is determined by looking for the SHELL environment variable. If it
|
||||||
|
* does not exist, /bin/sh is used.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void start_application(const char *command) {
|
||||||
|
/* Create a startup notification context to monitor the progress of this
|
||||||
|
* startup. */
|
||||||
|
SnLauncherContext *context;
|
||||||
|
context = sn_launcher_context_new(sndisplay, conn_screen);
|
||||||
|
sn_launcher_context_set_name(context, "i3");
|
||||||
|
sn_launcher_context_set_description(context, "exec command in i3");
|
||||||
|
/* Chop off everything starting from the first space (if there are any
|
||||||
|
* spaces in the command), since we don’t want the parameters. */
|
||||||
|
char *first_word = sstrdup(command);
|
||||||
|
char *space = strchr(first_word, ' ');
|
||||||
|
if (space)
|
||||||
|
*space = '\0';
|
||||||
|
sn_launcher_context_initiate(context, "i3", first_word, last_timestamp);
|
||||||
|
free(first_word);
|
||||||
|
|
||||||
|
/* Trigger a timeout after 60 seconds */
|
||||||
|
struct ev_timer *timeout = scalloc(sizeof(struct ev_timer));
|
||||||
|
ev_timer_init(timeout, startup_timeout, 60.0, 0.);
|
||||||
|
timeout->data = context;
|
||||||
|
ev_timer_start(main_loop, timeout);
|
||||||
|
|
||||||
|
LOG("startup id = %s\n", sn_launcher_context_get_startup_id(context));
|
||||||
|
|
||||||
|
/* Save the ID and current workspace in our internal list of startup
|
||||||
|
* sequences */
|
||||||
|
Con *ws = con_get_workspace(focused);
|
||||||
|
struct Startup_Sequence *sequence = scalloc(sizeof(struct Startup_Sequence));
|
||||||
|
sequence->id = sstrdup(sn_launcher_context_get_startup_id(context));
|
||||||
|
sequence->workspace = sstrdup(ws->name);
|
||||||
|
sequence->context = context;
|
||||||
|
TAILQ_INSERT_TAIL(&startup_sequences, sequence, sequences);
|
||||||
|
|
||||||
|
/* Increase the refcount once (it starts with 1, so it will be 2 now) for
|
||||||
|
* the timeout. Even if the sequence gets completed, the timeout still
|
||||||
|
* needs the context (but will unref it then) */
|
||||||
|
sn_launcher_context_ref(context);
|
||||||
|
|
||||||
|
LOG("executing: %s\n", command);
|
||||||
|
if (fork() == 0) {
|
||||||
|
/* Child process */
|
||||||
|
setsid();
|
||||||
|
if (fork() == 0) {
|
||||||
|
/* Setup the environment variable(s) */
|
||||||
|
sn_launcher_context_setup_child_process(context);
|
||||||
|
|
||||||
|
/* Stores the path of the shell */
|
||||||
|
static const char *shell = NULL;
|
||||||
|
|
||||||
|
if (shell == NULL)
|
||||||
|
if ((shell = getenv("SHELL")) == NULL)
|
||||||
|
shell = "/bin/sh";
|
||||||
|
|
||||||
|
/* This is the child */
|
||||||
|
execl(shell, shell, "-c", command, (void*)NULL);
|
||||||
|
/* not reached */
|
||||||
|
}
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
wait(0);
|
||||||
|
|
||||||
|
/* Change the pointer of the root window to indicate progress */
|
||||||
|
if (xcursor_supported)
|
||||||
|
xcursor_set_root_cursor(XCURSOR_CURSOR_WATCH);
|
||||||
|
else xcb_set_root_cursor(XCURSOR_CURSOR_WATCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called by libstartup-notification when something happens
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void startup_monitor_event(SnMonitorEvent *event, void *userdata) {
|
||||||
|
SnStartupSequence *snsequence;
|
||||||
|
|
||||||
|
snsequence = sn_monitor_event_get_startup_sequence(event);
|
||||||
|
|
||||||
|
/* Get the corresponding internal startup sequence */
|
||||||
|
const char *id = sn_startup_sequence_get_id(snsequence);
|
||||||
|
struct Startup_Sequence *current, *sequence = NULL;
|
||||||
|
TAILQ_FOREACH(current, &startup_sequences, sequences) {
|
||||||
|
if (strcmp(current->id, id) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sequence = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sequence) {
|
||||||
|
DLOG("Got event for startup sequence that we did not initiate (ID = %s). Ignoring.\n", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (sn_monitor_event_get_type(event)) {
|
||||||
|
case SN_MONITOR_EVENT_COMPLETED:
|
||||||
|
DLOG("startup sequence %s completed\n", sn_startup_sequence_get_id(snsequence));
|
||||||
|
|
||||||
|
/* Unref the context, will be free()d */
|
||||||
|
sn_launcher_context_unref(sequence->context);
|
||||||
|
|
||||||
|
/* Delete our internal sequence */
|
||||||
|
TAILQ_REMOVE(&startup_sequences, sequence, sequences);
|
||||||
|
|
||||||
|
if (TAILQ_EMPTY(&startup_sequences)) {
|
||||||
|
DLOG("No more startup sequences running, changing root window cursor to default pointer.\n");
|
||||||
|
/* Change the pointer of the root window to indicate progress */
|
||||||
|
if (xcursor_supported)
|
||||||
|
xcursor_set_root_cursor(XCURSOR_CURSOR_POINTER);
|
||||||
|
else xcb_set_root_cursor(XCURSOR_CURSOR_POINTER);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* ignore */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if the given window belongs to a startup notification by checking if
|
||||||
|
* the _NET_STARTUP_ID property is set on the window (or on its leader, if it’s
|
||||||
|
* unset).
|
||||||
|
*
|
||||||
|
* If so, returns the workspace on which the startup was initiated.
|
||||||
|
* Returns NULL otherwise.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
char *startup_workspace_for_window(i3Window *cwindow, xcb_get_property_reply_t *startup_id_reply) {
|
||||||
|
/* The _NET_STARTUP_ID is only needed during this function, so we get it
|
||||||
|
* here and don’t save it in the 'cwindow'. */
|
||||||
|
if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
|
||||||
|
FREE(startup_id_reply);
|
||||||
|
DLOG("No _NET_STARTUP_ID set on this window\n");
|
||||||
|
if (cwindow->leader == XCB_NONE)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
xcb_get_property_cookie_t cookie;
|
||||||
|
cookie = xcb_get_property(conn, false, cwindow->leader, A__NET_STARTUP_ID, XCB_GET_PROPERTY_TYPE_ANY, 0, 512);
|
||||||
|
DLOG("Checking leader window 0x%08x\n", cwindow->leader);
|
||||||
|
startup_id_reply = xcb_get_property_reply(conn, cookie, NULL);
|
||||||
|
|
||||||
|
if (startup_id_reply == NULL || xcb_get_property_value_length(startup_id_reply) == 0) {
|
||||||
|
DLOG("No _NET_STARTUP_ID set on the leader either\n");
|
||||||
|
FREE(startup_id_reply);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *startup_id;
|
||||||
|
if (asprintf(&startup_id, "%.*s", xcb_get_property_value_length(startup_id_reply),
|
||||||
|
(char*)xcb_get_property_value(startup_id_reply)) == -1) {
|
||||||
|
perror("asprintf()");
|
||||||
|
DLOG("Could not get _NET_STARTUP_ID\n");
|
||||||
|
free(startup_id_reply);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Startup_Sequence *current, *sequence = NULL;
|
||||||
|
TAILQ_FOREACH(current, &startup_sequences, sequences) {
|
||||||
|
if (strcmp(current->id, startup_id) != 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sequence = current;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(startup_id);
|
||||||
|
free(startup_id_reply);
|
||||||
|
|
||||||
|
if (!sequence) {
|
||||||
|
DLOG("WARNING: This sequence (ID %s) was not found\n", startup_id);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sequence->workspace;
|
||||||
|
}
|
35
src/util.c
35
src/util.c
|
@ -21,6 +21,9 @@
|
||||||
#include <yajl/yajl_version.h>
|
#include <yajl/yajl_version.h>
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
|
|
||||||
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
|
#include <libsn/sn-launcher.h>
|
||||||
|
|
||||||
#include "all.h"
|
#include "all.h"
|
||||||
|
|
||||||
static iconv_t conversion_descriptor = 0;
|
static iconv_t conversion_descriptor = 0;
|
||||||
|
@ -58,38 +61,6 @@ bool update_if_necessary(uint32_t *destination, const uint32_t new_value) {
|
||||||
return ((*destination = new_value) != old_value);
|
return ((*destination = new_value) != old_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Starts the given application by passing it through a shell. We use double fork
|
|
||||||
* to avoid zombie processes. As the started application’s parent exits (immediately),
|
|
||||||
* the application is reparented to init (process-id 1), which correctly handles
|
|
||||||
* childs, so we don’t have to do it :-).
|
|
||||||
*
|
|
||||||
* The shell is determined by looking for the SHELL environment variable. If it
|
|
||||||
* does not exist, /bin/sh is used.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
void start_application(const char *command) {
|
|
||||||
LOG("executing: %s\n", command);
|
|
||||||
if (fork() == 0) {
|
|
||||||
/* Child process */
|
|
||||||
setsid();
|
|
||||||
if (fork() == 0) {
|
|
||||||
/* Stores the path of the shell */
|
|
||||||
static const char *shell = NULL;
|
|
||||||
|
|
||||||
if (shell == NULL)
|
|
||||||
if ((shell = getenv("SHELL")) == NULL)
|
|
||||||
shell = "/bin/sh";
|
|
||||||
|
|
||||||
/* This is the child */
|
|
||||||
execl(shell, shell, "-c", command, (void*)NULL);
|
|
||||||
/* not reached */
|
|
||||||
}
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
wait(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* exec()s an i3 utility, for example the config file migration script or
|
* exec()s an i3 utility, for example the config file migration script or
|
||||||
* i3-nagbar. This function first searches $PATH for the given utility named,
|
* i3-nagbar. This function first searches $PATH for the given utility named,
|
||||||
|
|
17
src/xcb.c
17
src/xcb.c
|
@ -341,3 +341,20 @@ void xcb_warp_pointer_rect(xcb_connection_t *conn, Rect *rect) {
|
||||||
LOG("warp pointer to: %d %d\n", mid_x, mid_y);
|
LOG("warp pointer to: %d %d\n", mid_x, mid_y);
|
||||||
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
|
xcb_warp_pointer(conn, XCB_NONE, root, 0, 0, 0, 0, mid_x, mid_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the cursor of the root window to the given cursor id.
|
||||||
|
* This function should only be used if xcursor_supported == false.
|
||||||
|
* Otherwise, use xcursor_set_root_cursor().
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void xcb_set_root_cursor(int cursor) {
|
||||||
|
xcb_cursor_t cursor_id = xcb_generate_id(conn);
|
||||||
|
i3Font cursor_font = load_font("cursor", false);
|
||||||
|
int xcb_cursor = xcursor_get_xcb_cursor(cursor);
|
||||||
|
xcb_create_glyph_cursor(conn, cursor_id, cursor_font.id, cursor_font.id,
|
||||||
|
xcb_cursor, xcb_cursor + 1, 0, 0, 0, 65535, 65535, 65535);
|
||||||
|
xcb_change_window_attributes(conn, root, XCB_CW_CURSOR, &cursor_id);
|
||||||
|
xcb_free_cursor(conn, cursor_id);
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
|
@ -14,7 +14,8 @@ static Cursor cursors[XCURSOR_CURSOR_MAX];
|
||||||
static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
|
static const int xcb_cursors[XCURSOR_CURSOR_MAX] = {
|
||||||
XCB_CURSOR_LEFT_PTR,
|
XCB_CURSOR_LEFT_PTR,
|
||||||
XCB_CURSOR_SB_H_DOUBLE_ARROW,
|
XCB_CURSOR_SB_H_DOUBLE_ARROW,
|
||||||
XCB_CURSOR_SB_V_DOUBLE_ARROW
|
XCB_CURSOR_SB_V_DOUBLE_ARROW,
|
||||||
|
XCB_CURSOR_WATCH
|
||||||
};
|
};
|
||||||
|
|
||||||
static Cursor load_cursor(const char *name) {
|
static Cursor load_cursor(const char *name) {
|
||||||
|
@ -28,6 +29,7 @@ void xcursor_load_cursors() {
|
||||||
cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
|
cursors[XCURSOR_CURSOR_POINTER] = load_cursor("left_ptr");
|
||||||
cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
|
cursors[XCURSOR_CURSOR_RESIZE_HORIZONTAL] = load_cursor("sb_h_double_arrow");
|
||||||
cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
|
cursors[XCURSOR_CURSOR_RESIZE_VERTICAL] = load_cursor("sb_v_double_arrow");
|
||||||
|
cursors[XCURSOR_CURSOR_WATCH] = load_cursor("watch");
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -41,9 +43,9 @@ void xcursor_load_cursors() {
|
||||||
* races might occur (even though we flush the Xlib connection).
|
* races might occur (even though we flush the Xlib connection).
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
void xcursor_set_root_cursor() {
|
void xcursor_set_root_cursor(int cursor_id) {
|
||||||
XSetWindowAttributes attributes;
|
XSetWindowAttributes attributes;
|
||||||
attributes.cursor = xcursor_get_cursor(XCURSOR_CURSOR_POINTER);
|
attributes.cursor = xcursor_get_cursor(cursor_id);
|
||||||
XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes);
|
XChangeWindowAttributes(xlibdpy, DefaultRootWindow(xlibdpy), CWCursor, &attributes);
|
||||||
XFlush(xlibdpy);
|
XFlush(xlibdpy);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ WriteMakefile(
|
||||||
'Test::Most' => 0,
|
'Test::Most' => 0,
|
||||||
'Test::Deep' => 0,
|
'Test::Deep' => 0,
|
||||||
'EV' => 0,
|
'EV' => 0,
|
||||||
|
'Inline' => 0,
|
||||||
},
|
},
|
||||||
# don't install any files from this directory
|
# don't install any files from this directory
|
||||||
PM => {},
|
PM => {},
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
#!perl
|
||||||
|
# vim:ts=4:sw=4:expandtab
|
||||||
|
#
|
||||||
|
# Test for the startup notification protocol.
|
||||||
|
#
|
||||||
|
|
||||||
|
use i3test;
|
||||||
|
use POSIX qw(mkfifo);
|
||||||
|
use File::Temp qw(:POSIX);
|
||||||
|
|
||||||
|
my $x = X11::XCB::Connection->new;
|
||||||
|
use ExtUtils::PkgConfig;
|
||||||
|
|
||||||
|
# setup dependency on libstartup-notification using pkg-config
|
||||||
|
my %sn_config;
|
||||||
|
BEGIN {
|
||||||
|
%sn_config = ExtUtils::PkgConfig->find('libstartup-notification-1.0');
|
||||||
|
}
|
||||||
|
|
||||||
|
use Inline C => Config => LIBS => $sn_config{libs}, CCFLAGS => $sn_config{cflags};
|
||||||
|
use Inline C => <<'END_OF_C_CODE';
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
#define SN_API_NOT_YET_FROZEN 1
|
||||||
|
#include <libsn/sn-common.h>
|
||||||
|
#include <libsn/sn-launchee.h>
|
||||||
|
|
||||||
|
static SnDisplay *sndisplay;
|
||||||
|
static SnLauncheeContext *ctx;
|
||||||
|
static xcb_connection_t *conn;
|
||||||
|
|
||||||
|
// TODO: this should use $x
|
||||||
|
void init_ctx() {
|
||||||
|
int screen;
|
||||||
|
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
|
||||||
|
xcb_connection_has_error(conn))
|
||||||
|
errx(1, "x11 conn failed");
|
||||||
|
|
||||||
|
printf("screen = %d\n", screen);
|
||||||
|
sndisplay = sn_xcb_display_new(conn, NULL, NULL);
|
||||||
|
ctx = sn_launchee_context_new_from_environment(sndisplay, screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *get_startup_id() {
|
||||||
|
return sn_launchee_context_get_startup_id(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_window(int window) {
|
||||||
|
sn_launchee_context_setup_window(ctx, (Window)window);
|
||||||
|
xcb_flush(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void complete_startup() {
|
||||||
|
/* mark the startup process complete */
|
||||||
|
sn_launchee_context_complete(ctx);
|
||||||
|
}
|
||||||
|
END_OF_C_CODE
|
||||||
|
|
||||||
|
my $first_ws = fresh_workspace;
|
||||||
|
|
||||||
|
is(@{get_ws_content($first_ws)}, 0, 'no containers on this workspace yet');
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# 1) initiate startup, switch workspace, create window
|
||||||
|
# (should be placed on the original workspace)
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
# Start a new process via i3 (to initialize a new startup notification
|
||||||
|
# context), then steal its DESKTOP_STARTUP_ID variable. We handle the startup
|
||||||
|
# notification in the testcase from there on.
|
||||||
|
#
|
||||||
|
# This works by setting up a FIFO in which the process (started by i3) will
|
||||||
|
# echo its $DESKTOP_STARTUP_ID. We (blockingly) read the variable into
|
||||||
|
# $startup_id in the testcase.
|
||||||
|
my $tmp = tmpnam();
|
||||||
|
mkfifo($tmp, 0600) or die "Could not create FIFO in $tmp";
|
||||||
|
|
||||||
|
cmd qq|exec echo \$DESKTOP_STARTUP_ID >$tmp|;
|
||||||
|
|
||||||
|
open(my $fh, '<', $tmp);
|
||||||
|
chomp(my $startup_id = <$fh>);
|
||||||
|
close($fh);
|
||||||
|
|
||||||
|
unlink($tmp);
|
||||||
|
|
||||||
|
$ENV{DESKTOP_STARTUP_ID} = $startup_id;
|
||||||
|
|
||||||
|
# Create a new libstartup-notification launchee context
|
||||||
|
init_ctx();
|
||||||
|
|
||||||
|
# Make sure the context was set up successfully
|
||||||
|
is(get_startup_id(), $startup_id, 'libstartup-notification returns the same id');
|
||||||
|
|
||||||
|
my $second_ws = fresh_workspace;
|
||||||
|
|
||||||
|
is(@{get_ws_content($second_ws)}, 0, 'no containers on the second workspace yet');
|
||||||
|
|
||||||
|
my $win = open_window($x, { dont_map => 1 });
|
||||||
|
mark_window($win->id);
|
||||||
|
$win->map;
|
||||||
|
wait_for_map($x);
|
||||||
|
# We sync with i3 here to make sure $x->input_focus is updated.
|
||||||
|
sync_with_i3($x);
|
||||||
|
|
||||||
|
is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
|
||||||
|
is(@{get_ws_content($first_ws)}, 1, 'one container on the first workspace');
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# same thing, but with _NET_STARTUP_ID set on the leader
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
my $leader = open_window($x, { dont_map => 1 });
|
||||||
|
mark_window($leader->id);
|
||||||
|
|
||||||
|
$win = open_window($x, { client_leader => $leader });
|
||||||
|
|
||||||
|
is(@{get_ws_content($second_ws)}, 0, 'still no containers on the second workspace');
|
||||||
|
is(@{get_ws_content($first_ws)}, 2, 'two containers on the first workspace');
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
# 2) open another window after the startup process is completed
|
||||||
|
# (should be placed on the current workspace)
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
complete_startup();
|
||||||
|
sync_with_i3($x);
|
||||||
|
|
||||||
|
my $otherwin = open_window($x);
|
||||||
|
is(@{get_ws_content($second_ws)}, 1, 'one container on the second workspace');
|
||||||
|
|
||||||
|
done_testing;
|
Loading…
Reference in New Issue