This forum has been archived. All content is frozen. Please use KDE Discuss instead.

new buttons disappear from title bar

Tags: None
(comma "," separated)
Rasar
Registered Member
Posts
3
Karma
0
OS
I posted this once but I don't see it. Pardon me if you end up seeing this twice.

Our application is experiencing a bizzare behavior starting in KDE 4 which we ended up with when we rebased from SuSE 10.3 to SuSE 11.1. The behavior is unique to KDE 4 and does not happen with the current version of Gnome so it's likely either a bug or a configuration issue.

The application adds its own buttons to the title bar to replace the minimize, maximize and delete buttons. Initially the added buttons show up fine. But upon movement of the window or interactivity with other windows the new buttons either disappear, or are obscured by the old minimize/maximize/delete buttons as though those never went away. At times you could see the new buttons slightly from in between the old buttons.

Any info on what might be causing this or what the fix is would be appreciated.
User avatar
TheBlackCat
Registered Member
Posts
2945
Karma
8
OS
What version of KDE are you using? By default openSUSE 11.1 comes with KDE 4.1, which was never intended for everyday use and has a lot of bugs and missing features.

This may have to do with what sort of window the application is identifying itself as, although I don't know what the proper one would be. But applications really shouldn't be painting their own titlebar or titlebar buttons, that should be handled by the window manager.


Man is the lowest-cost, 150-pound, nonlinear, all-purpose computer system which can be mass-produced by unskilled labor.
-NASA in 1965
Rasar
Registered Member
Posts
3
Karma
0
OS
BlackCat,

Thank you for your input. We are indeed using KDE 4.1 with SuSE out of the box. I'm puzzled why SuSE chose to distribute something that's unstable. Would you mind pointing out what version of KDE is stable and where to download it?

As for your second point, the application is not painting the title bar. It is customizing the buttons. This is nothing new. In fact KDE 3 had no issues with it whatsoever. If it's not allowed to customize the API shouldn't grant the call. Gnome 2.28 that is also bundled in SuSE 11.1 shows has no problems with this call. If, however, it's documented somewhere that it's not allowed to customize titles bars and buttons I would appreciate a reference to it so we pass it on to the application people.

Thanks again. I still welcome other input by others.
User avatar
bcooksley
Administrator
Posts
19765
Karma
87
OS
Updating to KDE 4.5 is highly recommended.
http://download.opensuse.org/repositori ... SUSE_11.1/


KDE Sysadmin
[img]content/bcooksley_sig.png[/img]
User avatar
TheBlackCat
Registered Member
Posts
2945
Karma
8
OS
Rasar wrote:As for your second point, the application is not painting the title bar. It is customizing the buttons. This is nothing new. In fact KDE 3 had no issues with it whatsoever. If it's not allowed to customize the API shouldn't grant the call. Gnome 2.28 that is also bundled in SuSE 11.1 shows has no problems with this call. If, however, it's documented somewhere that it's not allowed to customize titles bars and buttons I would appreciate a reference to it so we pass it on to the application people.

I am not sure exactly what you mean. Do you mean it is changing the button icons, or do you meant it is changing which buttons appear?


Man is the lowest-cost, 150-pound, nonlinear, all-purpose computer system which can be mass-produced by unskilled labor.
-NASA in 1965
mgraesslin
KDE Developer
Posts
572
Karma
7
OS
KDE's window manager never allowed applications to customize the buttons on the window decoration. This is out of scope for apps. I do not understand what you are trying to achieve but considering the various standards (ICCCM, NETWM/EWMH) the window manager is allowed to attach own window decorations to a window - no matter if an app wants it or not. There is no standaradized way to allow applications to interact with the decoration and at least KWin does not provide such a feature and won't at least for the lifetime of KDE 4 provide such a feature.

Nevertheless I think it's possible that I just completly missunderstood you. A screenshot might help to understand better...
Rasar
Registered Member
Posts
3
Karma
0
OS
Thank you all for your input. I was away for a few days and unable to reply. Here are some answers to the questions posed.

The purpose of customizing the buttons is to create the same look and feel for the application under any window manager.

