    }

    // [internal] Return string to describe display title.
    CImg<charT> __display() const {
      CImg<charT> res, str;
      cimglist_for(*this,l) {
        CImg<charT>::string((char*)_data[l]).move_to(str);
        if (l!=width() - 1) {
          str.resize(str._width + 1,1,1,1,0);
          str[str._width - 2] = ',';
          str[str._width - 1] = ' ';
        }
        res.append(str,'x');
      }
      if (!res) return CImg<charT>(1,1,1,1,0).move_to(res);
      cimg::strellipsize(res,128,false);
      if (_width>1) {
        const unsigned int l = (unsigned int)std::strlen(res);
        if (res._width<=l + 16) res.resize(l + 16,1,1,1,0);
        cimg_snprintf(res._data + l,16," (#%u)",_width);


    static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
                          const T& a4, const T& a5, const T& a6, const T& a7,
                          const T& a8, const T& a9) {
      CImg<T> r(1,10);
      r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5; r[6] = a6; r[7] = a7; r[8] = a8; r[9] = a9;
      return r;
    }


    CImg<Tuchar> get_XYZtoRGB(const bool use_D65=true) const {
      return CImg<Tuchar>(*this,false).XYZtoRGB(use_D65);
    }


    CImg<T>& histogram(const unsigned int nb_levels) {
      return get_histogram(nb_levels).move_to(*this);
    }


    operator const T*() const {
      return _data;
    }


    CImg<T>& draw_object3d(LibBoard::Board& board,
                           const float x0, const float y0, const float z0,
                           const CImg<tp>& vertices, const CImgList<tf>& primitives,
                           const CImgList<tc>& colors, const CImgList<to>& opacities,
                           const unsigned int render_type=4,
                           const bool is_double_sided=false, const float focale=700,
                           const float lightx=0, const float lighty=0, const float lightz=-5e8,
                           const float specular_lightness=0.2f, const float specular_shininess=0.1f,
                           const float g_opacity=1) {
      return draw_object3d(board,x0,y0,z0,vertices,primitives,colors,opacities,render_type,
                           is_double_sided,focale,lightx,lighty,lightz,
                           specular_lightness,specular_shininess,g_opacity,CImg<floatT>::empty());
    }


    CImg<T>& RGBtoLab(const bool use_D65=true) {
      return RGBtoXYZ(use_D65).XYZtoLab(use_D65);
    }


    CImg<T>& load_analyze(const char *const filename, float *const voxel_size=0) {
      return _load_analyze(0,filename,voxel_size);
    }


      static unsigned int cut(const double val) {
        return val<(double)min()?min():val>(double)max()?max():(unsigned int)val; }


    CImg<T>& load_cimg(const char *const filename, const char axis='z', const float align=0) {
      CImgList<T> list;
      list.load_cimg(filename);
      if (list._width==1) return list[0].move_to(*this);
      return assign(list.get_append(axis,align));
    }


    CImg<T>& draw_triangle(int x0, int y0,
                           int x1, int y1,
                           int x2, int y2,
                           const tc *const color,
                           const CImg<tl>& light,
                           int lx0, int ly0,
                           int lx1, int ly1,
                           int lx2, int ly2,
                           const float opacity=1) {
      if (is_empty()) return *this;
      if (!color)
        throw CImgArgumentException(_cimg_instance
                                    "draw_triangle(): Specified color is (null).",
                                    cimg_instance);
      if (light._depth>1 || light._spectrum<_spectrum)
        throw CImgArgumentException(_cimg_instance
                                    "draw_triangle(): Invalid specified light texture (%u,%u,%u,%u,%p).",
                                    cimg_instance,light._width,light._height,light._depth,light._spectrum,light._data);

      if (y0>y1) cimg::swap(x0,x1,y0,y1,lx0,lx1,ly0,ly1);
      if (y0>y2) cimg::swap(x0,x2,y0,y2,lx0,lx2,ly0,ly2);
      if (y1>y2) cimg::swap(x1,x2,y1,y2,lx1,lx2,ly1,ly2);
      if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this;

      const int
        w1 = width() - 1, h1 = height() - 1,
        dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1,
        dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1),
        cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1),
        hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2,
        dlx01 = lx1 - lx0, dlx02 = lx2 - lx0, dlx12 = lx2 - lx1,
        dly01 = ly1 - ly0, dly02 = ly2 - ly0, dly12 = ly2 - ly1,
        hdy01lx = dy01*cimg::sign(dlx01)/2, hdy02lx = dy02*cimg::sign(dlx02)/2, hdy12lx = dy12*cimg::sign(dlx12)/2,
        hdy01ly = dy01*cimg::sign(dly01)/2, hdy02ly = dy02*cimg::sign(dly02)/2, hdy12ly = dy12*cimg::sign(dly12)/2;

      const ulongT lwhd = (ulongT)light._width*light._height*light._depth;
      cimg_init_scanline(opacity);

      for (int y = cy0; y<=cy2; ++y) {
        const int yy0 = y - y0, yy1 = y - y1;
        int
          xm = y<y1?x0 + (dx01*yy0 + hdy01)/dy01:x1 + (dx12*yy1 + hdy12)/dy12,
          xM = x0 + (dx02*yy0 + hdy02)/dy02,
          lxm = y<y1?lx0 + (dlx01*yy0 + hdy01lx)/dy01:lx1 + (dlx12*yy1 + hdy12lx)/dy12,
          lxM = lx0 + (dlx02*yy0 + hdy02lx)/dy02,
          lym = y<y1?ly0 + (dly01*yy0 + hdy01ly)/dy01:ly1 + (dly12*yy1 + hdy12ly)/dy12,
          lyM = ly0 + (dly02*yy0 + hdy02ly)/dy02;
        if (xm>xM) cimg::swap(xm,xM,lxm,lxM,lym,lyM);
        if (xM>=0 && xm<=w1) {
          const int
            cxm = cimg::cut(xm,0,w1),
            cxM = cimg::cut(xM,0,w1);
          T *ptrd = data(cxm,y);
          const int
            dxmM = std::max(1,xM - xm), hdxmM = dxmM/2,
            dlxmM = lxM - lxm, dlymM = lyM - lym;

          for (int x = cxm; x<=cxM; ++x) {
            const int
              xxm = x - xm,
              lx = (lxm*dxmM + dlxmM*xxm + hdxmM)/dxmM,
              ly = (lym*dxmM + dlymM*xxm + hdxmM)/dxmM;
            const tl *const lig = &light._atXY(lx,ly);
            cimg_forC(*this,c) {
              const tc col = color[c];
              const float cbs = cimg::cut((float)lig[c*lwhd],0,2);
              const Tfloat val = cbs<=1?cbs*col:(2 - cbs)*col + (cbs - 1)*_sc_maxval;
              ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity);
            }
            ++ptrd;
          }
        }
      }
      return *this;
    }


      static double mp_atan(_cimg_math_parser& mp) {
        return std::atan(_mp_arg(2));
      }


    static void _render_resize(const T *ptrs, const unsigned int ws, const unsigned int hs,
                               t *ptrd, const unsigned int wd, const unsigned int hd) {
      typedef typename cimg::last<T,cimg_ulong>::type ulongT;
      const ulongT one = (ulongT)1;
      CImg<ulongT> off_x(wd), off_y(hd + 1);
      if (wd==ws) off_x.fill(1);
      else {
        ulongT *poff_x = off_x._data, curr = 0;
        for (unsigned int x = 0; x<wd; ++x) {
          const ulongT old = curr;
          curr = (x + one)*ws/wd;
          *(poff_x++) = curr - old;
        }
      }
      if (hd==hs) off_y.fill(ws);
      else {
        ulongT *poff_y = off_y._data, curr = 0;
        for (unsigned int y = 0; y<hd; ++y) {
          const ulongT old = curr;
          curr = (y + one)*hs/hd;
          *(poff_y++) = ws*(curr - old);
        }
        *poff_y = 0;
      }
      ulongT *poff_y = off_y._data;
      for (unsigned int y = 0; y<hd; ) {
        const T *ptr = ptrs;
        ulongT *poff_x = off_x._data;
        for (unsigned int x = 0; x<wd; ++x) { *(ptrd++) = *ptr; ptr+=*(poff_x++); }
        ++y;
        ulongT dy = *(poff_y++);
        for ( ; !dy && y<hd; std::memcpy(ptrd,ptrd - wd,sizeof(t)*wd), ++y, ptrd+=wd, dy = *(poff_y++)) {}
        ptrs+=dy;
      }
    }


    static CImg<T> row_vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) {
      CImg<T> r(6,1);
      r[0] = a0; r[1] = a1; r[2] = a2; r[3] = a3; r[4] = a4; r[5] = a5;
      return r;
    }


    //! Insert n copies of the list \c list at position \c pos of the current list \newinstance.
    template<typename t>
    CImgList<T> get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U,


    const CImg<T> get_shared_slice(const unsigned int z0, const unsigned int c0=0) const {
      return get_shared_slices(z0,z0,c0);
    }


    CImg<T>& load_png(const char *const filename, unsigned int *const bits_per_value=0) {
      return _load_png(0,filename,bits_per_value);
    }


    CImg<T>& draw_triangle(int x0, int y0, const float z0,
                           int x1, int y1, const float z1,
                           int x2, int y2, const float z2,
                           const CImg<tc>& texture,
                           int tx0, int ty0,
                           int tx1, int ty1,
                           int tx2, int ty2,
                           const float opacity=1,
                           const float brightness=1) {
      if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
      if (texture._depth>1 || texture._spectrum<_spectrum)
        throw CImgArgumentException(_cimg_instance
                                    "draw_triangle(): Invalid specified texture (%u,%u,%u,%u,%p).",
                                    cimg_instance,
                                    texture._width,texture._height,texture._depth,texture._spectrum,texture._data);
      if (is_overlapped(texture))
        return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);

      float iz0 = 1/z0, iz1 = 1/z1, iz2 = 1/z2;
      if (y0>y1) cimg::swap(x0,x1,y0,y1,iz0,iz1,tx0,tx1,ty0,ty1);
      if (y0>y2) cimg::swap(x0,x2,y0,y2,iz0,iz2,tx0,tx2,ty0,ty2);
      if (y1>y2) cimg::swap(x1,x2,y1,y2,iz1,iz2,tx1,tx2,ty1,ty2);
      if (y2<0 || y0>=height() || cimg::min(x0,x1,x2)>=width() || cimg::max(x0,x1,x2)<0 || !opacity) return *this;

      const int
        w1 = width() - 1, h1 = height() - 1,
        dx01 = x1 - x0, dx02 = x2 - x0, dx12 = x2 - x1,
        dy01 = std::max(1,y1 - y0), dy02 = std::max(1,y2 - y0), dy12 = std::max(1,y2 - y1),
        cy0 = cimg::cut(y0,0,h1), cy2 = cimg::cut(y2,0,h1),
        hdy01 = dy01*cimg::sign(dx01)/2, hdy02 = dy02*cimg::sign(dx02)/2, hdy12 = dy12*cimg::sign(dx12)/2;
      const float
        diz01 = iz1 - iz0, diz02 = iz2 - iz0, diz12 = iz2 - iz1,
        txz0 = tx0*iz0, txz1 = tx1*iz1, txz2 = tx2*iz2,
        tyz0 = ty0*iz0, tyz1 = ty1*iz1, tyz2 = ty2*iz2,
        dtxz01 = txz1 - txz0, dtxz02 = txz2 - txz0, dtxz12 = txz2 - txz1,
        dtyz01 = tyz1 - tyz0, dtyz02 = tyz2 - tyz0, dtyz12 = tyz2 - tyz1;

      const ulongT twhd = (ulongT)texture._width*texture._height*texture._depth;
      const float cbs = cimg::cut(brightness,0,2);
      cimg_init_scanline(opacity);

      for (int y = cy0; y<=cy2; ++y) {
        const int yy0 = y - y0, yy1 = y - y1;
        int
          xm = y<y1?x0 + (dx01*yy0 + hdy01)/dy01:x1 + (dx12*yy1 + hdy12)/dy12,
          xM = x0 + (dx02*yy0 + hdy02)/dy02;
        float
          izm = y<y1?(iz0 + diz01*yy0/dy01):(iz1 + diz12*yy1/dy12),
          izM = iz0 + diz02*yy0/dy02,
          txzm = y<y1?(txz0 + dtxz01*yy0/dy01):(txz1 + dtxz12*yy1/dy12),
          txzM = txz0 + dtxz02*yy0/dy02,
          tyzm = y<y1?(tyz0 + dtyz01*yy0/dy01):(tyz1 + dtyz12*yy1/dy12),
          tyzM = tyz0 + dtyz02*yy0/dy02;
        if (xm>xM) cimg::swap(xm,xM,txzm,txzM,tyzm,tyzM,izm,izM);
        if (xM>=0 && xm<=w1) {
          const int
            cxm = cimg::cut(xm,0,w1),
            cxM = cimg::cut(xM,0,w1);
          T *ptrd = data(cxm,y);
          const int dxmM = std::max(1,xM - xm);
          const float dizmM = izM - izm, dtxzmM = txzM - txzm, dtyzmM = tyzM - tyzm;

          for (int x = cxm; x<=cxM; ++x) {
            const int xxm = x - xm;
            const float
              iz = izm + dizmM*xxm/dxmM,
              txz = txzm + dtxzmM*xxm/dxmM,
              tyz = tyzm + dtyzmM*xxm/dxmM;
            const int
              tx = (int)cimg::round(txz/iz),
              ty = (int)cimg::round(tyz/iz);
            const tc *const color = &texture._atXY(tx,ty);
            cimg_forC(*this,c) {
              const Tfloat val = cbs<=1?color[c*twhd]*cbs:(2 - cbs)*color[c*twhd] + (cbs - 1)*_sc_maxval;
              ptrd[c*_sc_whd] = (T)(opacity>=1?val:val*_sc_nopacity + ptrd[c*_sc_whd]*_sc_copacity);
            }
            ++ptrd;
          }
        }
      }
      return *this;
    }


      static double mp_list_set_Ioff_v(_cimg_math_parser& mp) {
        if (!mp.imglist.width()) return cimg::type<double>::nan();
        const unsigned int ind = (unsigned int)cimg::mod((int)_mp_arg(2),mp.imglist.width());
        CImg<T> &img = mp.imglist[ind];
        const longT
          off = (longT)_mp_arg(3),
          whd = (longT)img.width()*img.height()*img.depth();
        const double *ptrs = &_mp_arg(1) + 1;
        if (off>=0 && off<whd) {
          const unsigned int vsiz = (unsigned int)mp.opcode[4];
          T *ptrd = &img[off];
          cimg_for_inC(img,0,vsiz - 1,c) { *ptrd = (T)*(ptrs++); ptrd+=whd; }
        }
        return cimg::type<double>::nan();
      }


    CImgDisplay& assign(const CImgList<T>& list, const char *const title=0,
                        const unsigned int normalization_type=3,
                        const bool fullscreen_flag=false, const bool closed_flag=false) {
      if (!list) return assign();
      CImg<T> tmp;
      const CImg<T> img = list>'x', &nimg = (img._depth==1)?img:(tmp=img.get_projections2d((img._width - 1)/2,
                                                                                (img._height - 1)/2,
                                                                                (img._depth - 1)/2));
      _assign(nimg._width,nimg._height,title,normalization_type,fullscreen_flag,closed_flag);
      if (_normalization==2) _min = (float)nimg.min_max(_max);
      return render(nimg).paint();
    }


      static double mp_complex_pow_vv(_cimg_math_parser& mp) {
        const double *ptr1 = &_mp_arg(2) + 1, *ptr2 = &_mp_arg(3) + 1;
        double *ptrd = &_mp_arg(1) + 1;
        _mp_complex_pow(ptr1[0],ptr1[1],ptr2[0],ptr2[1],ptrd);
        return cimg::type<double>::nan();
      }


    CImg<T> get_rand(const T& val_min, const T& val_max) const {
      return (+*this).rand(val_min,val_max);
    }


    /**
      \param filename Filename to write data to.
    **/
    const CImgList<T>& save_gzip_external(const char *const filename) const {
      if (!filename)
        throw CImgIOException(_cimglist_instance
                              "save_gzip_external(): Specified filename is (null).",
                              cimglist_instance);
      CImg<charT> command(1024), filename_tmp(256), body(256);
      const char
        *ext = cimg::split_filename(filename,body),
        *ext2 = cimg::split_filename(body,0);
      std::FILE *file;
      do {
        if (!cimg::strcasecmp(ext,"gz")) {
          if (*ext2) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",
                                   cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext2);
          else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg",
                             cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
        } else {
          if (*ext) cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.%s",
                                  cimg::temporary_path(),cimg_file_separator,cimg::filenamerand(),ext);
          else cimg_snprintf(filename_tmp,filename_tmp._width,"%s%c%s.cimg",
                             cimg::temporary_path(),cimg_file_separator,cimg::filenamerand());
        }
        if ((file=cimg::std_fopen(filename_tmp,"rb"))!=0) cimg::fclose(file);
      } while (file);

      if (is_saveable(body)) {
        save(filename_tmp);
        cimg_snprintf(command,command._width,"\"%s\" -c \"%s\" > \"%s\"",
                      cimg::gzip_path(),
                      CImg<charT>::string(filename_tmp)._system_strescape().data(),
                      CImg<charT>::string(filename)._system_strescape().data());
        cimg::system(command, cimg::gzip_path());
        file = cimg::std_fopen(filename,"rb");
        if (!file)
          throw CImgIOException(_cimglist_instance
                                "save_gzip_external(): Failed to save file '%s' with external command 'gzip'.",
                                cimglist_instance,
                                filename);
        else cimg::fclose(file);
        std::remove(filename_tmp);
      } else {
        CImg<charT> nfilename(1024);
        cimglist_for(*this,l) {
          cimg::number_filename(body,l,6,nfilename);
          if (*ext) cimg_sprintf(nfilename._data + std::strlen(nfilename),".%s",ext);
          _data[l].save_gzip_external(nfilename);
        }


    Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int c=0) const {
      const float
        nfx = cimg::type<float>::is_nan(fx)?0:cimg::cut(fx,0,width() - 1),
        nfy = cimg::type<float>::is_nan(fy)?0:cimg::cut(fy,0,height() - 1);
      const int x = (int)nfx, y = (int)nfy;
      const float dx = nfx - x, dy = nfy - y;
      const int
        px = x - 1<0?0:x - 1, nx = dx<=0?x:x + 1, ax = x + 2>=width()?width() - 1:x + 2,
        py = y - 1<0?0:y - 1, ny = dy<=0?y:y + 1, ay = y + 2>=height()?height() - 1:y + 2;
      const Tfloat
        Ipp = (Tfloat)(*this)(px,py,z,c), Icp = (Tfloat)(*this)(x,py,z,c), Inp = (Tfloat)(*this)(nx,py,z,c),
        Iap = (Tfloat)(*this)(ax,py,z,c),
        Ip = Icp + 0.5f*(dx*(-Ipp + Inp) + dx*dx*(2*Ipp - 5*Icp + 4*Inp - Iap) + dx*dx*dx*(-Ipp + 3*Icp - 3*Inp + Iap)),
        Ipc = (Tfloat)(*this)(px,y,z,c),  Icc = (Tfloat)(*this)(x, y,z,c), Inc = (Tfloat)(*this)(nx,y,z,c),
        Iac = (Tfloat)(*this)(ax,y,z,c),
        Ic = Icc + 0.5f*(dx*(-Ipc + Inc) + dx*dx*(2*Ipc - 5*Icc + 4*Inc - Iac) + dx*dx*dx*(-Ipc + 3*Icc - 3*Inc + Iac)),
        Ipn = (Tfloat)(*this)(px,ny,z,c), Icn = (Tfloat)(*this)(x,ny,z,c), Inn = (Tfloat)(*this)(nx,ny,z,c),
        Ian = (Tfloat)(*this)(ax,ny,z,c),
        In = Icn + 0.5f*(dx*(-Ipn + Inn) + dx*dx*(2*Ipn - 5*Icn + 4*Inn - Ian) + dx*dx*dx*(-Ipn + 3*Icn - 3*Inn + Ian)),
        Ipa = (Tfloat)(*this)(px,ay,z,c), Ica = (Tfloat)(*this)(x,ay,z,c), Ina = (Tfloat)(*this)(nx,ay,z,c),
        Iaa = (Tfloat)(*this)(ax,ay,z,c),
        Ia = Ica + 0.5f*(dx*(-Ipa + Ina) + dx*dx*(2*Ipa - 5*Ica + 4*Ina - Iaa) + dx*dx*dx*(-Ipa + 3*Ica - 3*Ina + Iaa));
      return Ic + 0.5f*(dy*(-Ip + In) + dy*dy*(2*Ip - 5*Ic + 4*In - Ia) + dy*dy*dy*(-Ip + 3*Ic - 3*In + Ia));
    }


    CImg<Tfloat> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const {
      CImg<uintT> foo;
      return get_dijkstra(starting_node,ending_node,foo);
    }


    CImg<Tfloat> get_resize_object3d() const {
      return CImg<Tfloat>(*this,false).resize_object3d();
    }


    CImg<Tfloat> get_haar(const bool invert=false, const unsigned int nb_scales=1) const {
      CImg<Tfloat> res;
      if (nb_scales==1) { // Single scale transform
        if (_width>1) get_haar('x',invert,1).move_to(res);
        if (_height>1) { if (res) res.haar('y',invert,1); else get_haar('y',invert,1).move_to(res); }
        if (_depth>1) { if (res) res.haar('z',invert,1); else get_haar('z',invert,1).move_to(res); }
        if (res) return res;
      } else { // Multi-scale transform
        if (invert) { // Inverse transform
          res.assign(*this,false);
          if (_width>1) {
            if (_height>1) {
              if (_depth>1) {
                unsigned int w = _width, h = _height, d = _depth;
                for (unsigned int s = 1; w && h && d && s<nb_scales; ++s) { w/=2; h/=2; d/=2; }
                for (w = w?w:1, h = h?h:1, d = d?d:1; w<=_width && h<=_height && d<=_depth; w*=2, h*=2, d*=2)
                  res.draw_image(res.get_crop(0,0,0,w - 1,h - 1,d - 1).get_haar(true,1));
              } else {
                unsigned int w = _width, h = _height;
                for (unsigned int s = 1; w && h && s<nb_scales; ++s) { w/=2; h/=2; }
                for (w = w?w:1, h = h?h:1; w<=_width && h<=_height; w*=2, h*=2)
                  res.draw_image(res.get_crop(0,0,0,w - 1,h - 1,0).get_haar(true,1));
              }
            } else {
              if (_depth>1) {
                unsigned int w = _width, d = _depth;
                for (unsigned int s = 1; w && d && s<nb_scales; ++s) { w/=2; d/=2; }
                for (w = w?w:1, d = d?d:1; w<=_width && d<=_depth; w*=2, d*=2)
                  res.draw_image(res.get_crop(0,0,0,w - 1,0,d - 1).get_haar(true,1));
              } else {
                unsigned int w = _width;
                for (unsigned int s = 1; w && s<nb_scales; ++s) w/=2;
                for (w = w?w:1; w<=_width; w*=2)
                  res.draw_image(res.get_crop(0,0,0,w - 1,0,0).get_haar(true,1));
              }
            }
          } else {
            if (_height>1) {
              if (_depth>1) {
                unsigned int h = _height, d = _depth;
                for (unsigned int s = 1; h && d && s<nb_scales; ++s) { h/=2; d/=2; }
                for (h = h?h:1, d = d?d:1; h<=_height && d<=_depth; h*=2, d*=2)
                  res.draw_image(res.get_crop(0,0,0,0,h - 1,d - 1).get_haar(true,1));
              } else {
                unsigned int h = _height;
                for (unsigned int s = 1; h && s<nb_scales; ++s) h/=2;
                for (h = h?h:1; h<=_height; h*=2)
                  res.draw_image(res.get_crop(0,0,0,0,h - 1,0).get_haar(true,1));
              }
            } else {
              if (_depth>1) {
                unsigned int d = _depth;
                for (unsigned int s = 1; d && s<nb_scales; ++s) d/=2;
                for (d = d?d:1; d<=_depth; d*=2)
                  res.draw_image(res.get_crop(0,0,0,0,0,d - 1).get_haar(true,1));
              } else return *this;
            }
          }
        } else { // Direct transform
          res = get_haar(false,1);
          if (_width>1) {
            if (_height>1) {
              if (_depth>1)
                for (unsigned int s = 1, w = _width/2, h = _height/2, d = _depth/2; w && h && d && s<nb_scales;
                     ++s, w/=2, h/=2, d/=2)
                  res.draw_image(res.get_crop(0,0,0,w - 1,h - 1,d - 1).haar(false,1));
              else for (unsigned int s = 1, w = _width/2, h = _height/2; w && h && s<nb_scales; ++s, w/=2, h/=2)
                     res.draw_image(res.get_crop(0,0,0,w - 1,h - 1,0).haar(false,1));
            } else {
              if (_depth>1) for (unsigned int s = 1, w = _width/2, d = _depth/2; w && d && s<nb_scales; ++s, w/=2, d/=2)
                              res.draw_image(res.get_crop(0,0,0,w - 1,0,d - 1).haar(false,1));
              else for (unsigned int s = 1, w = _width/2; w && s<nb_scales; ++s, w/=2)
                     res.draw_image(res.get_crop(0,0,0,w - 1,0,0).haar(false,1));
            }
          } else {
            if (_height>1) {
              if (_depth>1)
                for (unsigned int s = 1, h = _height/2, d = _depth/2; h && d && s<nb_scales; ++s, h/=2, d/=2)
                  res.draw_image(res.get_crop(0,0,0,0,h - 1,d - 1).haar(false,1));
              else for (unsigned int s = 1, h = _height/2; h && s<nb_scales; ++s, h/=2)
                     res.draw_image(res.get_crop(0,0,0,0,h - 1,0).haar(false,1));
            } else {
              if (_depth>1) for (unsigned int s = 1, d = _depth/2; d && s<nb_scales; ++s, d/=2)
                              res.draw_image(res.get_crop(0,0,0,0,0,d - 1).haar(false,1));
              else return *this;
            }
          }
        }
        return res;
      }
      return *this;
    }


    inline double rand(const double val_max, cimg_uint64 *const p_rng) {
      const double val = cimg::_rand(p_rng)/(double)~0U;
      return val_max*val;
    }


      static double mp_min(_cimg_math_parser& mp) {
        const unsigned int i_end = (unsigned int)mp.opcode[2];
        double val = _mp_arg(3);
        for (unsigned int i = 4; i<i_end; ++i) val = std::min(val,_mp_arg(i));
        return val;
      }


    inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) {
      dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
    }


      static double mp_sin(_cimg_math_parser& mp) {
        return std::sin(_mp_arg(2));
      }


    static CImgDisplay& empty() {
      static CImgDisplay _empty;
      return _empty.assign();
    }


    unsigned int button() const {
      return _button;
    }


      \param pos1 Starting index of the removal.
      \param pos2 Ending index of the removal.
    **/
    CImgList<T>& remove(const unsigned int pos1, const unsigned int pos2) {
      const unsigned int
        npos1 = pos1<pos2?pos1:pos2,
        tpos2 = pos1<pos2?pos2:pos1,
        npos2 = tpos2<_width?tpos2:_width - 1;
      if (npos1>=_width)
        throw CImgArgumentException(_cimglist_instance
                                    "remove(): Invalid remove request at positions %u->%u.",
                                    cimglist_instance,
                                    npos1,tpos2);
      else {
        if (tpos2>=_width)
          throw CImgArgumentException(_cimglist_instance
                                      "remove(): Invalid remove request at positions %u->%u.",
                                      cimglist_instance,
                                      npos1,tpos2);

        for (unsigned int k = npos1; k<=npos2; ++k) _data[k].assign();
        const unsigned int nb = 1 + npos2 - npos1;
        if (!(_width-=nb)) return assign();
        if (_width>(_allocated_width>>4) || _allocated_width<=16) { // Removing items without reallocation
          if (npos1!=_width)
            std::memmove((void*)(_data + npos1),(void*)(_data + npos2 + 1),sizeof(CImg<T>)*(_width - npos1));
          std::memset((void*)(_data + _width),0,sizeof(CImg<T>)*nb);
        } else { // Removing items with reallocation
          _allocated_width>>=4;
          while (_allocated_width>16 && _width<(_allocated_width>>1)) _allocated_width>>=1;
          CImg<T> *const new_data = new CImg<T>[_allocated_width];
          if (npos1) std::memcpy((void*)new_data,(void*)_data,sizeof(CImg<T>)*npos1);
          if (npos1!=_width)
            std::memcpy((void*)(new_data + npos1),(void*)(_data + npos2 + 1),sizeof(CImg<T>)*(_width - npos1));
          if (_width!=_allocated_width)
            std::memset((void*)(new_data + _width),0,sizeof(CImg<T>)*(_allocated_width - _width));
          std::memset((void*)_data,0,sizeof(CImg<T>)*(_width + nb));
          delete[] _data;
          _data = new_data;
        }


    CImgException(const CImgException& e):std::exception(e) {
      const size_t size = std::strlen(e._message);
      _message = new char[size + 1];
      std::strncpy(_message,e._message,size);
      _message[size] = 0;
    }


      static double mp_norm0(_cimg_math_parser& mp) {
        const unsigned int i_end = (unsigned int)mp.opcode[2];
        switch (i_end - 3) {
        case 1 : return _mp_arg(3)!=0;
        case 2 : return (_mp_arg(3)!=0) + (_mp_arg(4)!=0);
        }
        double res = 0;
        for (unsigned int i = 3; i<i_end; ++i)
          res+=_mp_arg(i)==0?0:1;
        return res;
      }


    CImg<T> get_rows(const int y0, const int y1) const {
      return get_crop(0,y0,0,0,width() - 1,y1,depth() - 1,spectrum() - 1);
    }


    CImg<T>& _quicksort(const long indm, const long indM, CImg<t>& permutations,
                        const bool is_increasing, const bool is_permutations) {
      if (indm<indM) {
        const long mid = (indm + indM)/2;
        if (is_increasing) {
          if ((*this)[indm]>(*this)[mid]) {
            cimg::swap((*this)[indm],(*this)[mid]);
            if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
          }
          if ((*this)[mid]>(*this)[indM]) {
            cimg::swap((*this)[indM],(*this)[mid]);
            if (is_permutations) cimg::swap(permutations[indM],permutations[mid]);
          }
          if ((*this)[indm]>(*this)[mid]) {
            cimg::swap((*this)[indm],(*this)[mid]);
            if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
          }
        } else {
          if ((*this)[indm]<(*this)[mid]) {
            cimg::swap((*this)[indm],(*this)[mid]);
            if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
          }
          if ((*this)[mid]<(*this)[indM]) {
            cimg::swap((*this)[indM],(*this)[mid]);
            if (is_permutations) cimg::swap(permutations[indM],permutations[mid]);
          }
          if ((*this)[indm]<(*this)[mid]) {
            cimg::swap((*this)[indm],(*this)[mid]);
            if (is_permutations) cimg::swap(permutations[indm],permutations[mid]);
          }
        }
        if (indM - indm>=3) {
          const T pivot = (*this)[mid];
          long i = indm, j = indM;
          if (is_increasing) {
            do {
              while ((*this)[i]<pivot) ++i;
              while ((*this)[j]>pivot) --j;
              if (i<=j) {
                if (is_permutations) cimg::swap(permutations[i],permutations[j]);
                cimg::swap((*this)[i++],(*this)[j--]);
              }
            } while (i<=j);
          } else {
            do {
              while ((*this)[i]>pivot) ++i;
              while ((*this)[j]<pivot) --j;
              if (i<=j) {
                if (is_permutations) cimg::swap(permutations[i],permutations[j]);
                cimg::swap((*this)[i++],(*this)[j--]);
              }
            } while (i<=j);
          }
          if (indm<j) _quicksort(indm,j,permutations,is_increasing,is_permutations);
          if (i<indM) _quicksort(i,indM,permutations,is_increasing,is_permutations);
        }
      }
      return *this;
    }


    CImg<intT> get_select_graph(CImgDisplay &disp,
                                const unsigned int plot_type=1, const unsigned int vertex_type=1,
                                const char *const labelx=0, const double xmin=0, const double xmax=0,
                                const char *const labely=0, const double ymin=0, const double ymax=0,
                                const bool exit_on_anykey=false) const {
      if (is_empty())
        throw CImgInstanceException(_cimg_instance
                                    "select_graph(): Empty instance.",
                                    cimg_instance);
      if (!disp) disp.assign(cimg_fitscreen(CImgDisplay::screen_width()/2,CImgDisplay::screen_height()/2,1),0,0).
                   set_title("CImg<%s>",pixel_type());
      const ulongT siz = (ulongT)_width*_height*_depth;
      const unsigned int old_normalization = disp.normalization();
      disp.show().set_button().set_wheel()._normalization = 0;

      double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax;
      if (nymin==nymax) { nymin = (Tfloat)min_max(nymax); const double dy = nymax - nymin; nymin-=dy/20; nymax+=dy/20; }
      if (nymin==nymax) { --nymin; ++nymax; }
      if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.; }

      static const unsigned char black[] = { 0, 0, 0 }, white[] = { 255, 255, 255 }, gray[] = { 220, 220, 220 };
      static const unsigned char gray2[] = { 110, 110, 110 }, ngray[] = { 35, 35, 35 };

      CImg<ucharT> colormap(3,_spectrum);
      if (_spectrum==1) { colormap[0] = colormap[1] = 120; colormap[2] = 200; }
      else {
        colormap(0,0) = 220; colormap(1,0) = 10; colormap(2,0) = 10;
        if (_spectrum>1) { colormap(0,1) = 10;  colormap(1,1) = 220; colormap(2,1) = 10;  }
        if (_spectrum>2) { colormap(0,2) = 10;  colormap(1,2) = 10;  colormap(2,2) = 220; }
        if (_spectrum>3) { colormap(0,3) = 220; colormap(1,3) = 220; colormap(2,3) = 10;  }
        if (_spectrum>4) { colormap(0,4) = 220; colormap(1,4) = 10;  colormap(2,4) = 220; }
        if (_spectrum>5) { colormap(0,5) = 10;  colormap(1,5) = 220; colormap(2,5) = 220; }
        if (_spectrum>6) {
          cimg_uint64 rng = 10;
          cimg_for_inY(colormap,6,colormap.height()-1,k) {
            colormap(0,k) = (unsigned char)(120 + cimg::rand(-100.f,100.f,&rng));
            colormap(1,k) = (unsigned char)(120 + cimg::rand(-100.f,100.f,&rng));
            colormap(2,k) = (unsigned char)(120 + cimg::rand(-100.f,100.f,&rng));
          }
        }
      }

      CImg<ucharT> visu0, visu, graph, text, axes;
      int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2;
      const unsigned int one = plot_type==3?0U:1U;
      unsigned int okey = 0, obutton = 0, font_size = 32;
      CImg<charT> message(1024);
      CImg_3x3(I,unsigned char);

      for (bool selected = false; !selected && !disp.is_closed() && !okey && !disp.wheel(); ) {
        const int mouse_x = disp.mouse_x(), mouse_y = disp.mouse_y();
        const unsigned int key = disp.key(), button = disp.button();

        // Generate graph representation.
        if (!visu0) {
          visu0.assign(disp.width(),disp.height(),1,3,220);
          const int gdimx = disp.width() - 32, gdimy = disp.height() - 32;
          if (gdimx>0 && gdimy>0) {
            graph.assign(gdimx,gdimy,1,3,255);
            if (siz<32) {
              if (siz>1) graph.draw_grid(gdimx/(float)(siz - one),gdimy/(float)(siz - one),0,0,
                                         false,true,black,0.2f,0x33333333,0x33333333);
            } else graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333);
            cimg_forC(*this,c)
              graph.draw_graph(get_shared_channel(c),&colormap(0,c),(plot_type!=3 || _spectrum==1)?1:0.6f,
                               plot_type,vertex_type,nymax,nymin);

            axes.assign(gdimx,gdimy,1,1,0);
            const float
              dx = (float)cimg::abs(nxmax - nxmin), dy = (float)cimg::abs(nymax - nymin),
              px = (float)std::pow(10.,(int)std::log10(dx?dx:1) - 2.),
              py = (float)std::pow(10.,(int)std::log10(dy?dy:1) - 2.);
            const CImg<Tdouble>
              seqx = dx<=0?CImg<Tdouble>::vector(nxmin):
                CImg<Tdouble>::sequence(1 + gdimx/60,nxmin,one?nxmax:nxmin + (nxmax - nxmin)*(siz + 1)/siz),
              seqy = CImg<Tdouble>::sequence(1 + gdimy/60,nymax,nymin);

            const bool allow_zero = (nxmin*nxmax>0) || (nymin*nymax>0);
            axes.draw_axes(seqx,seqy,white,1,~0U,~0U,13,allow_zero,px,py);
            if (nymin>0) axes.draw_axis(seqx,gdimy - 1,gray,1,~0U,13,allow_zero,px);
            if (nymax<0) axes.draw_axis(seqx,0,gray,1,~0U,13,allow_zero,px);
            if (nxmin>0) axes.draw_axis(0,seqy,gray,1,~0U,13,allow_zero,py);
            if (nxmax<0) axes.draw_axis(gdimx - 1,seqy,gray,1,~0U,13,allow_zero,py);

            cimg_for3x3(axes,x,y,0,0,I,unsigned char)
              if (Icc) {
                if (Icc==255) cimg_forC(graph,c) graph(x,y,c) = 0;
                else cimg_forC(graph,c) graph(x,y,c) = (unsigned char)(2*graph(x,y,c)/3);
              }
              else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp)
                cimg_forC(graph,c) graph(x,y,c) = (unsigned char)((graph(x,y,c) + 511)/3);

            visu0.draw_image(16,16,graph);
            visu0.draw_line(15,15,16 + gdimx,15,gray2).draw_line(16 + gdimx,15,16 + gdimx,16 + gdimy,gray2).
              draw_line(16 + gdimx,16 + gdimy,15,16 + gdimy,white).draw_line(15,16 + gdimy,15,15,white);
          } else graph.assign();
          text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1,13).resize(-100,-100,1,3);
          visu0.draw_image((visu0.width() - text.width())/2,visu0.height() - 14,~text);
          text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1,13).rotate(-90).resize(-100,-100,1,3);
          visu0.draw_image(1,(visu0.height() - text.height())/2,~text);
          visu.assign();
        }

        // Generate and display current view.
        if (!visu) {
          visu.assign(visu0);
          if (graph && x0>=0 && x1>=0) {
            const int
              nx0 = x0<=x1?x0:x1,
              nx1 = x0<=x1?x1:x0,
              ny0 = y0<=y1?y0:y1,
              ny1 = y0<=y1?y1:y0,
              sx0 = (int)(16 + nx0*(visu.width() - 32)/std::max((ulongT)1,siz - one)),
              sx1 = (int)(15 + (nx1 + 1)*(visu.width() - 32)/std::max((ulongT)1,siz - one)),
              sy0 = 16 + ny0,
              sy1 = 16 + ny1;
            if (y0>=0 && y1>=0)
              visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU);
            else visu.draw_rectangle(sx0,0,sx1,visu.height() - 17,gray,0.5f).
                   draw_line(sx0,16,sx0,visu.height() - 17,black,0.5f,0xCCCCCCCCU).
                   draw_line(sx1,16,sx1,visu.height() - 17,black,0.5f,0xCCCCCCCCU);
          }
          if (mouse_x>=16 && mouse_y>=16 && mouse_x<visu.width() - 16 && mouse_y<visu.height() - 16) {
            if (graph) visu.draw_line(mouse_x,16,mouse_x,visu.height() - 17,black,0.5f,0x55555555U);
            const unsigned int
              x = (unsigned int)cimg::round((mouse_x - 16.f)*(siz - one)/(disp.width() - 32),1,one?0:-1);
            const double cx = nxmin + x*(nxmax - nxmin)/std::max((ulongT)1,siz - 1);
            if (_spectrum>=7)
              cimg_snprintf(message,message._width,"Value[%u:%g] = ( %g %g %g ... %g %g %g )",x,cx,
                            (double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2),
                            (double)(*this)(x,0,0,_spectrum - 4),(double)(*this)(x,0,0,_spectrum - 3),
                            (double)(*this)(x,0,0,_spectrum - 1));
            else {
              cimg_snprintf(message,message._width,"Value[%u:%g] = ( ",x,cx);
              cimg_forC(*this,c) cimg_sprintf(message._data + std::strlen(message),"%g ",(double)(*this)(x,0,0,c));
              cimg_sprintf(message._data + std::strlen(message),")");
            }
            if (x0>=0 && x1>=0) {
              const unsigned int
                nx0 = (unsigned int)(x0<=x1?x0:x1),
                nx1 = (unsigned int)(x0<=x1?x1:x0),
                ny0 = (unsigned int)(y0<=y1?y0:y1),
                ny1 = (unsigned int)(y0<=y1?y1:y0);
              const double
                cx0 = nxmin + nx0*(nxmax - nxmin)/std::max((ulongT)1,siz - 1),
                cx1 = nxmin + (nx1 + one)*(nxmax - nxmin)/std::max((ulongT)1,siz - 1),
                cy0 = nymax - ny0*(nymax - nymin)/(visu._height - 32),
                cy1 = nymax - ny1*(nymax - nymin)/(visu._height - 32);
              if (y0>=0 && y1>=0)
                cimg_sprintf(message._data + std::strlen(message)," - Range ( %u:%g, %g ) - ( %u:%g, %g )",
                             x0,cx0,cy0,x1 + one,cx1,cy1);
              else
                cimg_sprintf(message._data + std::strlen(message)," - Range [ %u:%g - %u:%g ]",
                             x0,cx0,x1 + one,cx1);
            }
            text.assign().draw_text(0,0,message,white,ngray,1,13).resize(-100,-100,1,3);
            visu.draw_image((visu.width() - text.width())/2,1,~text);
          }
          visu.display(disp);
        }

        // Test keys.
        CImg<charT> filename(32);
        switch (okey = key) {
#if cimg_OS!=2
        case cimg::keyCTRLRIGHT : case cimg::keySHIFTRIGHT :
#endif
        case cimg::keyCTRLLEFT : case cimg::keySHIFTLEFT : okey = 0; break;
        case cimg::keyD : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
          disp.set_fullscreen(false).
            resize(CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,false),
                   CImgDisplay::_fitscreen(3*disp.width()/2,3*disp.height()/2,1,128,-100,true),false).
            _is_resized = true;
          disp.set_key(key,false); okey = 0;
        } break;
        case cimg::keyC : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
          disp.set_fullscreen(false).
            resize(cimg_fitscreen(2*disp.width()/3,2*disp.height()/3,1),false)._is_resized = true;
          disp.set_key(key,false); okey = 0;
        } break;
        case cimg::keyR : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
            disp.set_fullscreen(false).
              resize(cimg_fitscreen(CImgDisplay::screen_width()/2,
                                    CImgDisplay::screen_height()/2,1),false)._is_resized = true;
            disp.set_key(key,false); okey = 0;
          } break;
        case cimg::keyF : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
            disp.resize(disp.screen_width(),disp.screen_height(),false).toggle_fullscreen()._is_resized = true;
            disp.set_key(key,false); okey = 0;
          } break;
        case cimg::keyS : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
            static unsigned int snap_number = 0;
            if (visu || visu0) {
              CImg<ucharT> &screen = visu?visu:visu0;
              std::FILE *file;
              do {
                cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.bmp",snap_number++);
                if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file);
              } while (file);
              (+screen).__draw_text(" Saving snapshot... ",font_size,0).display(disp);
              screen.save(filename);
              (+screen).__draw_text(" Snapshot '%s' saved. ",font_size,0,filename._data).display(disp);
            }
            disp.set_key(key,false); okey = 0;
          } break;
        case cimg::keyO : if (disp.is_keyCTRLLEFT() || disp.is_keyCTRLRIGHT()) {
            static unsigned int snap_number = 0;
            if (visu || visu0) {
              CImg<ucharT> &screen = visu?visu:visu0;
              std::FILE *file;
              do {

#ifdef cimg_use_zlib
                cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimgz",snap_number++);
#else
                cimg_snprintf(filename,filename._width,cimg_appname "_%.4u.cimg",snap_number++);
#endif
                if ((file=cimg::std_fopen(filename,"r"))!=0) cimg::fclose(file);
              } while (file);
              (+screen).__draw_text(" Saving instance... ",font_size,0).display(disp);
              save(filename);
              (+screen).__draw_text(" Instance '%s' saved. ",font_size,0,filename._data).display(disp);
            }
            disp.set_key(key,false); okey = 0;
          } break;
        }

        // Handle mouse motion and mouse buttons.
        if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) {
          visu.assign();
          if (disp.mouse_x()>=0 && disp.mouse_y()>=0) {
            const int
              mx = (mouse_x - 16)*(int)(siz - one)/(disp.width() - 32),
              cx = cimg::cut(mx,0,(int)(siz - 1 - one)),
              my = mouse_y - 16,
              cy = cimg::cut(my,0,disp.height() - 32);
            if (button&1) {
              if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; }
            }
            else if (button&2) {
              if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; }
            }
            else if (obutton) { x1 = x1>=0?cx:-1; y1 = y1>=0?cy:-1; selected = true; }
          } else if (!button && obutton) selected = true;
          obutton = button; omouse_x = mouse_x; omouse_y = mouse_y;
        }
        if (disp.is_resized()) { disp.resize(false); visu0.assign(); }
        if (visu && visu0) disp.wait();
        if (!exit_on_anykey && okey && okey!=cimg::keyESC &&
            (okey!=cimg::keyW || (!disp.is_keyCTRLLEFT() && !disp.is_keyCTRLRIGHT()))) {
          disp.set_key(key,false);
          okey = 0;
        }
      }

      disp._normalization = old_normalization;
      if (x1>=0 && x1<x0) cimg::swap(x0,x1);
      if (y1<y0) cimg::swap(y0,y1);
      disp.set_key(okey);
      return CImg<intT>(4,1,1,1,x0,y0,x1>=0?x1 + (int)one:-1,y1);
    }


    CImg<T>& normalize(const T& min_value, const T& max_value,
                       const float constant_case_ratio=0) {
      if (is_empty()) return *this;
      const T a = min_value<max_value?min_value:max_value, b = min_value<max_value?max_value:min_value;
      T m, M = max_min(m);
      const Tfloat fm = (Tfloat)m, fM = (Tfloat)M;
      if (m==M)
        return fill(constant_case_ratio==0?a:
                    constant_case_ratio==1?b:
                    (T)((1 - constant_case_ratio)*a + constant_case_ratio*b));
      if (m!=a || M!=b) cimg_rof(*this,ptrd,T) *ptrd = (T)((*ptrd - fm)/(fM - fm)*(b - a) + a);
      return *this;
    }


    CImg(const unsigned int size_x, const unsigned int size_y,
         const unsigned int size_z, const unsigned int size_c, const T& value):
      _is_shared(false) {
      const size_t siz = safe_size(size_x,size_y,size_z,size_c);
      if (siz) {
        _width = size_x; _height = size_y; _depth = size_z; _spectrum = size_c;
        try { _data = new T[siz]; } catch (...) {
          _width = _height = _depth = _spectrum = 0; _data = 0;
          throw CImgInstanceException(_cimg_instance
                                      "CImg(): Failed to allocate memory (%s) for image (%u,%u,%u,%u).",
                                      cimg_instance,
                                      cimg::strbuffersize(sizeof(T)*size_x*size_y*size_z*size_c),
                                      size_x,size_y,size_z,size_c);
        }
        fill(value);
      } else { _width = _height = _depth = _spectrum = 0; _data = 0; }
    }


    CImg<T>& draw_image(const int x0,
                        const CImg<t>& sprite, const float opacity=1) {
      return draw_image(x0,0,sprite,opacity);
    }


    CImg<T>& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
      return _load_rgba(0,filename,dimw,dimh);
    }


    CImg<T>& _load_raw(std::FILE *const file, const char *const filename,
                       const unsigned int size_x, const unsigned int size_y,
                       const unsigned int size_z, const unsigned int size_c,
                       const bool is_multiplexed, const bool invert_endianness,
                       const ulongT offset) {
      if (!file && !filename)
        throw CImgArgumentException(_cimg_instance
                                    "load_raw(): Specified filename is (null).",
                                    cimg_instance);
      if (cimg::is_directory(filename))
        throw CImgArgumentException(_cimg_instance
                                    "load_raw(): Specified filename '%s' is a directory.",
                                    cimg_instance,filename);
      const bool is_bool = pixel_type()==cimg::type<bool>::string();
      ulongT siz = (ulongT)size_x*size_y*size_z*size_c;
      unsigned int
        _size_x = size_x,
        _size_y = size_y,
        _size_z = size_z,
        _size_c = size_c;
      std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
      if (!siz) {  // Retrieve file size
        const longT fpos = cimg::ftell(nfile);
        if (fpos<0) throw CImgArgumentException(_cimg_instance
                                                "load_raw(): Cannot determine size of input file '%s'.",
                                                cimg_instance,filename?filename:"(FILE*)");
        cimg::fseek(nfile,0,SEEK_END);
        siz = (ulongT)cimg::ftell(nfile);
        if (!is_bool) { siz/=sizeof(T); _size_y = (unsigned int)siz; }
        else _size_y = (unsigned int)(siz*8);
        _size_x = _size_z = _size_c = 1;
        cimg::fseek(nfile,fpos,SEEK_SET);
      }
      cimg::fseek(nfile,(longT)offset,SEEK_SET);
      assign(_size_x,_size_y,_size_z,_size_c,0);

      if (is_bool) { // Boolean data (bitwise)
        unsigned char *const buf = new unsigned char[siz];
        cimg::fread(buf,siz,nfile);
        _uchar2bool(buf,siz,is_multiplexed);
        delete[] buf;
      } else { // Non-boolean data
        if (siz && (!is_multiplexed || size_c==1)) { // Non-multiplexed
          cimg::fread(_data,siz,nfile);
          if (invert_endianness) cimg::invert_endianness(_data,siz);
        } else if (siz) { // Multiplexed
          CImg<T> buf(1,1,1,_size_c);
          cimg_forXYZ(*this,x,y,z) {
            cimg::fread(buf._data,_size_c,nfile);
            if (invert_endianness) cimg::invert_endianness(buf._data,_size_c);
            set_vector_at(buf,x,y,z);
          }
        }
      }
      if (!file) cimg::fclose(nfile);
      return *this;
    }


    CImg<T>& fillC(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {
      const ulongT whd = (ulongT)_width*_height*_depth;
      if (x<_width && y<_height && z<_depth) _cimg_fill1(x,y,z,0,whd,_spectrum,int);
      return *this;
    }


evdev_scroll_get_default_button_lock(struct libinput_device *device)
{
	return LIBINPUT_CONFIG_SCROLL_BUTTON_LOCK_DISABLED;
}


evdev_update_key_down_count(struct evdev_device *device,
			    int code,
			    int pressed)
{
	int key_count;
	assert(code >= 0 && code < KEY_CNT);

	if (pressed) {
		key_count = ++device->key_count[code];
	} else {
		assert(device->key_count[code] > 0);
		key_count = --device->key_count[code];
	}

	if (key_count > 32) {
		evdev_log_bug_libinput(device,
				       "key count for %s reached abnormal values\n",
				       libevdev_event_code_get_name(EV_KEY, code));
	}

	return key_count;
}


evdev_accel_config_get_default_speed(struct libinput_device *device)
{
	return 0.0;
}


evdev_check_min_max(struct evdev_device *device, unsigned int code)
{
	struct libevdev *evdev = device->evdev;
	const struct input_absinfo *absinfo;

	if (!libevdev_has_event_code(evdev, EV_ABS, code))
		return true;

	absinfo = libevdev_get_abs_info(evdev, code);
	if (absinfo->minimum == absinfo->maximum) {
		/* Some devices have a sort-of legitimate min/max of 0 for
		 * ABS_MISC and above (e.g. Roccat Kone XTD). Don't ignore
		 * them, simply disable the axes so we won't get events,
		 * we don't know what to do with them anyway.
		 */
		if (absinfo->minimum == 0 &&
		    code >= ABS_MISC && code < ABS_MT_SLOT) {
			evdev_log_info(device,
				       "disabling EV_ABS %#x on device (min == max == 0)\n",
				       code);
			libevdev_disable_event_code(device->evdev,
						    EV_ABS,
						    code);
		} else {
			evdev_log_bug_kernel(device,
					     "device has min == max on %s\n",
					     libevdev_event_code_get_name(EV_ABS, code));
			return false;
		}
	}

	return true;
}


evdev_need_velocity_averaging(struct evdev_device *device)
{
	struct quirks_context *quirks;
	struct quirks *q;
	bool use_velocity_averaging = false; /* default off unless we have quirk */

	quirks = evdev_libinput_context(device)->quirks;
	q = quirks_fetch_for_device(quirks, device->udev_device);
	if (q) {
		quirks_get_bool(q,
				QUIRK_ATTR_USE_VELOCITY_AVERAGING,
				&use_velocity_averaging);
		quirks_unref(q);
	}

	if (use_velocity_averaging)
		evdev_log_info(device,
			       "velocity averaging is turned on\n");

	return use_velocity_averaging;
}


evdev_read_wheel_click_props(struct evdev_device *device)
{
	struct wheel_angle angles;
	const char *wheel_count = "MOUSE_WHEEL_CLICK_COUNT";
	const char *wheel_angle = "MOUSE_WHEEL_CLICK_ANGLE";
	const char *hwheel_count = "MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL";
	const char *hwheel_angle = "MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL";

	/* CLICK_COUNT overrides CLICK_ANGLE */
	if (evdev_read_wheel_click_count_prop(device, wheel_count, &angles.y) ||
	    evdev_read_wheel_click_prop(device, wheel_angle, &angles.y)) {
		evdev_log_debug(device,
				"wheel: vert click angle: %.2f\n", angles.y);
	}
	if (evdev_read_wheel_click_count_prop(device, hwheel_count, &angles.x) ||
	    evdev_read_wheel_click_prop(device, hwheel_angle, &angles.x)) {
		evdev_log_debug(device,
				"wheel: horizontal click angle: %.2f\n", angles.y);
	} else {
		angles.x = angles.y;
	}

	return angles;
}


evdev_accel_config_get_speed(struct libinput_device *device)
{
	struct evdev_device *dev = evdev_device(device);

	return filter_get_speed(dev->pointer.filter);
}


evdev_drain_fd(int fd)
{
	struct input_event ev[24];
	size_t sz = sizeof ev;

	while (read(fd, &ev, sz) == (int)sz) {
		/* discard all pending events */
	}
}


evdev_device_get_touch_count(struct evdev_device *device)
{
	int ntouches;

	if (!(device->seat_caps & EVDEV_DEVICE_TOUCH))
		return -1;

	ntouches = libevdev_get_num_slots(device->evdev);
	if (ntouches == -1) {
		/* mtdev devices have multitouch but we don't know
		 * how many. Otherwise, any touch device with num_slots of
		 * -1 is a single-touch device */
		if (device->mtdev)
			ntouches = 0;
		else
			ntouches = 1;
	}

	return ntouches;
}


evdev_read_wheel_click_count_prop(struct evdev_device *device,
				  const char *prop,
				  double *angle)
{
	int val;

	prop = udev_device_get_property_value(device->udev_device, prop);
	if (!prop)
		return false;

	val = parse_mouse_wheel_click_angle_property(prop);
	if (val) {
		*angle = 360.0/val;
		return true;
	}

	evdev_log_error(device,
		  "mouse wheel click count is present but invalid, "
		  "using %d degrees for angle instead instead\n",
		  DEFAULT_WHEEL_CLICK_ANGLE);
	*angle = DEFAULT_WHEEL_CLICK_ANGLE;

	return false;
}


evdev_libinput_context(const struct evdev_device *device)
{
	return device->base.seat->libinput;
}


evdev_notify_added_device(struct evdev_device *device)
{
	struct libinput_device *dev;

	list_for_each(dev, &device->base.seat->devices_list, link) {
		struct evdev_device *d = evdev_device(dev);
		if (dev == &device->base)
			continue;

		/* Notify existing device d about addition of device */
		if (d->dispatch->interface->device_added)
			d->dispatch->interface->device_added(d, device);

		/* Notify new device about existing device d */
		if (device->dispatch->interface->device_added)
			device->dispatch->interface->device_added(device, d);

		/* Notify new device if existing device d is suspended */
		if (d->is_suspended &&
		    device->dispatch->interface->device_suspended)
			device->dispatch->interface->device_suspended(device, d);
	}

	notify_added_device(&device->base);

	if (device->dispatch->interface->post_added)
		device->dispatch->interface->post_added(device,
							device->dispatch);
}


evdev_device_init_abs_range_warnings(struct evdev_device *device)
{
	const struct input_absinfo *x, *y;
	int width, height;

	x = device->abs.absinfo_x;
	y = device->abs.absinfo_y;
	width = device->abs.dimensions.x;
	height = device->abs.dimensions.y;

	device->abs.warning_range.min.x = x->minimum - 0.05 * width;
	device->abs.warning_range.min.y = y->minimum - 0.05 * height;
	device->abs.warning_range.max.x = x->maximum + 0.05 * width;
	device->abs.warning_range.max.y = y->maximum + 0.05 * height;

	/* One warning every 5 min is enough */
	ratelimit_init(&device->abs.warning_range.range_warn_limit,
		       s2us(3000),
		       1);
}


evdev_process_event(struct evdev_device *device, struct input_event *e)
{
	struct evdev_dispatch *dispatch = device->dispatch;
	uint64_t time = input_event_time(e);

#if 0
	evdev_print_event(device, e);
#endif

	libinput_timer_flush(evdev_libinput_context(device), time);

	dispatch->interface->process(dispatch, device, e, time);
}


safe_atod(const char *str, double *val)
{
	char *endptr;
	double v;
#ifdef HAVE_LOCALE_H
	locale_t c_locale;
#endif
	size_t slen = strlen(str);

	/* We don't have a use-case where we want to accept hex for a double
	 * or any of the other values strtod can parse */
	for (size_t i = 0; i < slen; i++) {
		char c = str[i];

		if (isdigit(c))
		       continue;
		switch(c) {
		case '+':
		case '-':
		case '.':
			break;
		default:
			return false;
		}
	}

#ifdef HAVE_LOCALE_H
	/* Create a "C" locale to force strtod to use '.' as separator */
	c_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0);
	if (c_locale == (locale_t)0)
		return false;

	errno = 0;
	v = strtod_l(str, &endptr, c_locale);
	freelocale(c_locale);
#else
	/* No locale support in provided libc, assume it already uses '.' */
	errno = 0;
	v = strtod(str, &endptr);
#endif
	if (errno > 0)
		return false;
	if (str == endptr)
		return false;
	if (*str != '\0' && *endptr != '\0')
		return false;
	if (v != 0.0 && !isnormal(v))
		return false;

	*val = v;
	return true;
}


