lv2 / pugl

A minimal portable API for embeddable GUIs
https://gitlab.com/lv2/pugl/
ISC License
181 stars 34 forks source link

Prevent segfault when $DISPLAY is set to something that doesn't exist #10

Closed 7890 closed 5 years ago

7890 commented 5 years ago

https://github.com/drobilla/pugl/blob/eada1042452e8708ca6c65f7c23ac3c59e4c53f0/pugl/pugl_x11.c#L86

        if(display==NULL)
        {
                fprintf(stderr, "Error: XOpenDisplay(0) failed\n");
                return 1;
        }

Eg. DISPLAY=:3 ./pugl_program If no X Server is running on :3, allow to terminate nicely.

drobilla commented 5 years ago

Thanks. This happens to already be a part of my branch that moves this "top level" stuff somewhere more appropriate, so it will get fixed along with that. I'll leave the issue open until then.

7890 commented 5 years ago

Somewhat unrelated, I have a ton of functions to propose, most of which already work on several platforms (mainly related to window management). Do you think it can make sense to share them early (in order to avoid eventual duplicate work), or do you prefer already hardened PR's? I could put a list of additional functions from pugl.h somewhere. The library made huge steps in the recent weeks, it's a pleasure to see it getting really good.

I just put it here, totally preliminary. There's some a new pugl struct for rectangles which proved to be handy.

//=============================================================================
/**
   @name Window Manager
   Functions for manipulating window aspects (WIP).
   @{
*/

/**
   Set a new caption for the window.
*/
PUGL_API void
puglSetWindowTitle(PuglView* view, const char *title);

/**
   Get a rectangle describing the screen.
   x and y will allways be 0.
   x=0, y=0 is the top left corner of the screen.
*/
PUGL_API void
puglGetScreenRect(PuglView* view, struct PuglRect* res);

/**
   Get a rectangle describing the position and size of the workare relative to the screen rectangle.
   This rectangle is of equal or smaller size than the screen rectangle, depending on taskbar and other widgets
   that should not be covered by a window (unless for fullscreen).
*/
PUGL_API void
puglGetWorkareaRect(PuglView* view, struct PuglRect* res);

/**
   Get a rectangle describing the position and size of the window relative to the screen rectangle.
   This rectangle is of equal or greater size than the view rectangle, depending on the current window decoration.
*/
PUGL_API void
puglGetWindowRect(PuglView* view, struct PuglRect* res);

/**
   Get a rectangle describing the position and size of the view (drawable area) relative to the screen rectangle.
*/
PUGL_API void
puglGetViewRect(PuglView* view, struct PuglRect* res);

/**
   Set window position on screen. x, y relate to the top left pixel of the window, including borders.
   The window and view sizes aren't changed.
   See puglGetWindowRect() to query the current position.
*/
PUGL_API void
puglSetWindowPosition(PuglView* view, int x, int y);

/**
   Resize window. width and height relate to window dimensions including window decorations/borders.
   The view size is updated accordingly.
   See puglGetWindowRect() to query the current dimensions.
*/
PUGL_API void
puglResizeWindow(PuglView* view, int width, int height);

/**
   Set both window position on screen and window dimensions.
   See puglGetWindowRect() to query the current position and dimensions.
*/
PUGL_API void
puglMoveResizeWindow(PuglView* view, int x, int y, int width, int height);

/**
   Set both window position on screen and window dimensions, using a rectangle.
   See puglGetWindowRect() to query the current position and dimensions.
*/
PUGL_API void
puglSetWindowRect(PuglView* view, struct PuglRect *rect);

/**
   Resize the view (drawable area).
*/
PUGL_API void
puglResizeView(PuglView* view, int width, int height);

/**
   Convenience function to place the window top left of the workarea.
*/
PUGL_API void
puglSetWindowPositionZero(PuglView* view);

/**
   Convenience function to reset window state to defaults:
   - not fullscreen
   - not minimized or maximized
   - not below or above others
   - with decorations
   - at top-left position in workarea
*/
PUGL_API void
puglResetWindow(PuglView* view);

/**
   Convenience function to center the window, respecting
   window decoration and workarea.
*/
PUGL_API void
puglCenterWindow(PuglView* view);