The program shows the correct, requested buttons under gnome and KDE 3.5. Under KDE 4.x the window manager ignores the request for updated buttons (such as the maximize, minimize buttons) and shows the standard KDE (^ and v buttons for maximize and minimize). Upgrading to KDE 4.5 did not help the situation.

Here is a stand alone containing the source. Once you build it with the following Makefile, run it under gnome, KDE 3.5 and KDE 4.5 and the difference is obvious. There are additional comments in the program header. We look forward to your input.

Makefile:

CFLAGS= -g -I. -I/usr/X11R6/include -I/usr/include/X11
LDFLAGS=-L /usr/X11R6/lib64
title_bar_buttons: main.o
cc -o title_bar_buttons main.o $(LDFLAGS) -lXm -lXt -lX11

Code: Select all
/*
File description:


    Sample program demonstrating the mechanism which is used to create
    additional "buttons" in a dialog box's title area.

    The program creates a top-level window (an XmMainWindow) and adds an event
    handler on the Shell widget for that window (shell_event_handler). Whenever
    this window is mapped,  this event handler will attempt to create one
    additional X window to act as a push button on the title bar. This button is
    positioned (approximately) over the standard window-menu button.

    To create this window, the program traverses up the X-window hierarchy from
    the window of the shell widget until it locates the X window which is the
    child of the root window (find_title_bar_window). It then creates the new X
    window as a child of this one, positioning it in the top-left corner.

    The main event loop for this application then identifies events for these
    extra windows and processes them directly. All other events are processed
    through the usual Xt event dispatching mechanism.

   
    When KDE4 has desktop compositing turned on, the drawing of this X window
    is never seen.  When compositing is turned off, this X window is sometimes
    visible but any redrawing of the window decoration will overpaint it.
*/


#include <stdio.h>
#include <stdlib.h>
#include <sys/signal.h>

#include <X11/Xlib.h>
#include <Xm/Xm.h>
#include <Xm/CutPaste.h>
#include <Xm/XmStrDefs.h>
#include <Xm/MainW.h>

/* The set of X events for the shell widget that we need to respond to */
#define SHELL_EVENT_MASK    (StructureNotifyMask|ExposureMask|FocusChangeMask)

#define BUTTON_MARGIN       1
#define BUTTON_SEPARATION   2

typedef void (*button_callback)(Widget shell, int button_id, void* client_data);

typedef struct
{
    /* The definition of this button */
    int                     button_id;
    Boolean                 align_right;
    button_callback         activate;
    void*                   client_data;
    Boolean                 visible;
    Boolean                 sensitive;
   
    /* Private data for this button */
    Widget                  shell;
    Window                  win;
    int                     offset; /* -ve means align to the right, +ve means to the left */
    Dimension               width;
    Dimension               height;
    Pixel                   background;
    Pixel                   foreground;
    Pixel                   top_shadow_color;
    Pixel                   bottom_shadow_color;
    Boolean                 armed;
    Boolean                 pressed;
    Boolean                 shell_mapped;
}
shell_button_t, *shell_button_p_t;

typedef struct
{
    Widget                  shell;
    Boolean                 shell_mapped;
    Boolean                 shell_active;
    int                     n_buttons;
    shell_button_p_t        buttons;
}
window_data_t, *window_data_p_t;

static Widget find_shell(Widget widget);
static void draw_button(Display* dpy, shell_button_p_t btn);
static void handle_button_event(XEvent* event, void* client_data);
static Pixel get_pixel(Display* dpy, Colormap cmap, const char* color);
static void create_button(Display* dpy, Window parent, int x, int y, int width, int height, shell_button_p_t btn);
static void destroy_button(Display* dpy, shell_button_p_t btn);
static Window get_child_of_root_window(Display* dpy, Window win);
static Boolean find_title_bar_window(Display* dpy, Window shell, Window* parent,
                                     Position* x, Position* y, Dimension* width, Dimension* height);
static Boolean find_button_parent_window(
        Display* dpy, window_data_p_t window_data, Window* parent,
        Position* x, Position* y, Dimension* width, Dimension* height);
static void create_buttons(Display* dpy, window_data_p_t window_data);
static void destroy_buttons(Display* dpy, window_data_p_t window_data);
static void draw_buttons(Display* dpy, window_data_p_t window_data);
static void shell_event_handler(Widget widget, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch);
static void button_clicked(Widget shell, int button_id, void* client_data);