evdev_disable_accelerometer_axes(struct evdev_device *device)
{
	struct libevdev *evdev = device->evdev;

	libevdev_disable_event_code(evdev, EV_ABS, ABS_X);
	libevdev_disable_event_code(evdev, EV_ABS, ABS_Y);
	libevdev_disable_event_code(evdev, EV_ABS, ABS_Z);

	libevdev_disable_event_code(evdev, EV_ABS, REL_X);
	libevdev_disable_event_code(evdev, EV_ABS, REL_Y);
	libevdev_disable_event_code(evdev, EV_ABS, REL_Z);
}


evdev_read_calibration_prop(struct evdev_device *device)
{
	const char *prop;
	float calibration[6];

	prop = udev_device_get_property_value(device->udev_device,
					      "LIBINPUT_CALIBRATION_MATRIX");

	if (prop == NULL)
		return;

	if (!device->abs.absinfo_x || !device->abs.absinfo_y)
		return;

	if (!parse_calibration_property(prop, calibration))
		return;

	evdev_device_set_default_calibration(device, calibration);
	evdev_log_info(device,
		       "applying calibration: %f %f %f %f %f %f\n",
		       calibration[0],
		       calibration[1],
		       calibration[2],
		       calibration[3],
		       calibration[4],
		       calibration[5]);
}


