windowmaker-wl/src/screen.c

697 lines
18 KiB
C

/* screen.c - screen management
*
* Window Maker window manager
*
* Copyright (c) 1997-2003 Alfredo K. Kojima
*
* 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 of the License, 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "wconfig.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <wraster.h>
#include "WindowMaker.h"
#include "def_pixmaps.h"
#include "screen.h"
#include "texture.h"
#include "pixmap.h"
#include "menu.h"
#include "window.h"
#include "main.h"
#include "actions.h"
#include "properties.h"
#include "dock.h"
#include "resources.h"
#include "workspace.h"
#include "session.h"
#include "balloon.h"
#include "geomview.h"
#include "wmspec.h"
#include "rootmenu.h"
#include "misc.h"
#include "xinerama.h"
#include "backend/backend.h"
#include <WINGs/WUtil.h>
#include <WINGs/WINGsP.h>
#include "defaults.h"
#define EVENT_MASK (LeaveWindowMask|EnterWindowMask|PropertyChangeMask\
|SubstructureNotifyMask|PointerMotionMask \
|SubstructureRedirectMask|ButtonPressMask|ButtonReleaseMask\
|KeyPressMask|KeyReleaseMask)
static WMPropList *dApplications = NULL;
static WMPropList *dWorkspace;
static WMPropList *dDock;
static WMPropList *dClip;
static WMPropList *dDrawers = NULL;
static void make_keys(void)
{
if (dApplications != NULL)
return;
dApplications = WMCreatePLString("Applications");
dWorkspace = WMCreatePLString("Workspace");
dDock = WMCreatePLString("Dock");
dClip = WMCreatePLString("Clip");
dDrawers = WMCreatePLString("Drawers");
}
void create_logo_image(WScreen *scr)
{
RImage *image = get_icon_image(scr, "Logo", "WMPanel", 128);
if (!image) {
wwarning(_("could not load logo image for panels: %s"), RMessageForError(RErrorCode));
} else {
WMSetApplicationIconImage(scr->wmscreen, image);
RReleaseImage(image);
}
}
/*
*----------------------------------------------------------------------
* wScreenInit--
* Initializes the window manager for the given screen and
* allocates a WScreen descriptor for it. Many resources are allocated
* for the screen and the root window is setup appropriately.
*
* Returns:
* The WScreen descriptor for the screen.
*
* Side effects:
* Many resources are allocated and the IconSize property is
* set on the root window.
* The program can be aborted if some fatal error occurs.
*
* TODO: User specifiable visual.
*----------------------------------------------------------------------
*/
WScreen *wScreenInit(int screen_number)
{
WScreen *scr;
RContextAttributes rattr = {};
int i;
scr = wmalloc(sizeof(WScreen));
scr->stacking_list = WMCreateTreeBag();
scr->screen = screen_number;
/* Allocate backend per-screen state; fills root_win, depth, colormap,
* scr_width, scr_height as well. */
wm_backend->screen_open(scr);
wInitXinerama(scr);
scr->usableArea = (WArea *) wmalloc(sizeof(WArea) * wXineramaHeads(scr));
scr->totalUsableArea = (WArea *) wmalloc(sizeof(WArea) * wXineramaHeads(scr));
for (i = 0; i < wXineramaHeads(scr); ++i) {
WMRect rect = wGetRectForHead(scr, i);
scr->usableArea[i].x1 = scr->totalUsableArea[i].x1 = rect.pos.x;
scr->usableArea[i].y1 = scr->totalUsableArea[i].y1 = rect.pos.y;
scr->usableArea[i].x2 = scr->totalUsableArea[i].x2 = rect.pos.x + rect.size.width;
scr->usableArea[i].y2 = scr->totalUsableArea[i].y2 = rect.pos.y + rect.size.height;
}
scr->fakeGroupLeaders = WMCreateArray(16);
if (!wm_backend->screen_replace_wm(scr)) {
wfree(scr);
return NULL;
}
if (!wm_backend->screen_select_input(scr, EVENT_MASK)) {
wfree(scr);
return NULL;
}
wm_backend->monitors_select_events(scr);
wm_backend->screen_set_root_cursor(scr, WCUR_ROOT);
/* Wayland path: screen_open() sets geometry; X11/wrlib context not needed.
* Allocate workspace 0 directly (wWorkspaceNew is unsafe here because it
* calls wDockCreate which requires X11 rendering resources). */
if (!dpy) {
WWorkspace *wspace = wmalloc(sizeof(WWorkspace));
wspace->name = wstrdup("Workspace 1");
wspace->clip = NULL;
wspace->map = NULL;
scr->workspaces = wmalloc(sizeof(WWorkspace *));
scr->workspaces[0] = wspace;
scr->workspace_count = 1;
/* Phase 62: run backend screen internals (sets no_focus_win,
* auto_focus, clears keyboard focus) before leaving the Wayland
* fast path — screen_create_internals is normally called at
* line 248 which the goto would skip. */
wm_backend->screen_create_internals(scr);
goto screen_done;
}
/* screen descriptor for raster graphic library */
rattr.flags = RC_RenderMode | RC_ColorsPerChannel | RC_StandardColormap;
rattr.render_mode = wPreferences.no_dithering ? RBestMatchRendering : RDitheredRendering;
/* if the std colormap stuff works ok, this will be ignored */
rattr.colors_per_channel = wPreferences.cmap_size;
if (rattr.colors_per_channel < 2)
rattr.colors_per_channel = 2;
/* Use standard colormap */
rattr.standard_colormap_mode = RUseStdColormap;
if (getWVisualID(screen_number) >= 0) {
rattr.flags |= RC_VisualID;
rattr.visualid = getWVisualID(screen_number);
}
scr->rcontext = RCreateContext(dpy, screen_number, &rattr);
if (!scr->rcontext && RErrorCode == RERR_STDCMAPFAIL) {
wwarning("%s", RMessageForError(RErrorCode));
rattr.flags &= ~RC_StandardColormap;
rattr.standard_colormap_mode = RUseStdColormap;
scr->rcontext = RCreateContext(dpy, screen_number, &rattr);
}
if (scr->rcontext == NULL) {
wfatal(_("can't create Context on screen %d, %s"),
screen_number, RMessageForError(RErrorCode));
goto abort_no_context;
}
scr->w_win = scr->rcontext->drawable;
scr->w_visual = scr->rcontext->visual;
scr->w_depth = scr->rcontext->depth;
scr->w_colormap = scr->rcontext->cmap;
/* create screen descriptor for WINGs */
scr->wmscreen = WMCreateScreenWithRContext(dpy, screen_number, scr->rcontext);
if (!scr->wmscreen) {
wfatal(_("could not initialize WINGs widget set"));
RDestroyContext(scr->rcontext);
abort_no_context:
WMFreeArray(scr->fakeGroupLeaders);
wfree(scr->totalUsableArea);
wfree(scr->usableArea);
WMFreeBag(scr->stacking_list);
wfree(scr);
return NULL;
}
scr->black = WMBlackColor(scr->wmscreen);
scr->white = WMWhiteColor(scr->wmscreen);
scr->gray = WMGrayColor(scr->wmscreen);
scr->darkGray = WMDarkGrayColor(scr->wmscreen);
scr->black_pixel = WMColorPixel(scr->black);
scr->white_pixel = WMColorPixel(scr->white);
scr->light_pixel = WMColorPixel(scr->gray);
scr->dark_pixel = WMColorPixel(scr->darkGray);
/* create GCs with default values */
wm_backend->screen_alloc_gcs(scr);
/* read defaults for this screen */
wReadDefaults(scr, w_global.domain.wmaker->dictionary);
scr->frame_border_pixel = WMColorPixel(scr->frame_border_color);
scr->frame_focused_border_pixel = WMColorPixel(scr->frame_focused_border_color);
scr->frame_selected_border_pixel = WMColorPixel(scr->frame_selected_border_color);
wm_backend->screen_create_internals(scr);
wNETWMInitStuff(scr);
/* create initial workspace */
wWorkspaceNew(scr);
/* create shared pixmaps */
wm_backend->screen_create_pixmaps(scr);
create_logo_image(scr);
/* set icon sizes we can accept from clients */
wm_backend->screen_set_icon_sizes(scr, 8, wPreferences.icon_size - 4);
/* setup WindowMaker protocols property in the root window */
PropSetWMakerProtocols(scr->root_win);
/* setup our noticeboard */
wm_backend->screen_setup_noticeboard(scr);
#ifdef BALLOON_TEXT
/* initialize balloon text stuff */
wBalloonInitialize(scr);
#endif
scr->info_text_font = WMBoldSystemFontOfSize(scr->wmscreen, 12);
wm_backend->screen_load_tech_font(scr);
scr->gview = WCreateGeometryView(scr->wmscreen);
WMRealizeWidget(scr->gview);
wScreenUpdateUsableArea(scr);
screen_done:
return scr;
}
void wScreenUpdateUsableArea(WScreen *scr)
{
/*
* scr->totalUsableArea[] will become the usableArea used for Windowplacement,
* scr->usableArea[] will be used for iconplacement, hence no iconyard nor
* border.
*/
WArea area;
int i;
unsigned long tmp_area, best_area = 0;
unsigned int size = wPreferences.workspace_border_size;
unsigned int position = wPreferences.workspace_border_position;
for (i = 0; i < wXineramaHeads(scr); ++i) {
WMRect rect = wGetRectForHead(scr, i);
scr->totalUsableArea[i].x1 = rect.pos.x;
scr->totalUsableArea[i].y1 = rect.pos.y;
scr->totalUsableArea[i].x2 = rect.pos.x + rect.size.width;
scr->totalUsableArea[i].y2 = rect.pos.y + rect.size.height;
if (wNETWMGetUsableArea(scr, i, &area)) {
scr->totalUsableArea[i].x1 = WMAX(scr->totalUsableArea[i].x1, area.x1);
scr->totalUsableArea[i].y1 = WMAX(scr->totalUsableArea[i].y1, area.y1);
scr->totalUsableArea[i].x2 = WMIN(scr->totalUsableArea[i].x2, area.x2);
scr->totalUsableArea[i].y2 = WMIN(scr->totalUsableArea[i].y2, area.y2);
}
scr->usableArea[i] = scr->totalUsableArea[i];
if (wPreferences.no_window_over_icons) {
if (wPreferences.icon_yard & IY_VERT) {
if (wPreferences.icon_yard & IY_RIGHT)
scr->totalUsableArea[i].x2 -= wPreferences.icon_size;
else
scr->totalUsableArea[i].x1 += wPreferences.icon_size;
} else {
if (wPreferences.icon_yard & IY_TOP)
scr->totalUsableArea[i].y1 += wPreferences.icon_size;
else
scr->totalUsableArea[i].y2 -= wPreferences.icon_size;
}
}
if (scr->totalUsableArea[i].x2 - scr->totalUsableArea[i].x1 < rect.size.width / 2) {
scr->totalUsableArea[i].x1 = rect.pos.x;
scr->totalUsableArea[i].x2 = rect.pos.x + rect.size.width;
}
if (scr->totalUsableArea[i].y2 - scr->totalUsableArea[i].y1 < rect.size.height / 2) {
scr->totalUsableArea[i].y1 = rect.pos.y;
scr->totalUsableArea[i].y2 = rect.pos.y + rect.size.height;
}
tmp_area = (scr->totalUsableArea[i].x2 - scr->totalUsableArea[i].x1) *
(scr->totalUsableArea[i].y2 - scr->totalUsableArea[i].y1);
if (tmp_area > best_area) {
best_area = tmp_area;
area = scr->totalUsableArea[i];
}
if (size > 0 && position != WB_NONE) {
if (position & WB_LEFTRIGHT) {
scr->totalUsableArea[i].x1 += size;
scr->totalUsableArea[i].x2 -= size;
}
if (position & WB_TOPBOTTOM) {
scr->totalUsableArea[i].y1 += size;
scr->totalUsableArea[i].y2 -= size;
}
}
}
if (wPreferences.auto_arrange_icons)
wArrangeIcons(scr, True);
}
void wScreenRestoreState(WScreen * scr)
{
WMPropList *state;
char *path;
OpenRootMenu(scr, -10000, -10000, False);
wMenuUnmap(scr->root_menu);
make_keys();
if (w_global.screen_count == 1) {
path = wdefaultspathfordomain("WMState");
} else {
char buf[16];
snprintf(buf, sizeof(buf), "WMState.%i", scr->screen);
path = wdefaultspathfordomain(buf);
}
scr->session_state = WMReadPropListFromFile(path);
wfree(path);
if (!scr->session_state && w_global.screen_count > 1) {
path = wdefaultspathfordomain("WMState");
scr->session_state = WMReadPropListFromFile(path);
wfree(path);
}
if (!scr->session_state)
scr->session_state = WMCreatePLDictionary(NULL, NULL);
if (!wPreferences.flags.nodock) {
state = WMGetFromPLDictionary(scr->session_state, dDock);
scr->dock = wDockRestoreState(scr, state, WM_DOCK);
}
if (!wPreferences.flags.noclip) {
state = WMGetFromPLDictionary(scr->session_state, dClip);
scr->clip_icon = wClipRestoreState(scr, state);
}
if (!wPreferences.flags.nodrawer) {
if (!scr->dock->on_right_side) {
/* Drawer tile was created early in wScreenInit() -> wReadDefaults(). At
* that time, scr->dock was NULL and the tile was created as if we were on
* the right side. If we aren't, redo it now. */
assert(scr->drawer_tile);
RReleaseImage(scr->drawer_tile);
scr->drawer_tile = wDrawerMakeTile(scr, scr->icon_tile);
}
wDrawersRestoreState(scr);
}
wWorkspaceRestoreState(scr);
wScreenUpdateUsableArea(scr);
}
void wScreenSaveState(WScreen * scr)
{
WWindow *wwin;
char *str;
WMPropList *old_state, *foo;
make_keys();
/* save state of windows */
wwin = scr->focused_window;
while (wwin) {
wWindowSaveState(wwin);
wwin = wwin->prev;
}
if (wPreferences.flags.noupdates)
return;
old_state = scr->session_state;
scr->session_state = WMCreatePLDictionary(NULL, NULL);
WMPLSetCaseSensitive(True);
/* save dock state to file */
if (!wPreferences.flags.nodock) {
wDockSaveState(scr, old_state);
} else {
foo = WMGetFromPLDictionary(old_state, dDock);
if (foo != NULL)
WMPutInPLDictionary(scr->session_state, dDock, foo);
}
if (!wPreferences.flags.noclip) {
wClipSaveState(scr);
} else {
foo = WMGetFromPLDictionary(old_state, dClip);
if (foo != NULL)
WMPutInPLDictionary(scr->session_state, dClip, foo);
}
wWorkspaceSaveState(scr, old_state);
if (!wPreferences.flags.nodrawer) {
wDrawersSaveState(scr);
} else {
foo = WMGetFromPLDictionary(old_state, dDrawers);
if (foo != NULL)
WMPutInPLDictionary(scr->session_state, dDrawers, foo);
}
if (wPreferences.save_session_on_exit) {
wSessionSaveState(scr);
} else {
foo = WMGetFromPLDictionary(old_state, dApplications);
if (foo != NULL)
WMPutInPLDictionary(scr->session_state, dApplications, foo);
foo = WMGetFromPLDictionary(old_state, dWorkspace);
if (foo != NULL)
WMPutInPLDictionary(scr->session_state, dWorkspace, foo);
}
/* clean up */
WMPLSetCaseSensitive(False);
wMenuSaveState(scr);
if (w_global.screen_count == 1) {
str = wdefaultspathfordomain("WMState");
} else {
char buf[16];
snprintf(buf, sizeof(buf), "WMState.%i", scr->screen);
str = wdefaultspathfordomain(buf);
}
if (!WMWritePropListToFile(scr->session_state, str)) {
werror(_("could not save session state in %s"), str);
}
wfree(str);
WMReleasePropList(old_state);
}
int wScreenBringInside(WScreen * scr, int *x, int *y, int width, int height)
{
int moved = 0;
int tol_w, tol_h;
/*
* With respect to the head that contains most of the window.
*/
int sx1, sy1, sx2, sy2;
WMRect rect;
int head, flags;
rect.pos.x = *x;
rect.pos.y = *y;
rect.size.width = width;
rect.size.height = height;
head = wGetRectPlacementInfo(scr, rect, &flags);
rect = wGetRectForHead(scr, head);
sx1 = rect.pos.x;
sy1 = rect.pos.y;
sx2 = sx1 + rect.size.width;
sy2 = sy1 + rect.size.height;
#if 0 /* NOTE: gives funky group movement */
if (flags & XFLAG_MULTIPLE) {
/*
* since we span multiple heads, pull window totaly inside
*/
if (*x < sx1)
*x = sx1, moved = 1;
else if (*x + width > sx2)
*x = sx2 - width, moved = 1;
if (*y < sy1)
*y = sy1, moved = 1;
else if (*y + height > sy2)
*y = sy2 - height, moved = 1;
return moved;
}
#endif
if (width > 20)
tol_w = width / 2;
else
tol_w = 20;
if (height > 20)
tol_h = height / 2;
else
tol_h = 20;
if (*x + width < sx1 + 10)
*x = sx1 - tol_w, moved = 1;
else if (*x >= sx2 - 10)
*x = sx2 - tol_w - 1, moved = 1;
if (*y < sy1 - height + 10)
*y = sy1 - tol_h, moved = 1;
else if (*y >= sy2 - 10)
*y = sy2 - tol_h - 1, moved = 1;
return moved;
}
int wScreenKeepInside(WScreen * scr, int *x, int *y, int width, int height)
{
int moved = 0;
int sx1, sy1, sx2, sy2;
WMRect rect;
int head;
rect.pos.x = *x;
rect.pos.y = *y;
rect.size.width = width;
rect.size.height = height;
head = wGetHeadForRect(scr, rect);
rect = wGetRectForHead(scr, head);
sx1 = rect.pos.x;
sy1 = rect.pos.y;
sx2 = sx1 + rect.size.width;
sy2 = sy1 + rect.size.height;
if (*x < sx1)
*x = sx1, moved = 1;
else if (*x + width > sx2)
*x = sx2 - width, moved = 1;
if (*y < sy1)
*y = sy1, moved = 1;
else if (*y + height > sy2)
*y = sy2 - height, moved = 1;
return moved;
}
void ScreenCapture(WScreen *scr, int mode)
{
time_t s;
short i = 0;
struct tm *tm_info;
char index_str[12] = "";
char filename_date_part[60];
char filename[60];
char *filepath;
char *screenshot_dir;
RImage *img = NULL;
RImage *scale_img = NULL;
#ifdef USE_PNG
char *filetype = ".png";
#else
#ifdef USE_JPEG
char *filetype = ".jpg";
#else
char *filetype = NULL;
#endif
#endif
if (!filetype) {
werror(_("Unable to find a proper screenshot image format"));
return;
}
screenshot_dir = wstrconcat(wusergnusteppath(), "/Library/WindowMaker/Screenshots/");
if (-1 == mkdir(screenshot_dir, 0700) && errno != EEXIST) {
wfree(screenshot_dir);
werror(_("Unable to create screenshot directory: %s"), strerror(errno));
return;
}
s = time(NULL);
tm_info = localtime(&s);
strftime(filename_date_part, sizeof(filename_date_part), "screenshot_%Y-%m-%d_at_%H:%M:%S", tm_info);
strcpy(filename, filename_date_part);
filepath = wstrconcat(screenshot_dir, strcat(filename, filetype));
while (access(filepath, F_OK) == 0 && i < 600) {
i++;
strcpy(filename, filename_date_part);
sprintf(index_str, "_%d", i);
strncat(filename, index_str, sizeof(filename) - strlen(filename) - 1);
wfree(filepath);
filepath = wstrconcat(screenshot_dir, strcat(filename, filetype));
}
/* cannot generate an available filename ?! */
if (i == 600) {
wfree(filepath);
wfree(screenshot_dir);
werror(_("Could not generate a free screenshot filename"));
return;
}
switch (mode) {
WWindow *wwin;
case PRINT_WINDOW:
wwin = scr->focused_window;
if (wwin && !wwin->flags.shaded) {
if (wwin->focus_mode >= WFM_LOCALLY_ACTIVE) {
img = RCreateImageFromDrawable(scr->rcontext, wwin->client_win, None);
} else {
img = wm_backend->screen_capture_window(scr, wwin);
}
}
break;
case PRINT_PARTIAL:
img = wm_backend->screen_capture_area(scr);
break;
default:
/* PRINT_SCREEN */
img = RCreateImageFromDrawable(scr->rcontext, scr->root_win, None);
}
if (img) {
if (RSaveTitledImage(img, filepath, (char *)(filetype + 1), "Screenshot from Window Maker")) {
scale_img = RSmoothScaleImage(img, scr->scr_width / 10, scr->scr_height / 10);
wm_backend->screen_show_mini_screenshot(scr, scale_img);
RReleaseImage(scale_img);
#ifdef DEBUG
wmessage("screenshot filepath: %s", filepath);
#endif
}
RReleaseImage(img);
}
wfree(filepath);
wfree(screenshot_dir);
}