/*------------------------------------------------------------------------------------------------*/
static Widget find_shell(Widget widget)
{
    while (widget != NULL && !XtIsShell(widget))
        widget = XtParent(widget);

    return widget;
}

/*------------------------------------------------------------------------------------------------*/
static void draw_button(Display* dpy, shell_button_p_t btn)
{
    if (btn->win != None && btn->shell_mapped)
    {
        Position x_off = (btn->width + 1)/2;
        Position y_off = (btn->height + 1)/2;

        GC gc = XCreateGC(dpy, btn->win, 0, NULL);

        XClearWindow(dpy, btn->win);

        /* If the button is armed (cursor over it) then move the image a little bit */
        if (btn->armed && !btn->pressed)
        {
            x_off -= 1;
            y_off -= 1;
        }
       
        XSetGraphicsExposures(dpy, gc, False);
        XGCValues values;
        values.foreground = btn->foreground;
        values.background = btn->background;
        XChangeGC(dpy, gc, GCForeground|GCBackground, &values);
        XDrawLine(dpy, btn->win, gc, 0, 0, btn->width-1, btn->height-1);
        XDrawLine(dpy, btn->win, gc, btn->width-1, 0, 0, btn->height-1);
        XSetGraphicsExposures(dpy, gc, True);

        /* If the button is armed (cursor over it) then draw a border */
        if (btn->armed)
        {
            XGCValues values;
            values.foreground = btn->pressed ? btn->bottom_shadow_color : btn->top_shadow_color;
            values.background = btn->background;
            XChangeGC(dpy, gc, GCForeground|GCBackground, &values);
            XDrawLine(dpy, btn->win, gc, btn->width-1, 0, 0, 0);
            XDrawLine(dpy, btn->win, gc, 0, 0, 0, btn->height-1);
            values.foreground = btn->pressed ? btn->top_shadow_color : btn->bottom_shadow_color;
            values.background = btn->background;
            XChangeGC(dpy, gc, GCForeground|GCBackground, &values);
            XDrawLine(dpy, btn->win, gc, 0, btn->height-1, btn->width-1, btn->height-1);
            XDrawLine(dpy, btn->win, gc, btn->width-1, btn->height-1, btn->width-1, 0);
        }

        XFreeGC(dpy, gc);
    }
}
   
/*------------------------------------------------------------------------------------------------*/
static void handle_button_event(XEvent* event, void* client_data)
{
    shell_button_p_t btn = (shell_button_p_t)client_data;

    switch(event->type)
    {
        case Expose:
        {
            printf("handle_button_event: button X window 0x%x is now Exposed\n", btn->win);
            draw_button(event->xany.display, btn);
            break;
        }
        case EnterNotify:
        {
            if (btn->sensitive)
                btn->armed = True;
            draw_button(event->xany.display, btn);
            break;
        }
        case LeaveNotify:
        {
            btn->armed = False;
            btn->pressed = False;
            draw_button(event->xany.display, btn);
            break;
        }
        case ButtonPress:
        {
            if (btn->sensitive)
                btn->pressed = True;
            draw_button(event->xany.display, btn);
            break;
        }
        case ButtonRelease:
        {
            Boolean activate_button = btn->pressed;
            btn->pressed = False;
            draw_button(event->xany.display, btn);
            if (activate_button && btn->sensitive)
                (*btn->activate)(btn->shell, btn->button_id, btn->client_data);
            break;
        }
        case DestroyNotify:
        {
            btn->win = None;
            break;
        }
        default:
        {
            break;
        }
    }
}

/*------------------------------------------------------------------------------------------------*/
static Pixel get_pixel(Display* dpy, Colormap cmap, const char* color)
{
    XColor x_color;
   
    if (XParseColor(dpy, cmap, color, &x_color) && XAllocColor(dpy, cmap, &x_color))
        return x_color.pixel;
    else
        return 0;
}