litest_utils_suite(void)
{
	TCase *tc;
	Suite *s;

	s = suite_create("litest:utils");
	tc = tcase_create("utils");

	tcase_add_test(tc, array_for_each);

	tcase_add_test(tc, bitfield_helpers);
	tcase_add_test(tc, matrix_helpers);
	tcase_add_test(tc, ratelimit_helpers);
	tcase_add_test(tc, dpi_parser);
	tcase_add_test(tc, wheel_click_parser);
	tcase_add_test(tc, wheel_click_count_parser);
	tcase_add_test(tc, dimension_prop_parser);
	tcase_add_test(tc, reliability_prop_parser);
	tcase_add_test(tc, calibration_prop_parser);
	tcase_add_test(tc, range_prop_parser);
	tcase_add_test(tc, boolean_prop_parser);
	tcase_add_test(tc, evcode_prop_parser);
	tcase_add_test(tc, input_prop_parser);
	tcase_add_test(tc, evdev_abs_parser);
	tcase_add_test(tc, safe_atoi_test);
	tcase_add_test(tc, safe_atoi_base_16_test);
	tcase_add_test(tc, safe_atoi_base_8_test);
	tcase_add_test(tc, safe_atou_test);
	tcase_add_test(tc, safe_atou_base_16_test);
	tcase_add_test(tc, safe_atou_base_8_test);
	tcase_add_test(tc, safe_atod_test);
	tcase_add_test(tc, strsplit_test);
	tcase_add_test(tc, strargv_test);
	tcase_add_test(tc, kvsplit_double_test);
	tcase_add_test(tc, strjoin_test);
	tcase_add_test(tc, strstrip_test);
	tcase_add_test(tc, strendswith_test);
	tcase_add_test(tc, strstartswith_test);
	tcase_add_test(tc, strsanitize_test);
	tcase_add_test(tc, time_conversion);
	tcase_add_test(tc, human_time);

	tcase_add_test(tc, list_test_insert);
	tcase_add_test(tc, list_test_append);
	tcase_add_test(tc, list_test_foreach);
	tcase_add_test(tc, strverscmp_test);
	tcase_add_test(tc, streq_test);
	tcase_add_test(tc, strneq_test);
	tcase_add_test(tc, trunkname_test);
	tcase_add_test(tc, basename_test);

	suite_add_tcase(s, tc);

	return s;
}


