Fix updates to mwm_hints, and make configure request move and resize atomic
This commit is contained in:
parent
bec4f7c894
commit
feb7462e38
8 changed files with 134 additions and 109 deletions
|
@ -1,6 +1,11 @@
|
|||
(Format: Year/Month/Day)
|
||||
Changes for 0.9.6:
|
||||
*03/09/24:
|
||||
* Fix updates to mwm_hints, and make configure request
|
||||
move and resize atomic (Simon)
|
||||
- fixes mplayer fullscreen window being moveable
|
||||
- fixes focus loss when toggling mplayer fullscreen
|
||||
FbWinFrame.hh/cc Window.hh/cc FbAtoms.hh/cc WinClient.cc
|
||||
* Fixed the "aterm"-bug (Henrik)
|
||||
Window.cc
|
||||
*03/09/23:
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: FbAtoms.cc,v 1.8 2003/05/13 11:47:29 fluxgen Exp $
|
||||
// $Id: FbAtoms.cc,v 1.9 2003/09/24 14:02:25 rathnor Exp $
|
||||
|
||||
#include "FbAtoms.hh"
|
||||
#include "App.hh"
|
||||
|
@ -46,7 +46,6 @@ FbAtoms::~FbAtoms() {
|
|||
FbAtoms *FbAtoms::instance() {
|
||||
if (s_singleton == 0)
|
||||
throw string("Create one instance of FbAtoms first!");
|
||||
|
||||
return s_singleton;
|
||||
}
|
||||
|
||||
|
@ -60,6 +59,7 @@ void FbAtoms::initAtoms() {
|
|||
xa_wm_change_state = XInternAtom(display, "WM_CHANGE_STATE", False);
|
||||
xa_wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", False);
|
||||
xa_wm_take_focus = XInternAtom(display, "WM_TAKE_FOCUS", False);
|
||||
motif_wm_hints = XInternAtom(display, "_MOTIF_WM_HINTS", False);
|
||||
|
||||
blackbox_hints = XInternAtom(display, "_BLACKBOX_HINTS", False);
|
||||
blackbox_attributes = XInternAtom(display, "_BLACKBOX_ATTRIBUTES", False);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: FbAtoms.hh,v 1.10 2003/04/26 18:56:02 fluxgen Exp $
|
||||
// $Id: FbAtoms.hh,v 1.11 2003/09/24 14:02:25 rathnor Exp $
|
||||
#ifndef FBATOMS_HH
|
||||
#define FBATOMS_HH
|
||||
|
||||
|
@ -34,7 +34,6 @@ public:
|
|||
|
||||
static FbAtoms *instance();
|
||||
|
||||
|
||||
inline Atom getWMChangeStateAtom() const { return xa_wm_change_state; }
|
||||
inline Atom getWMStateAtom() const { return xa_wm_state; }
|
||||
inline Atom getWMDeleteAtom() const { return xa_wm_delete_window; }
|
||||
|
@ -44,6 +43,7 @@ public:
|
|||
// this atom is for normal app->WM hints about decorations, stacking,
|
||||
// starting workspace etc...
|
||||
inline Atom getFluxboxHintsAtom() const { return blackbox_hints;}
|
||||
inline Atom getMWMHintsAtom() const { return motif_wm_hints; }
|
||||
|
||||
// these atoms are for normal app->WM interaction beyond the scope of the
|
||||
// ICCCM...
|
||||
|
@ -78,6 +78,8 @@ private:
|
|||
// NETAttributes
|
||||
Atom blackbox_attributes, blackbox_change_attributes, blackbox_hints;
|
||||
|
||||
Atom motif_wm_hints;
|
||||
|
||||
// NETStructureMessages
|
||||
Atom blackbox_structure_messages, blackbox_notify_startup,
|
||||
blackbox_notify_window_add, blackbox_notify_window_del,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: FbWinFrame.cc,v 1.53 2003/09/16 13:11:41 rathnor Exp $
|
||||
// $Id: FbWinFrame.cc,v 1.54 2003/09/24 14:02:25 rathnor Exp $
|
||||
|
||||
#include "FbWinFrame.hh"
|
||||
|
||||
|
@ -163,53 +163,66 @@ void FbWinFrame::shade() {
|
|||
|
||||
|
||||
void FbWinFrame::move(int x, int y) {
|
||||
// don't update unless we really changes position
|
||||
if (x == window().x() && y == window().y())
|
||||
return;
|
||||
|
||||
window().move(x, y);
|
||||
// update transparent only if we need to
|
||||
if (theme().alpha() == 255)
|
||||
return;
|
||||
|
||||
// restart update timer
|
||||
m_update_timer.start();
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
moveResize(x, y, 0, 0, true, false);
|
||||
}
|
||||
|
||||
void FbWinFrame::resize(unsigned int width, unsigned int height) {
|
||||
// update unshaded size if we're in shaded state and just resize width
|
||||
if (m_shaded) {
|
||||
moveResize(0, 0, width, height, false, true);
|
||||
}
|
||||
|
||||
// need an atomic moveresize where possible
|
||||
void FbWinFrame::moveResizeForClient(int x, int y, unsigned int width, unsigned int height, bool move, bool resize) {
|
||||
// total height for frame
|
||||
|
||||
unsigned int total_height = height;
|
||||
|
||||
if (resize) {
|
||||
// having a titlebar = 1 extra border + titlebar height
|
||||
if (m_use_titlebar)
|
||||
total_height += m_titlebar.height() + m_titlebar.borderWidth();
|
||||
// having a handle = 1 extra border + handle height
|
||||
if (m_use_handle)
|
||||
total_height += m_handle.height() + m_handle.borderWidth();
|
||||
}
|
||||
moveResize(x, y, width, total_height, move, resize);
|
||||
}
|
||||
|
||||
void FbWinFrame::resizeForClient(unsigned int width, unsigned int height) {
|
||||
moveResizeForClient(0, 0, width, height, false, true);
|
||||
}
|
||||
|
||||
void FbWinFrame::moveResize(int x, int y, unsigned int width, unsigned int height, bool move, bool resize) {
|
||||
if (move && x == window().x() && y == window().y())
|
||||
move = false;
|
||||
|
||||
if (resize && width == FbWinFrame::width() && height == FbWinFrame::height())
|
||||
resize = false;
|
||||
|
||||
if (!move && !resize)
|
||||
return;
|
||||
|
||||
if (resize && m_shaded) {
|
||||
// update unshaded size if we're in shaded state and just resize width
|
||||
m_width_before_shade = width;
|
||||
m_height_before_shade = height;
|
||||
m_window.resize(width, m_window.height());
|
||||
height = m_window.height();
|
||||
}
|
||||
|
||||
if (move && resize) {
|
||||
m_window.moveResize(x, y, width, height);
|
||||
} else if (move) {
|
||||
m_window.move(x, y);
|
||||
// this stuff will be caught by reconfigure if resized
|
||||
if (theme().alpha() != 255) {
|
||||
// restart update timer
|
||||
m_update_timer.start();
|
||||
}
|
||||
} else {
|
||||
m_window.resize(width, height);
|
||||
}
|
||||
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
void FbWinFrame::resizeForClient(unsigned int width, unsigned int height) {
|
||||
// total height for frame
|
||||
unsigned int total_height = height;
|
||||
|
||||
// having a titlebar = 1 extra border + titlebar height
|
||||
if (m_use_titlebar)
|
||||
total_height += m_titlebar.height() + m_titlebar.borderWidth();
|
||||
// having a handle = 1 extra border + handle height
|
||||
if (m_use_handle)
|
||||
total_height += m_handle.height() + m_handle.borderWidth();
|
||||
resize(width, total_height);
|
||||
}
|
||||
|
||||
void FbWinFrame::moveResize(int x, int y, unsigned int width, unsigned int height) {
|
||||
move(x, y);
|
||||
if (width != FbWinFrame::width() || height != FbWinFrame::height())
|
||||
resize(width, height);
|
||||
if (resize)
|
||||
reconfigure();
|
||||
}
|
||||
|
||||
void FbWinFrame::setFocus(bool newvalue) {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: FbWinFrame.hh,v 1.20 2003/09/14 10:32:31 fluxgen Exp $
|
||||
// $Id: FbWinFrame.hh,v 1.21 2003/09/24 14:02:25 rathnor Exp $
|
||||
|
||||
#ifndef FBWINFRAME_HH
|
||||
#define FBWINFRAME_HH
|
||||
|
@ -80,7 +80,12 @@ public:
|
|||
void resize(unsigned int width, unsigned int height);
|
||||
/// resize client to specified size and resize frame to it
|
||||
void resizeForClient(unsigned int width, unsigned int height);
|
||||
void moveResize(int x, int y, unsigned int width, unsigned int height);
|
||||
|
||||
// for when there needs to be an atomic move+resize operation
|
||||
void moveResizeForClient(int x, int y, unsigned int width, unsigned int height, bool move = true, bool resize = true);
|
||||
|
||||
// can elect to ignore move or resize (mainly for use of move/resize individual functions
|
||||
void moveResize(int x, int y, unsigned int width, unsigned int height, bool move = true, bool resize = true);
|
||||
|
||||
/// set focus/unfocus style
|
||||
void setFocus(bool newvalue);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: WinClient.cc,v 1.26 2003/09/21 13:24:27 rathnor Exp $
|
||||
// $Id: WinClient.cc,v 1.27 2003/09/24 14:02:25 rathnor Exp $
|
||||
|
||||
#include "WinClient.hh"
|
||||
|
||||
|
@ -356,12 +356,12 @@ void WinClient::updateMWMHints() {
|
|||
int format;
|
||||
Atom atom_return;
|
||||
unsigned long num = 0, len = 0;
|
||||
Atom motif_wm_hints = XInternAtom(FbTk::App::instance()->display(), "_MOTIF_WM_HINTS", False);
|
||||
|
||||
if (m_mwm_hint) {
|
||||
XFree(m_mwm_hint);
|
||||
m_mwm_hint = 0;
|
||||
}
|
||||
Atom motif_wm_hints = FbAtoms::instance()->getMWMHintsAtom();
|
||||
|
||||
if (!(property(motif_wm_hints, 0,
|
||||
PropMwmHintsElements, false,
|
||||
|
|
112
src/Window.cc
112
src/Window.cc
|
@ -22,7 +22,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: Window.cc,v 1.233 2003/09/24 11:33:40 fluxgen Exp $
|
||||
// $Id: Window.cc,v 1.234 2003/09/24 14:02:25 rathnor Exp $
|
||||
|
||||
#include "Window.hh"
|
||||
|
||||
|
@ -415,9 +415,9 @@ void FluxboxWindow::init() {
|
|||
decorations.close = false;
|
||||
|
||||
if (m_client->getBlackboxHint() != 0)
|
||||
getBlackboxHints();
|
||||
updateBlackboxHintsFromClient(*m_client);
|
||||
else
|
||||
getMWMHints();
|
||||
updateMWMHintsFromClient(*m_client);
|
||||
|
||||
//!!
|
||||
// fetch client size and placement
|
||||
|
@ -498,8 +498,7 @@ void FluxboxWindow::init() {
|
|||
|
||||
}
|
||||
|
||||
frame().move(wattrib.x, wattrib.y);
|
||||
frame().resizeForClient(wattrib.width, wattrib.height);
|
||||
frame().moveResizeForClient(wattrib.x, wattrib.y, wattrib.width, wattrib.height);
|
||||
|
||||
// if we're a transient then we should be on the same layer as our parent
|
||||
if (m_client->isTransient() &&
|
||||
|
@ -907,8 +906,8 @@ bool FluxboxWindow::isGroupable() const {
|
|||
|
||||
void FluxboxWindow::associateClientWindow() {
|
||||
m_client->setBorderWidth(0);
|
||||
updateTitleFromClient();
|
||||
updateIconNameFromClient();
|
||||
updateTitleFromClient(*m_client);
|
||||
updateIconNameFromClient(*m_client);
|
||||
|
||||
frame().setClientWindow(*m_client);
|
||||
frame().resizeForClient(m_client->width(), m_client->height());
|
||||
|
@ -964,25 +963,24 @@ void FluxboxWindow::reconfigure() {
|
|||
}
|
||||
|
||||
/// update current client title and title in our frame
|
||||
void FluxboxWindow::updateTitleFromClient() {
|
||||
m_client->updateTitle();
|
||||
void FluxboxWindow::updateTitleFromClient(WinClient &client) {
|
||||
client.updateTitle();
|
||||
// compare old title with new and see if we need to update
|
||||
// graphics
|
||||
if (m_labelbuttons[m_client]->text() != m_client->title()) {
|
||||
m_labelbuttons[m_client]->setText(m_client->title());
|
||||
m_labelbuttons[m_client]->clear(); // redraw text
|
||||
m_labelbuttons[m_client]->updateTransparent();
|
||||
if (m_labelbuttons[&client]->text() != client.title()) {
|
||||
m_labelbuttons[&client]->setText(client.title());
|
||||
m_labelbuttons[&client]->clear(); // redraw text
|
||||
m_labelbuttons[&client]->updateTransparent();
|
||||
}
|
||||
}
|
||||
|
||||
/// update icon title from client
|
||||
void FluxboxWindow::updateIconNameFromClient() {
|
||||
m_client->updateIconTitle();
|
||||
void FluxboxWindow::updateIconNameFromClient(WinClient &client) {
|
||||
client.updateIconTitle();
|
||||
}
|
||||
|
||||
|
||||
void FluxboxWindow::getMWMHints() {
|
||||
const WinClient::MwmHints *hint = m_client->getMwmHint();
|
||||
void FluxboxWindow::updateMWMHintsFromClient(WinClient &client) {
|
||||
const WinClient::MwmHints *hint = client.getMwmHint();
|
||||
|
||||
if (!hint) return;
|
||||
|
||||
|
@ -1049,8 +1047,8 @@ void FluxboxWindow::updateFunctions() {
|
|||
setupWindow();
|
||||
}
|
||||
|
||||
void FluxboxWindow::getBlackboxHints() {
|
||||
const FluxboxWindow::BlackboxHints *hint = m_client->getBlackboxHint();
|
||||
void FluxboxWindow::updateBlackboxHintsFromClient(WinClient &client) {
|
||||
const FluxboxWindow::BlackboxHints *hint = client.getBlackboxHint();
|
||||
if (!hint) return;
|
||||
|
||||
if (hint->flags & ATTRIB_SHADED)
|
||||
|
@ -1912,10 +1910,12 @@ void FluxboxWindow::handleEvent(XEvent &event) {
|
|||
// case MapRequest:
|
||||
// mapRequestEvent(event.xmaprequest);
|
||||
//break;
|
||||
case PropertyNotify:
|
||||
if (event.xproperty.state != PropertyDelete) {
|
||||
propertyNotifyEvent(event.xproperty.atom);
|
||||
case PropertyNotify: {
|
||||
WinClient *client = findClient(event.xproperty.window);
|
||||
if (client) {
|
||||
propertyNotifyEvent(*client, event.xproperty.atom);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -2076,8 +2076,7 @@ void FluxboxWindow::destroyNotifyEvent(XDestroyWindowEvent &de) {
|
|||
}
|
||||
|
||||
|
||||
void FluxboxWindow::propertyNotifyEvent(Atom atom) {
|
||||
|
||||
void FluxboxWindow::propertyNotifyEvent(WinClient &client, Atom atom) {
|
||||
switch(atom) {
|
||||
case XA_WM_CLASS:
|
||||
case XA_WM_CLIENT_MACHINE:
|
||||
|
@ -2085,31 +2084,28 @@ void FluxboxWindow::propertyNotifyEvent(Atom atom) {
|
|||
break;
|
||||
|
||||
case XA_WM_TRANSIENT_FOR: {
|
||||
// TODO: this property notify should be handled by winclient
|
||||
// but for now we'll justhave to update all transient info
|
||||
//bool was_transient = isTransient();
|
||||
for_each(clientList().begin(), clientList().end(),
|
||||
mem_fun(&WinClient::updateTransientInfo));
|
||||
reconfigure();
|
||||
// TODO: this is broken whilst we don't know which client
|
||||
bool was_transient = client.isTransient();
|
||||
client.updateTransientInfo();
|
||||
// update our layer to be the same layer as our transient for
|
||||
//if (isTransient() && isTransient() != was_transient)
|
||||
// layerItem().setLayer(getTransientFor()->layerItem().getLayer());
|
||||
if (client.isTransient() && !was_transient
|
||||
&& client.transientFor()->fbwindow())
|
||||
layerItem().setLayer(client.transientFor()->fbwindow()->layerItem().getLayer());
|
||||
|
||||
} break;
|
||||
|
||||
case XA_WM_HINTS:
|
||||
m_client->updateWMHints();
|
||||
client.updateWMHints();
|
||||
hintSig().notify(); // notify listeners
|
||||
break;
|
||||
|
||||
case XA_WM_ICON_NAME:
|
||||
updateIconNameFromClient();
|
||||
client.updateIconTitle();
|
||||
updateIconNameFromClient(client);
|
||||
updateIcon();
|
||||
break;
|
||||
|
||||
case XA_WM_NAME:
|
||||
updateTitleFromClient();
|
||||
updateTitleFromClient(client);
|
||||
|
||||
if (! iconic)
|
||||
screen().getWorkspace(m_workspace_number)->update();
|
||||
|
@ -2120,27 +2116,27 @@ void FluxboxWindow::propertyNotifyEvent(Atom atom) {
|
|||
break;
|
||||
|
||||
case XA_WM_NORMAL_HINTS: {
|
||||
m_client->updateWMNormalHints();
|
||||
client.updateWMNormalHints();
|
||||
|
||||
if ((m_client->normal_hint_flags & PMinSize) &&
|
||||
(m_client->normal_hint_flags & PMaxSize)) {
|
||||
if ((client.normal_hint_flags & PMinSize) &&
|
||||
(client.normal_hint_flags & PMaxSize)) {
|
||||
|
||||
if (m_client->max_width != 0 && m_client->max_width <= m_client->min_width &&
|
||||
m_client->max_height != 0 && m_client->max_height <= m_client->min_height) {
|
||||
if (client.max_width != 0 && client.max_width <= client.min_width &&
|
||||
client.max_height != 0 && client.max_height <= client.min_height) {
|
||||
decorations.maximize = false;
|
||||
decorations.handle = false;
|
||||
functions.resize=false;
|
||||
functions.maximize=false;
|
||||
} else {
|
||||
// TODO: is broken while handled by FbW, needs to be in WinClient
|
||||
if (! winClient().isTransient()) {
|
||||
if (! client.isTransient()) {
|
||||
decorations.maximize = true;
|
||||
decorations.handle = true;
|
||||
functions.maximize = true;
|
||||
}
|
||||
functions.resize = true;
|
||||
}
|
||||
|
||||
setupWindow();
|
||||
}
|
||||
|
||||
// save old values
|
||||
|
@ -2159,13 +2155,16 @@ void FluxboxWindow::propertyNotifyEvent(Atom atom) {
|
|||
}
|
||||
|
||||
default:
|
||||
if (atom == FbAtoms::instance()->getWMProtocolsAtom()) {
|
||||
m_client->updateWMProtocols();
|
||||
//!!TODO check this area
|
||||
// reset window actions
|
||||
setupWindow();
|
||||
|
||||
}
|
||||
FbAtoms *fbatoms = FbAtoms::instance();
|
||||
if (atom == fbatoms->getWMProtocolsAtom()) {
|
||||
client.updateWMProtocols();
|
||||
} else if (atom == fbatoms->getMWMHintsAtom()) {
|
||||
client.updateMWMHints();
|
||||
updateMWMHintsFromClient(client);
|
||||
} else if (atom == fbatoms->getFluxboxHintsAtom()) {
|
||||
client.updateBlackboxHints();
|
||||
updateBlackboxHintsFromClient(client);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -2208,11 +2207,12 @@ void FluxboxWindow::configureRequestEvent(XConfigureRequestEvent &cr) {
|
|||
|
||||
// the request is for client window so we resize the frame to it first
|
||||
if (frame().width() != cw || frame().height() != ch) {
|
||||
frame().resizeForClient(cw, ch);
|
||||
if (frame().x() != cx || frame().y() != cy)
|
||||
frame().moveResizeForClient(cx, cy, cw, ch);
|
||||
else
|
||||
frame().resizeForClient(cw, ch);
|
||||
send_notify = true;
|
||||
}
|
||||
|
||||
if (frame().x() != cx || frame().y() != cy) {
|
||||
} else if (frame().x() != cx || frame().y() != cy) {
|
||||
move(cx, cy);
|
||||
// since we already send a notify in move we don't need to do that again
|
||||
send_notify = false;
|
||||
|
@ -2627,7 +2627,7 @@ void FluxboxWindow::applyDecorations(bool initial) {
|
|||
|
||||
// if the location changes, shift it
|
||||
if (grav_x != 0 || grav_y != 0)
|
||||
frame().move(grav_x + frame().x(), grav_y + frame().y());
|
||||
move(grav_x + frame().x(), grav_y + frame().y());
|
||||
|
||||
frame().reconfigure();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: Window.hh,v 1.94 2003/09/23 13:52:05 rathnor Exp $
|
||||
// $Id: Window.hh,v 1.95 2003/09/24 14:02:25 rathnor Exp $
|
||||
|
||||
#ifndef WINDOW_HH
|
||||
#define WINDOW_HH
|
||||
|
@ -236,7 +236,7 @@ public:
|
|||
void unmapNotifyEvent(XUnmapEvent &unmapev);
|
||||
void exposeEvent(XExposeEvent &ee);
|
||||
void configureRequestEvent(XConfigureRequestEvent &ce);
|
||||
void propertyNotifyEvent(Atom a);
|
||||
void propertyNotifyEvent(WinClient &client, Atom a);
|
||||
void enterNotifyEvent(XCrossingEvent &ev);
|
||||
void leaveNotifyEvent(XCrossingEvent &ev);
|
||||
//@}
|
||||
|
@ -374,11 +374,11 @@ private:
|
|||
|
||||
bool getState();
|
||||
/// gets title string from client window and updates frame's title
|
||||
void updateTitleFromClient();
|
||||
void updateTitleFromClient(WinClient &client);
|
||||
/// gets icon name from client window
|
||||
void updateIconNameFromClient();
|
||||
void getMWMHints();
|
||||
void getBlackboxHints();
|
||||
void updateIconNameFromClient(WinClient &client);
|
||||
void updateMWMHintsFromClient(WinClient &client);
|
||||
void updateBlackboxHintsFromClient(WinClient &client);
|
||||
void saveBlackboxAttribs();
|
||||
void setNetWMAttributes();
|
||||
void associateClientWindow();
|
||||
|
|
Loading…
Reference in a new issue