/*------------------------------------------------------------------------------------------------*/
static void create_button(Display* dpy, Window parent, int x, int y, int width, int height, shell_button_p_t btn)
{
    Colormap                cmap;
   
    XtVaGetValues(btn->shell, XmNcolormap, &cmap, NULL);
   
    btn->foreground = get_pixel(dpy, cmap, "#000000");
    btn->background = get_pixel(dpy, cmap, "#c0c0c0");
    btn->top_shadow_color = get_pixel(dpy, cmap, "#eeeeee");
    btn->bottom_shadow_color = get_pixel(dpy, cmap, "#000000");
   
    if (btn->align_right)
        x += width;
   
    y += (height - btn->height)/2;
   
    btn->win = XCreateSimpleWindow(dpy, parent, x + btn->offset, y, btn->width, btn->height, 0, 0, btn->background);
    printf("create_button: created X window 0x%x as child of 0x%x\n", btn->win, parent);
 
    // Ensure that we don't get extra window decoration for these buttons... 
    XSetWindowAttributes attr;
    attr.override_redirect = True;
    XChangeWindowAttributes(dpy, btn->win, CWOverrideRedirect, &attr);
   
#if 0
    // Copy the decorations and functions from the main shell window in case this
    // window manager tries to use this "button" to identify what actions are permissable
    Atom mwm_hints_atom = XInternAtom(dpy, _XA_MOTIF_WM_HINTS, False);
    PropMwmHints mwm_hints;
    mwm_hints.flags = MWM_HINTS_DECORATIONS | MWM_HINTS_FUNCTIONS;
    XtVaGetValues(btn->shell, XmNmwmDecorations, &mwm_hints.decorations,
                              XmNmwmFunctions, &mwm_hints.functions,
                              NULL);
    XChangeProperty(dpy, btn->win, mwm_hints_atom, mwm_hints_atom, 32, PropModeReplace,
                    (unsigned char*)&mwm_hints, PROP_MWM_HINTS_ELEMENTS);
#endif
   
    btn->armed = False;
    btn->pressed = False;
   
    XSelectInput(dpy, btn->win, ExposureMask|EnterWindowMask|LeaveWindowMask|ButtonPressMask|ButtonReleaseMask);
   
    XMapWindow(dpy, btn->win);
   
    draw_button(dpy, btn);
}

/*------------------------------------------------------------------------------------------------*/
static void destroy_button(Display* dpy, shell_button_p_t btn)
{
    /* Actually the window manager will destroy the X window so all we need to
       do is unregister our event handler for this (now dead) window
    */
    if (btn->win != None)
    {
        XDestroyWindow(dpy, btn->win);
        btn->win = None;
    }
}

/*------------------------------------------------------------------------------------------------*/
static Window get_child_of_root_window(Display* dpy, Window win)
{
    Window root_win = None;
    Window parent_win = win;

    while(win && parent_win != root_win)
    {
        win = parent_win;
       
        Window* children = NULL;
        unsigned int n_children = 0;
       
        if (XQueryTree(dpy, win, &root_win, &parent_win, &children, &n_children) == 0)
            return None;
       
        if (children)
            XFree(children);
    }
   
    return win;
}

/*------------------------------------------------------------------------------------------------*/
static Boolean find_title_bar_window(Display* dpy, Window shell, Window* parent,
                                     Position* x, Position* y, Dimension* width, Dimension* height)
{
    *parent = get_child_of_root_window(dpy, shell);
   
    /* If this window is not mapped, it may not be a child of the root window */
    if (*parent == None)
        return False;
   
    /* Even if this window is mapped, it may not yet have a parent window created for it
       which holds the window decoration (i.e. the shell window may itself be the direct
       child of the root window).
    */
    if (*parent == shell)
        return False;
   
    XWindowAttributes get_attr;
    XGetWindowAttributes(dpy, shell, &get_attr);

    *x = 4; //layUIgetFrameSize();
    *y = 4; //layUIgetFrameSize();
    *width = get_attr.width;
    *height = 20; //layUIgetBannerSize();

    return True;
}

/*------------------------------------------------------------------------------------------------*/
static Boolean find_button_parent_window(
        Display* dpy, window_data_p_t window_data, Window* parent,
        Position* x, Position* y, Dimension* width, Dimension* height)
{
    if (find_title_bar_window(dpy, XtWindow(window_data->shell), parent,
                              x, y, width, height))
    {
        return True;
    }
   
    return False;
}
       