evdev_sync_device(struct evdev_device *device)
{
	struct input_event ev;
	int rc;

	do {
		rc = libevdev_next_event(device->evdev,
					 LIBEVDEV_READ_FLAG_SYNC, &ev);
		if (rc < 0)
			break;
		evdev_device_dispatch_one(device, &ev);
	} while (rc == LIBEVDEV_READ_STATUS_SYNC);

	return rc == -EAGAIN ? 0 : rc;
}


evdev_device_get_sysname(struct evdev_device *device)
{
	return device->sysname;
}


evdev_device_has_key(struct evdev_device *device, uint32_t code)
{
	if (!(device->seat_caps & EVDEV_DEVICE_KEYBOARD))
		return -1;

	return libevdev_has_event_code(device->evdev, EV_KEY, code);
}


evdev_accel_config_get_profile(struct libinput_device *libinput_device)
{
	struct evdev_device *device = evdev_device(libinput_device);

	return filter_get_type(device->pointer.filter);
}


evdev_pre_configure_model_quirks(struct evdev_device *device)
{
	struct quirks_context *quirks;
	struct quirks *q;
	const struct quirk_tuples *t;
	const uint32_t *props = NULL;
	size_t nprops = 0;
	char *prop;

	/* Touchpad claims to have 4 slots but only ever sends 2
	 * https://bugs.freedesktop.org/show_bug.cgi?id=98100 */
	if (evdev_device_has_model_quirk(device, QUIRK_MODEL_HP_ZBOOK_STUDIO_G3))
		libevdev_set_abs_maximum(device->evdev, ABS_MT_SLOT, 1);

	/* Generally we don't care about MSC_TIMESTAMP and it can cause
	 * unnecessary wakeups but on some devices we need to watch it for
	 * pointer jumps */
	quirks = evdev_libinput_context(device)->quirks;
	q = quirks_fetch_for_device(quirks, device->udev_device);
	if (!q ||
	    !quirks_get_string(q, QUIRK_ATTR_MSC_TIMESTAMP, &prop) ||
	    !streq(prop, "watch")) {
		libevdev_disable_event_code(device->evdev, EV_MSC, MSC_TIMESTAMP);
	}

	if (quirks_get_tuples(q, QUIRK_ATTR_EVENT_CODE_ENABLE, &t)) {
		for (size_t i = 0; i < t->ntuples; i++) {
			const struct input_absinfo absinfo = {
				.minimum = 0,
				.maximum = 1,
			};

			int type = t->tuples[i].first;
			int code = t->tuples[i].second;

			if (code == EVENT_CODE_UNDEFINED)
				libevdev_enable_event_type(device->evdev, type);
			else
				libevdev_enable_event_code(device->evdev,
							    type,
							    code,
							    type == EV_ABS ?  &absinfo : NULL);
			evdev_log_debug(device,
					"quirks: enabling %s %s (%#x %#x)\n",
					libevdev_event_type_get_name(type),
					libevdev_event_code_get_name(type, code),
					type,
					code);
		}
	}

	if (quirks_get_tuples(q, QUIRK_ATTR_EVENT_CODE_DISABLE, &t)) {
		for (size_t i = 0; i < t->ntuples; i++) {
			int type = t->tuples[i].first;
			int code = t->tuples[i].second;

			if (code == EVENT_CODE_UNDEFINED)
				libevdev_disable_event_type(device->evdev,
							    type);
			else
				libevdev_disable_event_code(device->evdev,
							    type,
							    code);
			evdev_log_debug(device,
					"quirks: disabling %s %s (%#x %#x)\n",
					libevdev_event_type_get_name(type),
					libevdev_event_code_get_name(type, code),
					type,
					code);
		}
	}

	if (quirks_get_uint32_array(q,
				    QUIRK_ATTR_INPUT_PROP_ENABLE,
				    &props,
				    &nprops)) {
		for (size_t idx = 0; idx < nprops; idx++) {
			unsigned int p = props[idx];
			libevdev_enable_property(device->evdev, p);
			evdev_log_debug(device,
					"quirks: enabling %s (%#x)\n",
					libevdev_property_get_name(p),
					p);
		}
	}

	if (quirks_get_uint32_array(q,
					 QUIRK_ATTR_INPUT_PROP_DISABLE,
					 &props,
					 &nprops)) {
#if HAVE_LIBEVDEV_DISABLE_PROPERTY
		for (size_t idx = 0; idx < nprops; idx++) {
			unsigned int p = props[idx];
			libevdev_disable_property(device->evdev, p);
			evdev_log_debug(device,
					"quirks: disabling %s (%#x)\n",
					libevdev_property_get_name(p),
					p);
		}
#else
		evdev_log_error(device,
				"quirks: a quirk for this device requires newer libevdev than installed\n");
#endif
	}

	quirks_unref(q);
}