/**
   Use all available space from workarea to expand the window.
   new_state is one of PUGL_ADD, PUGL_REMOVE, PUGL_TOGGLE
*/
PUGL_API void
puglMaximizeWindow(PuglView* view, int new_state);

/**
   Query whether or not the window is currently maximized.
*/
PUGL_API bool
puglGetMaximized(PuglView* view);

/**
   Hide window, put it to taskbar (or similar facilities).
   new_state is one of PUGL_ADD, PUGL_REMOVE, PUGL_TOGGLE
*/
PUGL_API void
puglMinimizeWindow(PuglView* view, int new_state);

/**
   Query whether or not the window is currently minimized.
*/
PUGL_API bool
puglGetMinimized(PuglView* view);

/**
   Control fullscreen.
   new_state is one of PUGL_ADD, PUGL_REMOVE, PUGL_TOGGLE
*/
PUGL_API void
puglSetFullScreen(PuglView* view, int new_state);

/**
   Return the current fullscreen status.
*/
PUGL_API bool
puglGetFullScreen(PuglView* view);

/**
   Control window decoration.
   new_state is one of PUGL_ADD, PUGL_REMOVE, PUGL_TOGGLE
   See puglSetWindowDecoration() to query whether or not the window is currently deocrated.
*/
PUGL_API void
puglSetWindowDecoration(PuglView* view, int new_state);

/**
   Query whether or not the window is currently decorated.
   A decorated window means there is a border around view.
*/
PUGL_API bool
puglGetWindowDecoration(PuglView* view);

/**
   Set a window on top of every other window.
   new_state is one of PUGL_ADD, PUGL_REMOVE, PUGL_TOGGLE (!!! test toggle)
*/
PUGL_API void
puglSetWindowToFront(PuglView* view, int new_state);

/**
   Put a window to the lowest layer, other windows might cover it.
   new_state is one of PUGL_ADD, PUGL_REMOVE, PUGL_TOGGLE (!!! test toggle)
*/
PUGL_API void
puglSetWindowToBack(PuglView* view, int new_state);

/**
   Do not show window in taskbar, task switcher or similar facilities.
*/
PUGL_API void
puglHideInTaskbar(PuglView* view);

/**
   Get extents of a rectangle 'rect', given a reference rectangle 'reference'.

   - 'reference' is supposed to be of equal or greater size than 'rect'.
   - 'rect' is supposed to be fully covered by 'reference' (inside 'reference').
   - Both rectangles must refer to the same x,y origin.

\verbatim
common origin
.------
|
|  reference
|  .---------------------.
|  |       ^ t           |
   |    ___|____         |
   | l |        | r      |
   |<--|  rect  |------->|
   |   |________|        |
   |       | b           |
   |       v             |
   .---------------------.
\endverbatim

   Examples:
   - reference: screen, rect: window
   - reference: workarea, rect: window

   left, top, right and bottom values are either 0 or a positive number (see arrows in diag.)
*/
PUGL_API void
puglGetExtents(struct PuglRect* reference, struct PuglRect* rect, struct PuglExtents* res);

/**
   Get distances of view edges to window edges.
   This describes the window decoration dimensions.
*/
PUGL_API void
puglGetWindowExtents(PuglView* view, struct PuglExtents *res);

/**
   Move the window towards the specified direction, so that it aligns with the workarea edge.
*/
void
puglMoveWindowEdge(PuglView* view, int direction);

/**
   Expand the window towards the specified direction, so that it aligns with the workarea edge.
*/
PUGL_API void
puglExpandWindowEdge(PuglView* view, int direction);

/**
   Prepare a byte buffer with pixel data to be used as window icon.
   The pixel data buffer layout must be RGBA (32 bits per pixel, 8 bits per component).
   icon_len will contain the length of the prepared buffer.
   The functions returns a pointer to the prepared buffer.
   A caller must arrange for a later free() to release resources.
*/
PUGL_API char*
puglPrepareIconBuffer(char *rgba_buf, int width, int height, int *icon_len);

/**
   Set an icon for the window, using a prepared (native) pixel buffer.
*/
PUGL_API void
puglSetWindowIcon(PuglView* view, const char* icon_buffer, int size_bytes);