/*------------------------------------------------------------------------------------------------*/
static void create_buttons(Display* dpy, window_data_p_t window_data)
{
    Window parent;
    Position x, y;
    Dimension width, height;
           
    if (find_button_parent_window(dpy, window_data, &parent, &x, &y, &width, &height))
    {
        int left_offset = BUTTON_MARGIN, right_offset = -BUTTON_MARGIN + BUTTON_SEPARATION;
        int i;
       
        for(i = 0; i < window_data->n_buttons; i++)
        {
            shell_button_p_t btn = &window_data->buttons[i];

            if (!btn->visible)
            {
                btn->offset = 0;
                continue;
            }
            else if (btn->align_right)
            {
                right_offset -= btn->width + BUTTON_SEPARATION;
                btn->offset = right_offset;
            }
            else
            {
                btn->offset = left_offset;
                left_offset += btn->width + BUTTON_SEPARATION;
            }

            create_button(dpy, parent, x, y, width, height, btn);
        }
    }
    else /* Destroy any existing buttons... */
    {
        destroy_buttons(dpy, window_data);
    }
}

/*------------------------------------------------------------------------------------------------*/
static void destroy_buttons(Display* dpy, window_data_p_t window_data)
{
    int i;
   
    for(i = 0; i < window_data->n_buttons; i++)
    {
        shell_button_p_t btn = &window_data->buttons[i];
        destroy_button(dpy, btn);
    }
}

/*------------------------------------------------------------------------------------------------*/
static void draw_buttons(Display* dpy, window_data_p_t window_data)
{
    int i;
   
    for(i = 0; i < window_data->n_buttons; i++)
    {
        shell_button_p_t btn = &window_data->buttons[i];
        draw_button(dpy, btn);
    }
}

/*------------------------------------------------------------------------------------------------*/
static void move_buttons(Display* dpy, window_data_p_t window_data)
{
    Window parent;
    Position x, y;
    Dimension width, height;
           
    if (find_button_parent_window(dpy, window_data, &parent, &x, &y, &width, &height))
    {
        int i;
       
        for(i = 0; i < window_data->n_buttons; i++)
        {
            shell_button_p_t btn = &window_data->buttons[i];
           
            /* Move all right-aligned buttons */
            if (btn->visible && btn->align_right && btn->win != None)
            {
                if (height > btn->height)
                    XMoveWindow(dpy, btn->win, x + width + btn->offset, y + (height-btn->height)/2);
                else
                    XMoveWindow(dpy, btn->win, x + width + btn->offset, y);
            }
           
            /* Draw all visible buttons */
            if (btn->visible && btn->win != None)
                draw_button(dpy, btn);
        }
    }
}

/*------------------------------------------------------------------------------------------------*/
static void shell_event_handler(Widget widget, XtPointer client_data, XEvent *event, Boolean *continue_to_dispatch)
{
    window_data_p_t window_data = (window_data_p_t)client_data;
    Display* dpy = XtDisplay(widget);
    int i;

    switch(event->type)
    {
        case MapNotify:
        {
            window_data->shell_mapped = True;
            for(i = 0; i < window_data->n_buttons; i++)
            {
                shell_button_p_t btn = &window_data->buttons[i];
                btn->shell_mapped = True;
            }
            create_buttons(dpy, window_data);
            break;
        }
        case UnmapNotify:
        {
            destroy_buttons(dpy, window_data);
            for(i = 0; i < window_data->n_buttons; i++)
            {
                shell_button_p_t btn = &window_data->buttons[i];
                btn->shell_mapped = False;
            }
            window_data->shell_mapped = False;
            break;
        }
        case ConfigureNotify:
        {
            /* Shell window may have been resized, adjust the location of any right-aligned buttons */
            if (window_data->shell_mapped)
                move_buttons(dpy, window_data);
            break;
        }
        case FocusIn:
        {
            window_data->shell_active = True;

            if (window_data->shell_mapped)
            {
                draw_buttons(dpy, window_data);
            }

            break;   
        }
        case FocusOut:
        {
            window_data->shell_active = False;

            if (window_data->shell_mapped)
            {
                draw_buttons(dpy, window_data);
            }

            break;   
        }
        case Expose:
        {
            if (window_data->shell_mapped)
                draw_buttons(dpy, window_data);

            break;   
        }
        default:
            break;   
    }
}