evdev_log_msg(struct evdev_device *device,
	      enum libinput_log_priority priority,
	      const char *format,
	      ...)
{
	va_list args;
	char buf[1024];

	if (!is_logged(evdev_libinput_context(device), priority))
		return;

	/* Anything info and above is user-visible, use the device name */
	snprintf(buf,
		 sizeof(buf),
		 "%-7s - %s%s%s",
		 evdev_device_get_sysname(device),
		 (priority > LIBINPUT_LOG_PRIORITY_DEBUG) ?  device->log_prefix_name : "",
		 (priority > LIBINPUT_LOG_PRIORITY_DEBUG) ?  ": " : "",
		 format);

	va_start(args, format);
	log_msg_va(evdev_libinput_context(device), priority, buf, args);
	va_end(args);

}


evdev_button_scroll_button(struct evdev_device *device,
			   uint64_t time, int is_press)
{
	/* Where the button lock is enabled, we wrap the buttons into
	   their own little state machine and filter out the events.
	 */
	switch (device->scroll.lock_state) {
	case BUTTONSCROLL_LOCK_DISABLED:
		break;
	case BUTTONSCROLL_LOCK_IDLE:
		assert(is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTDOWN;
		evdev_log_debug(device, "scroll lock: first down\n");
		break; /* handle event */
	case BUTTONSCROLL_LOCK_FIRSTDOWN:
		assert(!is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_FIRSTUP;
		evdev_log_debug(device, "scroll lock: first up\n");
		return; /* filter release event */
	case BUTTONSCROLL_LOCK_FIRSTUP:
		assert(is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_SECONDDOWN;
		evdev_log_debug(device, "scroll lock: second down\n");
		return; /* filter press event */
	case BUTTONSCROLL_LOCK_SECONDDOWN:
		assert(!is_press);
		device->scroll.lock_state = BUTTONSCROLL_LOCK_IDLE;
		evdev_log_debug(device, "scroll lock: idle\n");
		break; /* handle event */
	}

	if (is_press) {
		enum timer_flags flags = TIMER_FLAG_NONE;

		device->scroll.button_scroll_state = BUTTONSCROLL_BUTTON_DOWN;

		/* Special case: if middle button emulation is enabled and
		 * our scroll button is the left or right button, we only
		 * get here *after* the middle button timeout has expired
		 * for that button press. The time passed is the button-down
		 * time though (which is in the past), so we have to allow
		 * for a negative timer to be set.
		 */
		if (device->middlebutton.enabled &&
		    (device->scroll.button == BTN_LEFT ||
		     device->scroll.button == BTN_RIGHT)) {
			flags = TIMER_FLAG_ALLOW_NEGATIVE;
		}

		libinput_timer_set_flags(&device->scroll.timer,
					 time + DEFAULT_BUTTON_SCROLL_TIMEOUT,
					 flags);
		device->scroll.button_down_time = time;
		evdev_log_debug(device, "btnscroll: down\n");
	} else {
		libinput_timer_cancel(&device->scroll.timer);
		switch(device->scroll.button_scroll_state) {
		case BUTTONSCROLL_IDLE:
			evdev_log_bug_libinput(device,
				       "invalid state IDLE for button up\n");
			break;
		case BUTTONSCROLL_BUTTON_DOWN:
		case BUTTONSCROLL_READY:
			evdev_log_debug(device, "btnscroll: cancel\n");

			/* If the button is released quickly enough or
			 * without scroll events, emit the
			 * button press/release events. */
			evdev_pointer_post_button(device,
					device->scroll.button_down_time,
					device->scroll.button,
					LIBINPUT_BUTTON_STATE_PRESSED);
			evdev_pointer_post_button(device, time,
					device->scroll.button,
					LIBINPUT_BUTTON_STATE_RELEASED);
			break;
		case BUTTONSCROLL_SCROLLING:
			evdev_log_debug(device, "btnscroll: up\n");
			evdev_stop_scroll(device, time,
					  LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS);
			break;
		}

		device->scroll.button_scroll_state = BUTTONSCROLL_IDLE;
	}
}


static inline int str2out_method(char *optarg) {
    if (optarg) {
        if (!strcasecmp(optarg,"plain")) return PLAIN_OUT;
#ifdef HAVE_CURSES
            else
        if (!strcasecmp(optarg,"curses")) return CURSES_OUT;
	    else
        if (!strcasecmp(optarg,"curses2")) return CURSES2_OUT;
#endif
#ifdef CSV
            else
        if (!strcasecmp(optarg,"csv")) return CSV_OUT;
#endif
#ifdef HTML
        else
        if (!strcasecmp(optarg,"html")) return HTML_OUT;
#endif
    }
    return -1;
}


static struct logState *findState(const char *fn)
{
    const int i = hashIndex(fn);
    struct logState *p;
    if (i < 0)
        /* hash table not yet allocated */
        return NULL;

    for (p = states[i]->head.lh_first; p != NULL; p = p->list.le_next)
        if (!strcmp(fn, p->fn))
            break;

    /* new state */
    if (p == NULL) {
        if ((p = newState(fn)) == NULL)
            return NULL;

        LIST_INSERT_HEAD(&(states[i]->head), p, list);
    }

    return p;
}


static int postrotateSingleLog(const struct logInfo *log, unsigned logNum,
                               struct logState *state,
                               struct logNames *rotNames)
{
    int hasErrors = 0;

    if (!state->doRotate) {
        return 0;
    }

    if (!hasErrors && (log->flags & LOG_FLAG_TMPFILENAME)) {
        char *tmpFilename;
        if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) {
            message_OOM();
            return 1;
        }
        hasErrors = copyTruncate(tmpFilename, rotNames->finalName,
                                 &state->sb, LOG_FLAG_COPY, /* skip_copy */ 0);
        message(MESS_DEBUG, "removing tmp log %s\n", tmpFilename);
        if (!debug && !hasErrors) {
            unlink(tmpFilename);
        }
        free(tmpFilename);
    }

    if (!hasErrors && (log->flags & LOG_FLAG_COMPRESS) &&
            !(log->flags & LOG_FLAG_DELAYCOMPRESS)) {
        hasErrors = compressLogFile(rotNames->finalName, log, &state->sb);
    }

    if (!hasErrors && log->logAddress) {
        char *mailFilename;

        if (log->flags & LOG_FLAG_MAILFIRST)
            mailFilename = rotNames->firstRotated;
        else
            mailFilename = rotNames->disposeName;

        if (mailFilename)
            hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log);
    }

    if (!hasErrors && rotNames->disposeName)
        hasErrors = removeLogFile(rotNames->disposeName, log);

    restoreSecCtx(&prev_context);
    return hasErrors;
}


static void sortGlobResult(glob_t *result, size_t prefix_len, const char *dformat) {
    /* TODO */
}


static int removeLogFile(const char *name, const struct logInfo *log)
{
    int fd = -1;
    int result = 0;
    message(MESS_DEBUG, "removing old log %s\n", name);

    if (log->flags & LOG_FLAG_SHRED) {
        fd = open(name, O_RDWR | O_NOFOLLOW);
        if (fd < 0) {
            message(MESS_ERROR, "error opening %s: %s\n",
                    name, strerror(errno));
            return 1;
        }
    }

    if (!debug && shred_file(fd, name, log)) {
        message(MESS_ERROR, "Failed to remove old log %s: %s\n",
                name, strerror(errno));
        result = 1;
    }

    if (fd != -1)
        close(fd);
    return result;
}


static int runScript(const struct logInfo *log, const char *logfn, const char *logrotfn, const char *script)
{
    int rc;
    pid_t pid;

    if (debug) {
        message(MESS_DEBUG, "running script with args %s %s: \"%s\"\n",
                logfn, logrotfn ? logrotfn : "", script);
        return 0;
    }

    pid = fork();

    if (pid == -1) {
        message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
        return 1;
    }

    if (pid == 0) {
        if (log->flags & LOG_FLAG_SU) {
            if (switch_user_back_permanently() != 0) {
                exit(1);
            }
        }
        execl("/bin/sh", "sh", "-c", (char *) script, "logrotate_script", (char *) logfn, (char *) logrotfn, (char *) NULL);
        exit(1);
    }

    wait(&rc);
    return rc;
}


static int rotateSingleLog(const struct logInfo *log, unsigned logNum,
                           struct logState *state, struct logNames *rotNames)
{
    int hasErrors = 0;
    struct stat sb;
    char *savedContext = NULL;

    if (!state->doRotate)
        return 0;

    if (!hasErrors) {

        if (!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
            if (setSecCtxByName(log->files[logNum], &savedContext) != 0) {
                /* error msg already printed */
                return 1;
            }
#ifdef WITH_ACL
            if ((prev_acl = acl_get_file(log->files[logNum], ACL_TYPE_ACCESS)) == NULL) {
                if (is_acl_well_supported(errno)) {
                    message(MESS_ERROR, "getting file ACL %s: %s\n",
                            log->files[logNum], strerror(errno));
                    hasErrors = 1;
                }
            }
#endif /* WITH_ACL */
            if (log->flags & LOG_FLAG_TMPFILENAME) {
                char *tmpFilename;

                if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) {
                    message_OOM();
                    restoreSecCtx(&savedContext);
                    return 1;
                }

                message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
                        tmpFilename);
                if (!debug && !hasErrors && rename(log->files[logNum], tmpFilename)) {
                    message(MESS_ERROR, "failed to rename %s to %s: %s\n",
                            log->files[logNum], tmpFilename,
                            strerror(errno));
                    hasErrors = 1;
                }

                free(tmpFilename);
            }
            else {
                message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
                        rotNames->finalName);
                if (!debug && !hasErrors &&
                        rename(log->files[logNum], rotNames->finalName)) {
                    message(MESS_ERROR, "failed to rename %s to %s: %s\n",
                            log->files[logNum], rotNames->finalName,
                            strerror(errno));
                    hasErrors = 1;
                }
            }

            if (!log->rotateCount) {
                const char *ext = "";
                if (log->compress_ext && (log->flags & LOG_FLAG_COMPRESS))
                    ext = log->compress_ext;

                free(rotNames->disposeName);
                if (asprintf(&rotNames->disposeName, "%s%s", rotNames->finalName, ext) < 0) {
                    message_OOM();
                    return 1;
                }

                message(MESS_DEBUG, "disposeName will be %s\n", rotNames->disposeName);
            }
        }

        if (!hasErrors && (log->flags & LOG_FLAG_CREATE) &&
                !(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
            int have_create_mode = 0;

            if (log->createUid == NO_UID)
                sb.st_uid = state->sb.st_uid;
            else
                sb.st_uid = log->createUid;

            if (log->createGid == NO_GID)
                sb.st_gid = state->sb.st_gid;
            else
                sb.st_gid = log->createGid;
            if (log->createMode == NO_MODE)
                sb.st_mode = state->sb.st_mode & 0777;
            else {
                sb.st_mode = log->createMode;
                have_create_mode = 1;
            }

            message(MESS_DEBUG, "creating new %s mode = 0%o uid = %d "
                    "gid = %d\n", log->files[logNum], (unsigned int) sb.st_mode,
                    (int) sb.st_uid, (int) sb.st_gid);

            if (!debug) {
                if (!hasErrors) {
                    int fd = createOutputFile(log->files[logNum], O_CREAT | O_RDWR,
                            &sb, prev_acl, have_create_mode);
#ifdef WITH_ACL
                    if (prev_acl) {
                        acl_free(prev_acl);
                        prev_acl = NULL;
                    }
#endif
                    if (fd < 0)
                        hasErrors = 1;
                    else {
                        close(fd);
                    }
                }
            }
        }

        restoreSecCtx(&savedContext);

        if (!hasErrors
                && (log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))
                && !(log->flags & LOG_FLAG_TMPFILENAME)) {
            hasErrors = copyTruncate(log->files[logNum], rotNames->finalName,
                                     &state->sb, log->flags, !log->rotateCount);
        }