/**
   Turn on/off default mouse pointer for the window hosting the view.
*/
PUGL_API void
puglSetWindowMousePointer(PuglView* view, int action);

/**
   Test if two rectangles are equal
*/
PUGL_API bool
puglTestRectEquality(struct PuglRect* rect1, struct PuglRect* rect2);

I'm happy for early feedback if something looks odd.

drobilla commented 5 years ago

It doesn't hurt, but keep in mind that I don't consider "a ton of functions" a good thing and aim to make the API surface as small as possible. There is a lot there that doesn't need to be.

To that end, everything that can be a hint should be. Mouse pointer visibility and decoration seem like they should.

I was also considering adding a rect type, but I'm not sure it's worth the bother (and if it should then should it be used in events as well, etc).

(Also generally pretty hostile to applications playing window manager for themselves, for the record, but I understand that some of these things are necessary)

7890 commented 5 years ago

Thanks for the ffedback, the struct PuglRect showed to be extremely handy. It can be used at many places to simplify things. I haven't thought about it being useful for event callbacks too, I think it's a valid idea!

Playing window manager: I can see that this is not the core functionality of pugl. However there is 'Needs Attention' :) Thinking further, it would be nice if window manager functions can be put to the abstraction, so that this part also isn't platform-specific anymore. A side-effect of it is that applications running without a WM have some ways to take control.

For instance, it should be possible for an application to mimic a splash screen: without decorations, centered in the workarea, not visible in the taskbar. An application should be able to do this using the pugl API, without caring about paltform-specific code or using external WM functions.

This is another rough goal to achieve with WM functions: create a (context) menu in pugl that runs on all platforms, isn't captured inside another window and doesn't rely on native code. From what I see this involves full control on window decoration, placement, layering etc.

For now I'll continue the WIP to see where it fails.

drobilla commented 5 years ago

"Needs attention" is a feature built in to platforms that is handled in whatever way. From an application code point of view it is entirely semantic. I am totally receptive to such things.

That's quite different from placing, resizing, and restacking windows, which is bad behavior 98% of the time, and breaks some setup that doesn't fit misguided developer assumptions the other 2% of the time (tiling WM user checking in). Submit PRs for whatever you want, but things that are questionable and not motivated by requirements of an actual project are probably going to have a hard time. Pugl is opinionated on purpose to keep the scope in check. For example, I think splash screens are a terrible idea that exist only to paper over embarrassingly bloated apps that take too long to display. So, no, I don't actually think it should be possible, or at least if it ever is, it will be a side effect rather than an actual goal.

Pugl is more intended for simple things that just need a graphics view that they live inside of, that will work everywhere (including embedded-like systems that don't even have these WIMP concepts at all). I, along with nearly the entire Pugl userbase, am quite jaded with the desktop toolkit world, and just want something that works.

Menus are probably the most interesting case, mainly because MacOS requires that to be a totally platform dependent thing. That is probably best done as a separate library though.

drobilla commented 5 years ago

Also keep in mind the entire point of Pugl even existing at all: being embeddable.

Most of the things you are working on seem to be contrary to that purpose. Yes, there are features to work nicely as a stand-alone application, but that is so that conceptually (or literally) embeddable GUIs, e.g. plugins, can also work nicely as applications.

There is no point in implementing features in Pugl that only make sense for stand-alone applications. If you're writing something that only makes sense as an application, then use GLFW or a toolkit. They are much more comprehensive, not to mention popular and widely used.

7890 commented 5 years ago

I can for the most part follow your arguments. In my perception the line between what is still inside the domain of what pugl could do and whatnot is blurry. Setting a window position or an icon can be a semantic action, from one POV. Some analogy comes to mind: a room with chairs (representing the screen real estate). You either choose which chair to settle on or let somebody guide you to the chair you're supposed to sit on. Sometimes the guide isn't there and guides from different rooms speak different languages. Maybe you'd want to switch chairs at some point, or reserve all chairs at once. OK, maybe this not the best analogy ;

7890 commented 5 years ago

As soon as there are multiple windows involved which should play together in a strictly defined manner (eg. window 2 is always exactly at the right edge of window 1), having control on how and where they appear on the screen is a necessity. In such cases, the WM can not know what's the right thing to do. ~ "I want to sit next to John, even if that chair is already taken"

7890 commented 5 years ago

Re splashscreen, indeed they are debatable. However it can go beyond papering over bloated app, one example is Blender where the splash is some sort of an entry point to choose what to do. https://docs.blender.org/manual/en/latest/interface/splash.html. Of course this principle to startup is itself debatable again but it should be a decision left to be taken by the developers. Other examples of supposedly unbloated apps with splash are Ardour, Gimp you name it. At the end of the day having splashscreens isn't a goal per se. The goal would be more generic so that having them is not excluded from the possibilities.

drobilla commented 5 years ago

To be totally frank, I am not terribly keen on your characteristic/notorious armchair development derailing the project because of theories or whatever ;)

