Newer
Older
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
*/
/* Initially inspired by xfwm, fvwm2, enlightment and twm implementations */
#ifdef HAVE_CONFIG_H
Olivier Fourdan
committed
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <glib.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pwd.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <signal.h>
Olivier Fourdan
committed
#include <gtk/gtk.h>
#include <glib.h>
#include <libxfcegui4/libxfcegui4.h>
#include "display.h"
#include "screen.h"
#include "hints.h"
#include "client.h"
Olivier Fourdan
committed
#include "session.h"
typedef struct _match
{
unsigned long win;
unsigned long client_leader;
char *client_id;
char *res_name;
char *res_class;
char *window_role;
char *wm_name;
int wm_command_count;
char **wm_command;
int x;
int y;
int width;
int height;
int old_x;
int old_y;
int old_width;
int old_height;
unsigned long flags;
gboolean used;
}
Match;
static int num_match = 0;
static Match *matches = NULL;
static void
my_free_string_list (gchar ** list, gint n)
{
gchar **s;
gint i;
if (!list || !n)
Olivier Fourdan
committed
return; /* silently... :) */
i = 0;
s = list;
while ((i < n) && (s))
Olivier Fourdan
committed
g_free (*s);
*s = NULL;
s++;
i++;
Olivier Fourdan
committed
/*
2-pass function to compute new string length,
allocate memory and finally copy string
- Returned value must be freed -
*/
static gchar *
escape_quote (gchar * s)
{
gchar *ns;
gchar *idx1, *idx2;
g_return_val_if_fail (s != NULL, NULL);
lg = strlen (s);
/* First, count quotes in string */
idx1 = s;
Olivier Fourdan
committed
if (*(idx1++) == '"')
{
nbquotes++;
}
}
/* If there is no quote in the string, return it */
if (!nbquotes)
Olivier Fourdan
committed
{
Olivier Fourdan
committed
return (g_strdup (s));
Olivier Fourdan
committed
}
/* Or else, allocate memory for the new string */
ns = g_new (gchar, lg + nbquotes + 1);
/* And prepend a backslash before any quote found in string */
idx1 = s;
idx2 = ns;
Olivier Fourdan
committed
if (*idx1 == '"')
{
*(idx2++) = '\\';
*(idx2++) = '"';
}
else
{
*(idx2++) = *idx1;
}
idx1++;
}
/* Add null char */
*idx2 = '\0';
return ns;
}
Olivier Fourdan
committed
/*
single-pass function to replace backslash+quotes
by quotes.
- Returned value must be freed -
*/
static gchar *
unescape_quote (gchar * s)
{
gchar *ns;
gboolean backslash;
gchar *idx1, *idx2;
gint lg;
g_return_val_if_fail (s != NULL, NULL);
lg = strlen (s);
ns = g_new (gchar, lg + 1);
Olivier Fourdan
committed
if (*idx1 == '\\')
{
*(idx2++) = *idx1;
backslash = TRUE;
}
else if ((*idx1 == '"') && backslash)
{
/* Move backward to override the "\" */
*(--idx2) = *idx1;
idx2++;
backslash = FALSE;
}
else
{
*(idx2++) = *idx1;
backslash = FALSE;
}
idx1++;
}
*idx2 = '\0';
return ns;
}
static gchar *
getsubstring (gchar * s, gint * length)
g_return_val_if_fail (s != NULL, NULL);
while ((*skip == ' ') || (*skip == '\t'))
Olivier Fourdan
committed
end = ++skip;
(*length)++;
if (*skip == '"')
Olivier Fourdan
committed
pbrk = '"';
end = ++skip;
(*length)++;
Olivier Fourdan
committed
pbrk = ' ';
}
finished = FALSE;
while ((!finished) && (*end))
Olivier Fourdan
committed
if (*end == '\\')
{
backslash = TRUE;
}
else if ((*end == pbrk) && backslash)
{
backslash = FALSE;
}
else if (*end == pbrk)
{
finished = TRUE;
}
end++;
lg++;
(*length)++;
ns = g_new (gchar, lg + 1);
/* Skip pbrk character */
end--;
idx1 = skip;
idx2 = ns;
do
{
Olivier Fourdan
committed
*(idx2++) = *idx1;
while (++idx1 < end);
*idx2 = '\0';
return ns;
}
static void
sessionSaveScreen (ScreenInfo *screen_info, FILE *f)
char *client_id, *window_role;
char **wm_command;
int wm_command_count;
display_info = screen_info->display_info;
wm_command_count = 0;
wm_command = NULL;
window_role = NULL;
client_id = NULL;
for (c = screen_info->clients, client_idx = 0; client_idx < screen_info->client_count;
c = c->next, client_idx++)
if (c->type & (WINDOW_TYPE_DONT_PLACE))
{
continue;
}
Olivier Fourdan
committed
{
getWindowRole (display_info, c->window, &window_role);
}
else
{
window_role = NULL;
}
Olivier Fourdan
committed
Olivier Fourdan
committed
getClientID (display_info, c->window, &client_id);
if (client_id)
{
fprintf (f, " [CLIENT_ID] %s\n", client_id);
XFree (client_id);
client_id = NULL;
}
Olivier Fourdan
committed
if (c->client_leader)
{
fprintf (f, " [CLIENT_LEADER] 0x%lx\n", c->client_leader);
}
Olivier Fourdan
committed
if (window_role)
{
fprintf (f, " [WINDOW_ROLE] %s\n", window_role);
XFree (window_role);
window_role = NULL;
}
Olivier Fourdan
committed
if (c->class.res_class)
{
fprintf (f, " [RES_NAME] %s\n", c->class.res_name);
}
Olivier Fourdan
committed
if (c->class.res_name)
{
fprintf (f, " [RES_CLASS] %s\n", c->class.res_class);
}
Olivier Fourdan
committed
if (c->name)
{
fprintf (f, " [WM_NAME] %s\n", c->name);
}
Olivier Fourdan
committed
getWindowCommand (display_info, c->window, &wm_command, &wm_command_count);
if ((wm_command_count > 0) && (wm_command))
{
gint j;
fprintf (f, " [WM_COMMAND] (%i)", wm_command_count);
for (j = 0; j < wm_command_count; j++)
Olivier Fourdan
committed
{
gchar *escaped_string;
escaped_string = escape_quote (wm_command[j]);
fprintf (f, " \"%s\"", escaped_string);
g_free (escaped_string);
Olivier Fourdan
committed
}
fprintf (f, "\n");
XFreeStringList (wm_command);
wm_command = NULL;
wm_command_count = 0;
}
Olivier Fourdan
committed
fprintf (f, " [GEOMETRY] (%i,%i,%i,%i)\n", c->x, c->y, c->width,
c->height);
fprintf (f, " [GEOMETRY-MAXIMIZED] (%i,%i,%i,%i)\n", c->old_x,
c->old_y, c->old_width, c->old_height);
fprintf (f, " [SCREEN] %i\n", screen_info->screen);
fprintf (f, " [DESK] %i\n", c->win_workspace);
fprintf (f, " [FLAGS] 0x%lx\n", FLAG_TEST (c->flags,
CLIENT_FLAG_STICKY | CLIENT_FLAG_ICONIFIED |
CLIENT_FLAG_SHADED | CLIENT_FLAG_MAXIMIZED |
CLIENT_FLAG_NAME_CHANGED));
}
}
gboolean
sessionSaveWindowStates (DisplayInfo *display_info, gchar * filename)
{
FILE *f;
GSList *screens;
g_return_val_if_fail (filename != NULL, FALSE);
g_return_val_if_fail (display_info != NULL, FALSE);
if ((f = fopen (filename, "w")))
{
for (screens = display_info->screens; screens; screens = g_slist_next (screens))
{
ScreenInfo *screen_info_n = (ScreenInfo *) screens->data;
sessionSaveScreen (screen_info_n, f);
Olivier Fourdan
committed
}
fclose (f);
return TRUE;
}
return FALSE;
}
gboolean
sessionLoadWindowStates (gchar * filename)
{
FILE *f;
char s[4096], s1[4096];
int i, pos, pos1;
unsigned long w;
g_return_val_if_fail (filename != NULL, FALSE);
if ((f = fopen (filename, "r")))
Olivier Fourdan
committed
while (fgets (s, sizeof (s), f))
{
sscanf (s, "%4000s", s1);
if (!strcmp (s1, "[CLIENT]"))
{
sscanf (s, "%*s 0x%lx", &w);
num_match++;
matches = g_realloc (matches, sizeof (Match) * num_match);
matches[num_match - 1].win = w;
matches[num_match - 1].client_id = NULL;
matches[num_match - 1].res_name = NULL;
matches[num_match - 1].res_class = NULL;
matches[num_match - 1].window_role = NULL;
matches[num_match - 1].wm_name = NULL;
matches[num_match - 1].wm_command_count = 0;
matches[num_match - 1].wm_command = NULL;
matches[num_match - 1].x = 0;
matches[num_match - 1].y = 0;
matches[num_match - 1].width = 100;
matches[num_match - 1].height = 100;
matches[num_match - 1].old_x = matches[num_match - 1].x;
matches[num_match - 1].old_y = matches[num_match - 1].y;
matches[num_match - 1].old_width = matches[num_match - 1].width;
matches[num_match - 1].old_height = matches[num_match - 1].height;
Olivier Fourdan
committed
matches[num_match - 1].desktop = 0;
Olivier Fourdan
committed
matches[num_match - 1].used = FALSE;
matches[num_match - 1].flags = 0;
}
else if (!strcmp (s1, "[GEOMETRY]"))
{
sscanf (s, "%*s (%i,%i,%i,%i)", &matches[num_match - 1].x,
&matches[num_match - 1].y, &matches[num_match - 1].width,
&matches[num_match - 1].height);
}
else if (!strcmp (s1, "[GEOMETRY-MAXIMIZED]"))
{
sscanf (s, "%*s (%i,%i,%i,%i)", &matches[num_match - 1].old_x,
&matches[num_match - 1].old_y,
&matches[num_match - 1].old_width,
&matches[num_match - 1].old_height);
}
else if (!strcmp (s1, "[SCREEN]"))
{
sscanf (s, "%*s %i", &matches[num_match - 1].screen);
}
Olivier Fourdan
committed
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
else if (!strcmp (s1, "[DESK]"))
{
sscanf (s, "%*s %i", &matches[num_match - 1].desktop);
}
else if (!strcmp (s1, "[CLIENT_LEADER]"))
{
sscanf (s, "%*s 0x%lx",
&matches[num_match - 1].client_leader);
}
else if (!strcmp (s1, "[FLAGS]"))
{
sscanf (s, "%*s 0x%lx", &matches[num_match - 1].flags);
}
else if (!strcmp (s1, "[CLIENT_ID]"))
{
sscanf (s, "%*s %[^\n]", s1);
matches[num_match - 1].client_id = strdup (s1);
}
else if (!strcmp (s1, "[WINDOW_ROLE]"))
{
sscanf (s, "%*s %[^\n]", s1);
matches[num_match - 1].window_role = strdup (s1);
}
else if (!strcmp (s1, "[RES_NAME]"))
{
sscanf (s, "%*s %[^\n]", s1);
matches[num_match - 1].res_name = strdup (s1);
}
else if (!strcmp (s1, "[RES_CLASS]"))
{
sscanf (s, "%*s %[^\n]", s1);
matches[num_match - 1].res_class = strdup (s1);
}
else if (!strcmp (s1, "[WM_NAME]"))
{
sscanf (s, "%*s %[^\n]", s1);
matches[num_match - 1].wm_name = strdup (s1);
}
else if (!strcmp (s1, "[WM_COMMAND]"))
{
sscanf (s, "%*s (%i)%n", &matches[num_match - 1].wm_command_count, &pos);
matches[num_match - 1].wm_command = g_new (gchar *, matches[num_match - 1].wm_command_count + 1);
Olivier Fourdan
committed
for (i = 0; i < matches[num_match - 1].wm_command_count; i++)
{
gchar *substring;
substring = getsubstring (s + pos, &pos1);
pos += pos1;
matches[num_match - 1].wm_command[i] = unescape_quote (substring);
Olivier Fourdan
committed
g_free (substring);
}
matches[num_match - 1].wm_command[matches[num_match - 1].wm_command_count] = NULL;
Olivier Fourdan
committed
}
}
fclose (f);
return TRUE;
}
return FALSE;
}
void
sessionFreeWindowStates (void)
for (i = 0; i < num_match; i++)
Olivier Fourdan
committed
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
if (matches[i].client_id)
{
free (matches[i].client_id);
matches[i].client_id = NULL;
}
if (matches[i].res_name)
{
free (matches[i].res_name);
matches[i].res_name = NULL;
}
if (matches[i].res_class)
{
free (matches[i].res_class);
matches[i].res_class = NULL;
}
if (matches[i].window_role)
{
free (matches[i].window_role);
matches[i].window_role = NULL;
}
if (matches[i].wm_name)
{
free (matches[i].wm_name);
matches[i].wm_name = NULL;
}
if ((matches[i].wm_command_count) && (matches[i].wm_command))
{
my_free_string_list (matches[i].wm_command,
matches[i].wm_command_count);
g_free (matches[i].wm_command);
matches[i].wm_command_count = 0;
matches[i].wm_command = NULL;
}
Olivier Fourdan
committed
g_free (matches);
matches = NULL;
num_match = 0;
}
}
/* This complicated logic is from twm, where it is explained */
#define xstreq(a,b) ((!a && !b) || (a && b && (strcmp(a,b)==0)))
static gboolean
matchWin (Client * c, Match * m)
ScreenInfo *screen_info;
DisplayInfo *display_info;
char *client_id;
char *window_role;
char **wm_command;
int wm_command_count;
g_return_val_if_fail (c != NULL, FALSE);
screen_info = c->screen_info;
display_info = screen_info->display_info;
wm_command = NULL;
window_role = NULL;
client_id = NULL;
getClientID (display_info, c->window, &client_id);
if (xstreq (client_id, m->client_id))
Olivier Fourdan
committed
/* client_id's match */
if (c->client_leader != None)
{
getWindowRole (display_info, c->window, &window_role);
Olivier Fourdan
committed
}
else
{
window_role = NULL;
}
if ((window_role) || (m->window_role))
{
/* We have or had a window role, base decision on it */
found = xstreq (window_role, m->window_role);
}
else
{
Olivier Fourdan
committed
* WM_NAME has changed
*/
if (xstreq (c->class.res_name, m->res_name)
&& (FLAG_TEST (c->flags, CLIENT_FLAG_NAME_CHANGED)
|| (m->flags & CLIENT_FLAG_NAME_CHANGED)
|| xstreq (c->name, m->wm_name)))
{
if (client_id)
{
/* If we have a client_id, we don't compare
WM_COMMAND, since it will be different. */
found = TRUE;
}
else
{
/* for non-SM-aware clients we also compare WM_COMMAND */
wm_command_count = 0;
getWindowCommand (display_info, c->window, &wm_command, &wm_command_count);
Olivier Fourdan
committed
if (wm_command_count == m->wm_command_count)
{
for (i = 0; i < wm_command_count; i++)
{
if (strcmp (wm_command[i], m->wm_command[i]) != 0)
break;
}
if ((i == wm_command_count) && (wm_command_count))
{
found = TRUE;
}
}
* We have to deal with a now-SM-aware client, it means that it won't probably
Olivier Fourdan
committed
* restore its state in a proper manner.
* Thus, we also mark all other instances of this application as used, to avoid
* dummy side effects in case we found a matching entry.
*/
if (found)
{
for (i = 0; i < num_match; i++)
{
if (!(matches[i].used) && !(&matches[i] == m)
&& (m->client_leader)
&& (matches[i].client_leader == m->client_leader))
Olivier Fourdan
committed
{
matches[i].used = TRUE;
}
}
}
}
}
}
if (client_id)
Olivier Fourdan
committed
XFree (client_id);
client_id = NULL;
if (window_role)
Olivier Fourdan
committed
XFree (window_role);
window_role = NULL;
if ((wm_command_count > 0) && (wm_command))
Olivier Fourdan
committed
XFreeStringList (wm_command);
wm_command = NULL;
wm_command_count = 0;
gboolean
sessionMatchWinToSM (Client * c)
g_return_val_if_fail (c != NULL, FALSE);
for (i = 0; i < num_match; i++)
if (!matches[i].used && (c->screen_info->screen == matches[i].screen) && matchWin (c, &matches[i]))
Olivier Fourdan
committed
{
matches[i].used = TRUE;
c->x = matches[i].x;
c->y = matches[i].y;
c->width = matches[i].width;
c->height = matches[i].height;
c->old_x = matches[i].old_x;
c->old_y = matches[i].old_y;
c->old_width = matches[i].old_width;
c->old_height = matches[i].old_height;
c->win_workspace = matches[i].desktop;
FLAG_SET (c->flags,
matches[i].
flags & (CLIENT_FLAG_STICKY | CLIENT_FLAG_SHADED |
CLIENT_FLAG_MAXIMIZED | CLIENT_FLAG_ICONIFIED));
FLAG_SET (c->xfwm_flags, XFWM_FLAG_WORKSPACE_SET);
Olivier Fourdan
committed
return TRUE;
}
}
return FALSE;
}
Olivier Fourdan
committed
static char *
sessionBuildFilename(SessionClient *client_session)
{
gchar *filename, *path, *file;
GError *error;
path = xfce_resource_save_location (XFCE_RESOURCE_CACHE, "sessions", FALSE);
error = NULL;
if (!xfce_mkdirhier(path, 0700, &error))
Olivier Fourdan
committed
{
g_warning("Unable to create session dir %s: %s", path, error->message);
g_error_free (error);
g_free (path);
return NULL;
}
file = g_strdup_printf("xfwm4-%s", client_session->given_client_id);
filename = g_build_filename (path, file, NULL);
g_free (file);
g_free (path);
Olivier Fourdan
committed
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
return filename;
}
static void
sessionLoad (DisplayInfo *display_info)
{
SessionClient *session;
gchar *filename;
session = display_info->session;
filename = sessionBuildFilename(session);
if (filename)
{
sessionLoadWindowStates (filename);
g_free (filename);
}
}
static void
sessionSavePhase2 (gpointer data)
{
DisplayInfo *display_info;
SessionClient *session;
gchar *filename;
display_info = (DisplayInfo *) data;
session = display_info->session;
filename = sessionBuildFilename(session);
if (filename)
{
sessionSaveWindowStates (display_info, filename);
g_free (filename);
}
}
static void
sessionDie (gpointer data)
{
DisplayInfo *display_info;
display_info = (DisplayInfo *) data;
display_info->quit = TRUE;
gtk_main_quit ();
}
int
sessionStart (int argc, char **argv, DisplayInfo *display_info)
{
SessionClient *session;
display_info->session = client_session_new (argc, argv, (gpointer) display_info,
Olivier Fourdan
committed
SESSION_RESTART_IF_RUNNING, 20);
session = display_info->session;
session->data = (gpointer) display_info;
session->save_phase_2 = sessionSavePhase2;
session->die = sessionDie;
if (session_init (session))
{
sessionLoad (display_info);
return 1;
}
return 0;
}