#ifdef WITH_ACL
        if (prev_acl) {
            acl_free(prev_acl);
            prev_acl = NULL;
        }
#endif /* WITH_ACL */

    }
    return hasErrors;
}


static int shred_file(int fd, const char *filename, const struct logInfo *log)
{
    char count[DIGITS];    /*  that's a lot of shredding :)  */
    const char *fullCommand[6];
    int id = 0;
    int status;
    pid_t pid;

    if (log->preremove) {
        message(MESS_DEBUG, "running preremove script\n");
        if (runScript(log, filename, NULL, log->preremove)) {
            message(MESS_ERROR,
                    "error running preremove script "
                    "for %s of '%s'. Not removing this file.\n",
                    filename, log->pattern);
            /* What ever was supposed to happen did not happen,
             * therefore do not unlink the file yet.  */
            return 1;
        }
    }

    if (!(log->flags & LOG_FLAG_SHRED)) {
        goto unlink_file;
    }

    if (!(log->flags & LOG_FLAG_ALLOWHARDLINK)) {
        struct stat sb;
        if (fstat(fd, &sb) != 0) {
            message(MESS_ERROR, "cannot stat %s: %s\n", filename, strerror(errno));
            return 1;
        }

        if (sb.st_nlink != 1) {
            message(MESS_ERROR, "failed to shred \"%s\", because shredding files with"
                    " multiple hard links is disabled for %s.\n",
                    filename, log->pattern);
            return 1;
        }
    }

    message(MESS_DEBUG, "Using shred to remove the file %s\n", filename);

    fullCommand[id++] = "shred";
    fullCommand[id++] = "-u";

    if (log->shred_cycles != 0) {
        fullCommand[id++] = "-n";
        snprintf(count, DIGITS - 1, "%d", log->shred_cycles);
        fullCommand[id++] = count;
    }
    fullCommand[id++] = "-";
    fullCommand[id++] = NULL;

    pid = fork();

    if (pid == -1) {
        message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
        return 1;
    }

    if (pid == 0) {
        movefd(fd, STDOUT_FILENO);

        if (switch_user_permanently(log) != 0) {
            exit(1);
        }

        execvp(fullCommand[0], (void *) fullCommand);
        message(MESS_ERROR, "cannot execute shred command: %s\n", strerror(errno));
        exit(1);
    }

    wait(&status);

    if (!WIFEXITED(status) || WEXITSTATUS(status)) {
        message(MESS_ERROR, "Failed to shred %s, trying unlink\n", filename);
        return unlink(filename);
    }

    /* We have to unlink it after shred anyway,
     * because it doesn't remove the file itself */

unlink_file:
    if (unlink(filename) == 0)
        return 0;
    if (errno != ENOENT)
        return 1;

    /* unlink of log file that no longer exists is not a fatal error */
    message(MESS_ERROR, "error unlinking log file %s: %s\n", filename,
            strerror(errno));
    return 0;
}