eg. window 2 is always exactly at the right edge of window 1

This is absolutely atrocious and just plain wrong behavior. No.

it should be a decision left to be taken by the developers

Pugl is not designed for authors of large multi-window desktop applications, and it is not a kitchen sink that is intended to be maximally flexible so developers can make whatever weird decisions. It is, again, minimal and deliberately opinionated, and the only reason it exists at all is for embeddable UIs.

so that having them is not excluded from the possibilities.

Given the possibilities you are using as examples here, I'd say that excluding them is very much a good thing. API bloat that only supports bad non-portable 90s ideas like this will, without question, be rejected.

The only developers whose decisions I care about are real existing ones who are using Pugl for their UIs (which almost universally means audio plugin UIs). If you want to work on Pugl I suggest you spend your time on things that might actually be useful for them.

7890 commented 5 years ago

Sure, fair enough. Eventually as a plugin? A WM handler plugin for pugl, as a separate linkable unit.

7890 commented 5 years ago

Pass it a view and it can do all the unreasonable stuff

7890 commented 5 years ago

..and later on the menu unit (as a plugin). file open/save dialogue is yet another candidate. wouldn't it be nice to 'plugin' those without even changing pugl code? in theory. not to speak of drag & drop and eventually out-of-the-box useful widget sets, registered with pugl so they have the insights needed.

drobilla commented 5 years ago

A WM handler plugin for pugl

You seriously need to start asking yourself "Why?"

7890 commented 5 years ago

For a scenario like this: context-click inside any pugl view -> a context menu appears which is a totally independent window with an independent event loop and the ability to expand respecting the desktop area. It's on top and has no decorations. When it looses focus it disappears.

7890 commented 5 years ago

90s stuff u know :)

7890 commented 5 years ago

The alternative is to implement platform-specific menus. Doing it with WM-ized pugl looks like doable yet to proof. Or then use a bloated framework that offers menus..

7890 commented 5 years ago

I can hear that pugl isn't a plugin host and that's reasonable. I have no expectations at all, no obligations or userbase-backed needs. I think it's a brilliant library period.

drobilla commented 5 years ago

Or then use a bloated framework that offers menus

Bingo. Pugl is for single window UIs that work everywhere. Think more along the lines of games, and less along the lines of Windows 95. Countless apps have done just fine within those limitations.

Video mode and fullscreen support and such is more appropriate, because phones or WMless RPi installations or whatever count as "everywhere".

7890 commented 5 years ago

I saw just now the there's an "App" branch and it already has covered some of this... , https://github.com/drobilla/pugl/commit/353d91391afe18b8c38941e41c392eaf40efbb80 and https://github.com/drobilla/pugl/commit/dd0034886c08f7c06b084ac09dbe3c09f55ed860 and PuglRect struct. Seems like "App" branch has much things that will cooperate with my WM prototype nicely, making it slimmer after a consolidation. Also i found https://github.com/drobilla/pugl/commit/093825d7e9e6743ad6342b6380dd9e42d08f86af is good to better understand the embedding! This will be great! Last but not least https://github.com/drobilla/pugl/blob/490dd5ef6ed9fbc29ce5a53fb44db26dc0fc7bf0/pugl/detail/x11.c#L68 addressed the $DISPLAY case.