/*------------------------------------------------------------------------------------------------*/
static void button_clicked(Widget shell, int button_id, void* client_data)
{
    printf("button_clicked\n");
}

/*------------------------------------------------------------------------------------------------*/
extern int main(int argc, char* argv[])
{
    Widget              toplevel    = XtInitialize(argv[0], "shell", NULL, 0, &argc, argv);
    Widget              mainwindow  = XtVaCreateManagedWidget("mainwindow", xmMainWindowWidgetClass, toplevel,
                                                                XmNwidth, 500,
                                                                XmNheight, 400,
                                                                NULL);
   
    shell_button_t button_data[1];
    button_data[0].button_id;
    button_data[0].align_right = False;
    button_data[0].activate = button_clicked;
    button_data[0].client_data = NULL;
    button_data[0].visible = True;
    button_data[0].sensitive = True;
    button_data[0].shell = find_shell(mainwindow);
    button_data[0].win = None;
    button_data[0].offset = 0;
    button_data[0].width = 16;
    button_data[0].height = 16;
    button_data[0].background = 0;
    button_data[0].foreground = 0;
    button_data[0].top_shadow_color = 0;
    button_data[0].bottom_shadow_color = 0;
    button_data[0].armed = False;
    button_data[0].pressed = False;
    button_data[0].shell_mapped = False;
   
    window_data_t window_data;
    window_data.shell = find_shell(mainwindow);
    window_data.shell_mapped = False;
    window_data.shell_active = False;
    window_data.n_buttons = 1;
    window_data.buttons = button_data;
   
    XtAddEventHandler(window_data.shell, SHELL_EVENT_MASK, True, shell_event_handler, &window_data);
   
    XtRealizeWidget(toplevel);

    XtAppContext context = XtWidgetToApplicationContext(toplevel);
    while(!XtAppGetExitFlag(context))
    {
        int i;
       
        XEvent event;
        XtAppNextEvent(context, &event);

        // If this X event is for one of the title bar buttons,
        // process it directly....
        for(i = 0; i < window_data.n_buttons; i++)
        {
            if (event.xany.window == window_data.buttons[i].win)
            {
                handle_button_event(&event, &window_data.buttons[i]);
                break;
            }
        }
       
        // ... otherwise process it through the normal Xt mechanism.
        if (i == window_data.n_buttons)
            XtDispatchEvent(&event);
    }
   
    return 0;
}

User avatar
TheBlackCat
Registered Member
Posts
2945
Karma
8
OS
I haven't read the code, but based on your description, it sounds like a really nasty hack designed to work around the rules specifically set up to prevent this sort of thing. Applications are not supposed to to handle these buttons.

The whole point of the window manager is that the windows are managed by the window manager, not by the client window. The fact that gnome and KDE 3.5 do not enforce this behavior sounds like a flaw in them. Based on my understanding and a quick reading of the specs, KDE 4's behavior seems to be the one that is in line with the intention of the specifications.

The fact that there is no built-in mechanism to modify the window's title bar button and you had to resort to hiding them with another window should probably be a hint that this sort of behavior is not supported. If you were meant to do this, there would be an API for it.


Man is the lowest-cost, 150-pound, nonlinear, all-purpose computer system which can be mass-produced by unskilled labor.
-NASA in 1965
mgraesslin
KDE Developer
Posts
572
Karma
7
OS
Please read:
http://blog.martin-graesslin.com/blog/2 ... corations/ and http://blog.martin-graesslin.com/blog/2 ... corations/ and http://blog.martin-graesslin.com/blog/2 ... corations/ and http://blog.martin-graesslin.com/blog/2 ... corations/

Canonical btw dropped their idea about client side window decorations. It's not the task of the app to define the buttons. If you want to have a unified look between deco and app, just use the system defaults and you are done. My blog posts above should illustrate why what you tried to achieve is completely flawn and that it worked at all is pure luck.


Bookmarks



Who is online

Registered users: abc72656, Bing [Bot], daret, Google [Bot], lockheed, Sogou [Bot], Yahoo [Bot]