struct rtrs_clt_sess *rtrs_clt_open(struct rtrs_clt_ops *ops,
				 const char *pathname,
				 const struct rtrs_addr *paths,
				 size_t paths_num, u16 port,
				 size_t pdu_sz, u8 reconnect_delay_sec,
				 s16 max_reconnect_attempts, u32 nr_poll_queues)
{
	struct rtrs_clt_path *clt_path, *tmp;
	struct rtrs_clt_sess *clt;
	int err, i;

	if (strchr(pathname, '/') || strchr(pathname, '.')) {
		pr_err("pathname cannot contain / and .\n");
		err = -EINVAL;
		goto out;
	}

	clt = alloc_clt(pathname, paths_num, port, pdu_sz, ops->priv,
			ops->link_ev,
			reconnect_delay_sec,
			max_reconnect_attempts);
	if (IS_ERR(clt)) {
		err = PTR_ERR(clt);
		goto out;
	}
	for (i = 0; i < paths_num; i++) {
		struct rtrs_clt_path *clt_path;

		clt_path = alloc_path(clt, &paths[i], nr_cpu_ids,
				  nr_poll_queues);
		if (IS_ERR(clt_path)) {
			err = PTR_ERR(clt_path);
			goto close_all_path;
		}
		if (!i)
			clt_path->for_new_clt = 1;
		list_add_tail_rcu(&clt_path->s.entry, &clt->paths_list);

		err = init_path(clt_path);
		if (err) {
			list_del_rcu(&clt_path->s.entry);
			rtrs_clt_close_conns(clt_path, true);
			free_percpu(clt_path->stats->pcpu_stats);
			kfree(clt_path->stats);
			free_path(clt_path);
			goto close_all_path;
		}

		err = rtrs_clt_create_path_files(clt_path);
		if (err) {
			list_del_rcu(&clt_path->s.entry);
			rtrs_clt_close_conns(clt_path, true);
			free_percpu(clt_path->stats->pcpu_stats);
			kfree(clt_path->stats);
			free_path(clt_path);
			goto close_all_path;
		}
	}
	err = alloc_permits(clt);
	if (err)
		goto close_all_path;

	return clt;

close_all_path:
	list_for_each_entry_safe(clt_path, tmp, &clt->paths_list, s.entry) {
		rtrs_clt_destroy_path_files(clt_path, NULL);
		rtrs_clt_close_conns(clt_path, true);
		kobject_put(&clt_path->kobj);
	}
	rtrs_clt_destroy_sysfs_root(clt);
	free_clt(clt);

out:
	return ERR_PTR(err);
}


static int rtrs_clt_write_req(struct rtrs_clt_io_req *req)
{
	struct rtrs_clt_con *con = req->con;
	struct rtrs_path *s = con->c.path;
	struct rtrs_clt_path *clt_path = to_clt_path(s);
	struct rtrs_msg_rdma_write *msg;

	struct rtrs_rbuf *rbuf;
	int ret, count = 0;
	u32 imm, buf_id;
	struct ib_reg_wr rwr;
	struct ib_send_wr inv_wr;
	struct ib_send_wr *wr = NULL;
	bool fr_en = false;

	const size_t tsize = sizeof(*msg) + req->data_len + req->usr_len;

	if (tsize > clt_path->chunk_size) {
		rtrs_wrn(s, "Write request failed, size too big %zu > %d\n",
			  tsize, clt_path->chunk_size);
		return -EMSGSIZE;
	}
	if (req->sg_cnt) {
		count = ib_dma_map_sg(clt_path->s.dev->ib_dev, req->sglist,
				      req->sg_cnt, req->dir);
		if (!count) {
			rtrs_wrn(s, "Write request failed, map failed\n");
			return -EINVAL;
		}
	}
	/* put rtrs msg after sg and user message */
	msg = req->iu->buf + req->usr_len;
	msg->type = cpu_to_le16(RTRS_MSG_WRITE);
	msg->usr_len = cpu_to_le16(req->usr_len);

	/* rtrs message on server side will be after user data and message */
	imm = req->permit->mem_off + req->data_len + req->usr_len;
	imm = rtrs_to_io_req_imm(imm);
	buf_id = req->permit->mem_id;
	req->sg_size = tsize;
	rbuf = &clt_path->rbufs[buf_id];

	if (count) {
		ret = rtrs_map_sg_fr(req, count);
		if (ret < 0) {
			rtrs_err_rl(s,
				    "Write request failed, failed to map fast reg. data, err: %d\n",
				    ret);
			ib_dma_unmap_sg(clt_path->s.dev->ib_dev, req->sglist,
					req->sg_cnt, req->dir);
			return ret;
		}
		inv_wr = (struct ib_send_wr) {
			.opcode		    = IB_WR_LOCAL_INV,
			.wr_cqe		    = &req->inv_cqe,
			.send_flags	    = IB_SEND_SIGNALED,
			.ex.invalidate_rkey = req->mr->rkey,
		};
		req->inv_cqe.done = rtrs_clt_inv_rkey_done;
		rwr = (struct ib_reg_wr) {
			.wr.opcode = IB_WR_REG_MR,
			.wr.wr_cqe = &fast_reg_cqe,
			.mr = req->mr,
			.key = req->mr->rkey,
			.access = (IB_ACCESS_LOCAL_WRITE),
		};
		wr = &rwr.wr;
		fr_en = true;
		refcount_inc(&req->ref);
	}
	/*
	 * Update stats now, after request is successfully sent it is not
	 * safe anymore to touch it.
	 */
	rtrs_clt_update_all_stats(req, WRITE);

	ret = rtrs_post_rdma_write_sg(req->con, req, rbuf, fr_en,
				      req->usr_len + sizeof(*msg),
				      imm, wr, &inv_wr);
	if (ret) {
		rtrs_err_rl(s,
			    "Write request failed: error=%d path=%s [%s:%u]\n",
			    ret, kobject_name(&clt_path->kobj), clt_path->hca_name,
			    clt_path->hca_port);
		if (req->mp_policy == MP_POLICY_MIN_INFLIGHT)
			atomic_dec(&clt_path->stats->inflight);
		if (req->sg_cnt)
			ib_dma_unmap_sg(clt_path->s.dev->ib_dev, req->sglist,
					req->sg_cnt, req->dir);
	}

	return ret;
}


static int init_conns(struct rtrs_clt_path *clt_path)
{
	unsigned int cid;
	int err;

	/*
	 * On every new session connections increase reconnect counter
	 * to avoid clashes with previous sessions not yet closed
	 * sessions on a server side.
	 */
	clt_path->s.recon_cnt++;

	/* Establish all RDMA connections  */
	for (cid = 0; cid < clt_path->s.con_num; cid++) {
		err = create_con(clt_path, cid);
		if (err)
			goto destroy;

		err = create_cm(to_clt_con(clt_path->s.con[cid]));
		if (err) {
			destroy_con(to_clt_con(clt_path->s.con[cid]));
			goto destroy;
		}
	}
	err = alloc_path_reqs(clt_path);
	if (err)
		goto destroy;

	rtrs_start_hb(&clt_path->s);

	return 0;

destroy:
	while (cid--) {
		struct rtrs_clt_con *con = to_clt_con(clt_path->s.con[cid]);

		stop_cm(con);

		mutex_lock(&con->con_mutex);
		destroy_con_cq_qp(con);
		mutex_unlock(&con->con_mutex);
		destroy_cm(con);
		destroy_con(con);
	}
	/*
	 * If we've never taken async path and got an error, say,
	 * doing rdma_resolve_addr(), switch to CONNECTION_ERR state
	 * manually to keep reconnecting.
	 */
	rtrs_clt_change_state_get_old(clt_path, RTRS_CLT_CONNECTING_ERR, NULL);

	return err;
}


static void rtrs_clt_dev_release(struct device *dev)
{
	struct rtrs_clt_sess *clt = container_of(dev, struct rtrs_clt_sess,
						 dev);

	mutex_destroy(&clt->paths_ev_mutex);
	mutex_destroy(&clt->paths_mutex);
	kfree(clt);
}


static int rtrs_inv_rkey(struct rtrs_clt_io_req *req)
{
	struct rtrs_clt_con *con = req->con;
	struct ib_send_wr wr = {
		.opcode		    = IB_WR_LOCAL_INV,
		.wr_cqe		    = &req->inv_cqe,
		.send_flags	    = IB_SEND_SIGNALED,
		.ex.invalidate_rkey = req->mr->rkey,
	};
	req->inv_cqe.done = rtrs_clt_inv_rkey_done;

	return ib_post_send(con->c.qp, &wr, NULL);
}


static void rtrs_clt_init_hb(struct rtrs_clt_path *clt_path)
{
	rtrs_init_hb(&clt_path->s, &io_comp_cqe,
		      RTRS_HB_INTERVAL_MS,
		      RTRS_HB_MISSED_MAX,
		      rtrs_clt_hb_err_handler,
		      rtrs_wq);
}


static inline void flag_success_on_conn(struct rtrs_clt_con *con)
{
	struct rtrs_clt_path *clt_path = to_clt_path(con->c.path);

	atomic_inc(&clt_path->connected_cnt);
	con->cm_err = 1;
}


static int rtrs_clt_rdma_cm_handler(struct rdma_cm_id *cm_id,
				     struct rdma_cm_event *ev)
{
	struct rtrs_clt_con *con = cm_id->context;
	struct rtrs_path *s = con->c.path;
	struct rtrs_clt_path *clt_path = to_clt_path(s);
	int cm_err = 0;

	switch (ev->event) {
	case RDMA_CM_EVENT_ADDR_RESOLVED:
		cm_err = rtrs_rdma_addr_resolved(con);
		break;
	case RDMA_CM_EVENT_ROUTE_RESOLVED:
		cm_err = rtrs_rdma_route_resolved(con);
		break;
	case RDMA_CM_EVENT_ESTABLISHED:
		cm_err = rtrs_rdma_conn_established(con, ev);
		if (!cm_err) {
			/*
			 * Report success and wake up. Here we abuse state_wq,
			 * i.e. wake up without state change, but we set cm_err.
			 */
			flag_success_on_conn(con);
			wake_up(&clt_path->state_wq);
			return 0;
		}
		break;
	case RDMA_CM_EVENT_REJECTED:
		cm_err = rtrs_rdma_conn_rejected(con, ev);
		break;
	case RDMA_CM_EVENT_DISCONNECTED:
		/* No message for disconnecting */
		cm_err = -ECONNRESET;
		break;
	case RDMA_CM_EVENT_CONNECT_ERROR:
	case RDMA_CM_EVENT_UNREACHABLE:
	case RDMA_CM_EVENT_ADDR_CHANGE:
	case RDMA_CM_EVENT_TIMEWAIT_EXIT:
		rtrs_wrn(s, "CM error (CM event: %s, err: %d)\n",
			 rdma_event_msg(ev->event), ev->status);
		cm_err = -ECONNRESET;
		break;
	case RDMA_CM_EVENT_ADDR_ERROR:
	case RDMA_CM_EVENT_ROUTE_ERROR:
		rtrs_wrn(s, "CM error (CM event: %s, err: %d)\n",
			 rdma_event_msg(ev->event), ev->status);
		cm_err = -EHOSTUNREACH;
		break;
	case RDMA_CM_EVENT_DEVICE_REMOVAL:
		/*
		 * Device removal is a special case.  Queue close and return 0.
		 */
		rtrs_clt_close_conns(clt_path, false);
		return 0;
	default:
		rtrs_err(s, "Unexpected RDMA CM error (CM event: %s, err: %d)\n",
			 rdma_event_msg(ev->event), ev->status);
		cm_err = -ECONNRESET;
		break;
	}

	if (cm_err) {
		/*
		 * cm error makes sense only on connection establishing,
		 * in other cases we rely on normal procedure of reconnecting.
		 */
		flag_error_on_conn(con, cm_err);
		rtrs_rdma_error_recovery(con);
	}

	return 0;
}


void rtrs_clt_close_conns(struct rtrs_clt_path *clt_path, bool wait)
{
	if (rtrs_clt_change_state_get_old(clt_path, RTRS_CLT_CLOSING, NULL))
		queue_work(rtrs_wq, &clt_path->close_work);
	if (wait)
		flush_work(&clt_path->close_work);
}


status_check_symbol_set(pcl_state_t * pcs, pl_symbol_map_t * mapp,
                        pcl_data_storage_t storage)
{
    gs_const_string key;
    void *value;
    pl_dict_enum_t fenum;

    pl_dict_enum_begin(&pcs->soft_fonts, &fenum);
    while (pl_dict_enum_next(&fenum, &key, &value)) {
        pl_font_t *fp = (pl_font_t *) value;

        if (fp->storage != storage)
            continue;
        if (pcl_check_symbol_support(mapp->character_requirements,
                                     fp->character_complement))
            return true;
    }
    return false;
}


status_put_font(stream * s, pcl_state_t * pcs,
                uint font_id, uint internal_id,
                pl_font_t * plfont, int font_set, bool extended)
{
    char paren = (font_set > 0 ? ')' : '(');
    bool proportional = plfont->params.proportional_spacing;

    /* first escape sequence: symbol-set selection */
    stputs(s, "SELECT=\"");
    if (pl_font_is_bound(plfont) || font_set > 0) {
        /* Bound or current font, put out the symbol set. */
        uint symbol_set = font_set > 0 ?
            pcs->font_selection[font_set].params.symbol_set :
            plfont->params.symbol_set;
        stprintf(s, "<Esc>%c%u%c", paren, symbol_set >> 5,
                 (symbol_set & 31) + 'A' - 1);
    }

    /* second escape sequence: font selection */
    stprintf(s, "<Esc>%cs%dp", paren, proportional);
    if (plfont->scaling_technology == plfst_bitmap) {   /* Bitmap font */
        status_put_floating(s, pl_fp_pitch_per_inch(&plfont->params));
        stputs(s, "h");
        status_put_floating(s, plfont->params.height_4ths / 4.0);
        stputs(s, "v");
    } else {
        /* Scalable font: output depends on whether selected */
        if (font_set > 0) {
            /* If selected, we have to cheat and reach up for info;
             * plfont is below where the scaled values exist. */
            if (proportional) {
                status_put_floating(s,
                                    pcs->font_selection[font_set].params.
                                    height_4ths / 4.0);
                stputs(s, "h");
            } else {
                status_put_floating(s,
                                    pl_fp_pitch_per_inch(&pcs->
                                                         font_selection
                                                         [font_set].params));
                stputs(s, "v");
            }
        } else {
            stputs(s, proportional ? "__v" : "__h");
        }
    }
    stprintf(s, "%ds%db%uT", plfont->params.style,
             plfont->params.stroke_weight, plfont->params.typeface_family);
    if (plfont->storage & pcds_downloaded)
        stprintf(s, "<Esc>%c%uX", paren, font_id);
    stputs(s, "\"\r\n");
    if (!pl_font_is_bound(plfont) && font_set < 0) {
        int nid;
        ushort *idlist;
        pl_dict_enum_t denum;
        gs_const_string key;
        void *value;

        idlist = (ushort *) gs_alloc_bytes(pcs->memory,
                                           pl_dict_length(&pcs->
                                                          soft_symbol_sets,
                                                          false) +
                                           pl_dict_length(&pcs->
                                                          built_in_symbol_sets,
                                                          false),
                                           "status_fonts(idlist)");
        if (idlist == NULL)
            return e_Memory;
        nid = 0;
        /* Current fonts show the symbol set bound to them, above. */

        /* NOTE: Temporarily chain soft, built-in symbol sets.  DON'T
         * exit this section without unchaining them. */
        pl_dict_set_parent(&pcs->soft_symbol_sets,
                           &pcs->built_in_symbol_sets);
        pl_dict_enum_begin(&pcs->soft_symbol_sets, &denum);
        while (pl_dict_enum_next(&denum, &key, &value)) {
            pcl_symbol_set_t *ssp = (pcl_symbol_set_t *) value;
            pl_glyph_vocabulary_t gx;

            for (gx = plgv_MSL; gx < plgv_next; gx++)
                if (ssp->maps[gx] != NULL &&
                    pcl_check_symbol_support(ssp->maps[gx]->
                                             character_requirements,
                                             plfont->character_complement)) {
                    nid =
                        status_add_symbol_id(idlist, nid,
                                             (ssp->maps[gx]->id[0] << 8) +
                                             ssp->maps[gx]->id[1]);
                    break;      /* one will suffice */
                }
        }
        pl_dict_set_parent(&pcs->soft_symbol_sets, NULL);
        /* Symbol sets are back to normal. */

        gs_free_object(pcs->memory, (void *)idlist, "status_fonts(idlist)");
    }
    if (extended) {             /* Put out the "internal ID number". */
        if (plfont->storage & pcds_temporary)
            stputs(s, "DEFID=NONE\r\n");
        else {
            stputs(s, "DEFID=\"");
            if (plfont->storage & pcds_all_cartridges) {
                int c;
                int n = (plfont->storage & pcds_all_cartridges) >>
                    pcds_cartridge_shift;

                /* pick out the bit index of the cartridge */
                for (c = 0; (n & 1) == 0; c++)
                    n >>= 1;
                stprintf(s, "C%d ", c);
            } else if (plfont->storage & pcds_all_simms) {
                int m;
                int n = (plfont->storage & pcds_all_simms) >> pcds_simm_shift;

                /* pick out the bit index of the SIMM */
                for (m = 0; (n & 1) == 0; m++)
                    n >>= 1;
                stprintf(s, "M%d ", m);
            } else
                /* internal _vs_ permanent soft */
                stputs(s, (plfont->storage & pcds_internal) ? "I " : "S ");
            stprintf(s, "%d\"\r\n", internal_id);
        }

        /* XXX Put out the font name - we need a way to get the name
         * for fonts that weren't downloaded, hence lack the known
         * header field. */
        if ((plfont->storage & pcds_downloaded) && plfont->header != NULL) {
            /* Wire in the size of the FontName field (16)--it can't
             * change anyway, and this saves work. */
            pcl_font_header_t *hdr = (pcl_font_header_t *) (plfont->header);

            stprintf(s, "NAME=\"%.16s\"\r\n", hdr->FontName);
        }
    }
    return 0;
}


status_fonts(stream * s, pcl_state_t * pcs, pcl_data_storage_t storage)
{
    return status_do_fonts(s, pcs, storage, false);
}


 */
static inline int io_uring_add_tctx_node(struct io_ring_ctx *ctx)
{
	struct io_uring_task *tctx = current->io_uring;

	if (likely(tctx && tctx->last == ctx))
		return 0;
	return __io_uring_add_tctx_node(ctx);


static int io_timeout_prep(struct io_kiocb *req, const struct io_uring_sqe *sqe,
			   bool is_timeout_link)
{
	struct io_timeout_data *data;
	unsigned flags;
	u32 off = READ_ONCE(sqe->off);

	if (unlikely(req->ctx->flags & IORING_SETUP_IOPOLL))
		return -EINVAL;
	if (sqe->ioprio || sqe->buf_index || sqe->len != 1)
		return -EINVAL;
	if (off && is_timeout_link)
		return -EINVAL;
	flags = READ_ONCE(sqe->timeout_flags);
	if (flags & ~IORING_TIMEOUT_ABS)
		return -EINVAL;

	req->timeout.off = off;
	if (unlikely(off && !req->ctx->off_timeout_used))
		req->ctx->off_timeout_used = true;

	if (!req->async_data && io_alloc_async_data(req))
		return -ENOMEM;

	data = req->async_data;
	data->req = req;

	if (get_timespec64(&data->ts, u64_to_user_ptr(sqe->addr)))
		return -EFAULT;

	data->mode = io_translate_timeout_mode(flags);
	hrtimer_init(&data->timer, CLOCK_MONOTONIC, data->mode);
	if (is_timeout_link)
		io_req_track_inflight(req);
	return 0;


static int io_accept(struct io_kiocb *req, unsigned int issue_flags)
{
	struct io_accept *accept = &req->accept;
	bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK;
	unsigned int file_flags = force_nonblock ? O_NONBLOCK : 0;
	int ret;

	if (req->file->f_flags & O_NONBLOCK)
		req->flags |= REQ_F_NOWAIT;

	ret = __sys_accept4_file(req->file, file_flags, accept->addr,
					accept->addr_len, accept->flags,
					accept->nofile);
	if (ret == -EAGAIN && force_nonblock)
		return -EAGAIN;
	if (ret < 0) {
		if (ret == -ERESTARTSYS)
			ret = -EINTR;
		req_set_fail(req);
	}
	__io_req_complete(req, issue_flags, ret, 0);
	return 0;
}


static int io_init_req(struct io_ring_ctx *ctx, struct io_kiocb *req,
		       const struct io_uring_sqe *sqe)
{
	struct io_submit_state *state;
	unsigned int sqe_flags;
	int personality, ret = 0;

	req->opcode = READ_ONCE(sqe->opcode);
	/* same numerical values with corresponding REQ_F_*, safe to copy */
	req->flags = sqe_flags = READ_ONCE(sqe->flags);
	req->user_data = READ_ONCE(sqe->user_data);
	req->file = NULL;
	req->fixed_rsrc_refs = NULL;
	/* one is dropped after submission, the other at completion */
	atomic_set(&req->refs, 2);
	req->task = current;

	/* enforce forwards compatibility on users */
	if (unlikely(sqe_flags & ~SQE_VALID_FLAGS))
		return -EINVAL;
	if (unlikely(req->opcode >= IORING_OP_LAST))
		return -EINVAL;
	if (!io_check_restriction(ctx, req, sqe_flags))
		return -EACCES;

	if ((sqe_flags & IOSQE_BUFFER_SELECT) &&
	    !io_op_defs[req->opcode].buffer_select)
		return -EOPNOTSUPP;
	if (unlikely(sqe_flags & IOSQE_IO_DRAIN))
		ctx->drain_active = true;

	personality = READ_ONCE(sqe->personality);
	if (personality) {
		req->creds = xa_load(&ctx->personalities, personality);
		if (!req->creds)
			return -EINVAL;
		get_cred(req->creds);
		req->flags |= REQ_F_CREDS;
	}
	state = &ctx->submit_state;

	/*
	 * Plug now if we have more than 1 IO left after this, and the target
	 * is potentially a read/write to block based storage.
	 */
	if (!state->plug_started && state->ios_left > 1 &&
	    io_op_defs[req->opcode].plug) {
		blk_start_plug(&state->plug);
		state->plug_started = true;
	}

	if (io_op_defs[req->opcode].needs_file) {
		bool fixed = req->flags & REQ_F_FIXED_FILE;

		req->file = io_file_get(state, req, READ_ONCE(sqe->fd), fixed);
		if (unlikely(!req->file))
			ret = -EBADF;
	}

	state->ios_left--;
	return ret;


static inline bool io_sqring_full(struct io_ring_ctx *ctx)
{
	struct io_rings *r = ctx->rings;

	return READ_ONCE(r->sq.tail) - ctx->cached_sq_head == ctx->sq_entries;
}


static int io_recv(struct io_kiocb *req, unsigned int issue_flags)
{
	struct io_buffer *kbuf;
	struct io_sr_msg *sr = &req->sr_msg;
	struct msghdr msg;
	void __user *buf = sr->buf;
	struct socket *sock;
	struct iovec iov;
	unsigned flags;
	int min_ret = 0;
	int ret, cflags = 0;
	bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK;

	sock = sock_from_file(req->file);
	if (unlikely(!sock))
		return -ENOTSOCK;

	if (req->flags & REQ_F_BUFFER_SELECT) {
		kbuf = io_recv_buffer_select(req, !force_nonblock);
		if (IS_ERR(kbuf))
			return PTR_ERR(kbuf);
		buf = u64_to_user_ptr(kbuf->addr);
	}

	ret = import_single_range(READ, buf, sr->len, &iov, &msg.msg_iter);
	if (unlikely(ret))
		goto out_free;

	msg.msg_name = NULL;
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_namelen = 0;
	msg.msg_iocb = NULL;
	msg.msg_flags = 0;

	flags = req->sr_msg.msg_flags;
	if (force_nonblock)
		flags |= MSG_DONTWAIT;
	if (flags & MSG_WAITALL)
		min_ret = iov_iter_count(&msg.msg_iter);

	ret = sock_recvmsg(sock, &msg, flags);
	if (force_nonblock && ret == -EAGAIN)
		return -EAGAIN;
	if (ret == -ERESTARTSYS)
		ret = -EINTR;
out_free:
	if (req->flags & REQ_F_BUFFER_SELECTED)
		cflags = io_put_recv_kbuf(req);
	if (ret < min_ret || ((flags & MSG_WAITALL) && (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC))))
		req_set_fail(req);
	__io_req_complete(req, issue_flags, ret, cflags);
	return 0;
}