Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 1 | // Copyright 2020 The Wuffs Authors. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | // ---------------- |
| 16 | |
| 17 | /* |
| 18 | imageviewer is a simple GUI program for viewing images. On Linux, GUI means |
| 19 | X11. To run: |
| 20 | |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 21 | $CXX imageviewer.cc -lxcb -lxcb-image -lxcb-render -lxcb-render-util && \ |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 22 | ./a.out ../../test/data/bricks-*.gif; rm -f a.out |
| 23 | |
Nigel Tao | ccee207 | 2021-02-27 22:40:32 +1100 | [diff] [blame] | 24 | for a C++ compiler $CXX, such as clang++ or g++. |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 25 | |
| 26 | The Space and BackSpace keys cycle through the files, if more than one was |
| 27 | given as command line arguments. If none were given, the program reads from |
| 28 | stdin. |
| 29 | |
| 30 | The Return key is equivalent to the Space key. |
| 31 | |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 32 | The ',' Comma and '.' Period keys cycle through background colors, which |
| 33 | matters if the image has fully or partially transparent pixels. |
| 34 | |
Nigel Tao | 90d3232 | 2021-10-07 12:59:38 +1100 | [diff] [blame] | 35 | The '1' to '8' keys change the magnification zoom (or minification zoom with |
| 36 | the shift key). The '0' key toggles nearest neighbor and bilinear filtering. |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 37 | |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 38 | The arrow keys (or hjkl keys) scroll the image. The '`' key resets. |
| 39 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 40 | The Escape key quits. |
| 41 | */ |
| 42 | |
| 43 | #include <inttypes.h> |
Nigel Tao | b003f9f | 2021-10-19 22:09:21 +1100 | [diff] [blame] | 44 | #include <math.h> |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 45 | #include <stdio.h> |
| 46 | #include <stdlib.h> |
| 47 | |
| 48 | // Wuffs ships as a "single file C library" or "header file library" as per |
| 49 | // https://github.com/nothings/stb/blob/master/docs/stb_howto.txt |
| 50 | // |
| 51 | // To use that single file as a "foo.c"-like implementation, instead of a |
| 52 | // "foo.h"-like header, #define WUFFS_IMPLEMENTATION before #include'ing or |
| 53 | // compiling it. |
| 54 | #define WUFFS_IMPLEMENTATION |
| 55 | |
Nigel Tao | 7f9f37c | 2021-10-04 12:35:32 +1100 | [diff] [blame] | 56 | // Defining the WUFFS_CONFIG__STATIC_FUNCTIONS macro is optional, but when |
| 57 | // combined with WUFFS_IMPLEMENTATION, it demonstrates making all of Wuffs' |
| 58 | // functions have static storage. |
| 59 | // |
| 60 | // This can help the compiler ignore or discard unused code, which can produce |
| 61 | // faster compiles and smaller binaries. Other motivations are discussed in the |
| 62 | // "ALLOW STATIC IMPLEMENTATION" section of |
| 63 | // https://raw.githubusercontent.com/nothings/stb/master/docs/stb_howto.txt |
| 64 | #define WUFFS_CONFIG__STATIC_FUNCTIONS |
| 65 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 66 | // Defining the WUFFS_CONFIG__MODULE* macros are optional, but it lets users of |
Nigel Tao | 2f78804 | 2021-01-23 19:29:19 +1100 | [diff] [blame] | 67 | // release/c/etc.c choose which parts of Wuffs to build. That file contains the |
| 68 | // entire Wuffs standard library, implementing a variety of codecs and file |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 69 | // formats. Without this macro definition, an optimizing compiler or linker may |
| 70 | // very well discard Wuffs code for unused codecs, but listing the Wuffs |
| 71 | // modules we use makes that process explicit. Preprocessing means that such |
| 72 | // code simply isn't compiled. |
| 73 | #define WUFFS_CONFIG__MODULES |
Nigel Tao | 58c30e9 | 2020-12-23 22:11:04 +1100 | [diff] [blame] | 74 | #define WUFFS_CONFIG__MODULE__ADLER32 |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 75 | #define WUFFS_CONFIG__MODULE__AUX__BASE |
| 76 | #define WUFFS_CONFIG__MODULE__AUX__IMAGE |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 77 | #define WUFFS_CONFIG__MODULE__BASE |
Nigel Tao | e6cc2fe | 2020-02-06 14:36:29 +1100 | [diff] [blame] | 78 | #define WUFFS_CONFIG__MODULE__BMP |
Nigel Tao | 58c30e9 | 2020-12-23 22:11:04 +1100 | [diff] [blame] | 79 | #define WUFFS_CONFIG__MODULE__CRC32 |
| 80 | #define WUFFS_CONFIG__MODULE__DEFLATE |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 81 | #define WUFFS_CONFIG__MODULE__GIF |
| 82 | #define WUFFS_CONFIG__MODULE__LZW |
Nigel Tao | 1197e86 | 2020-12-22 15:29:35 +1100 | [diff] [blame] | 83 | #define WUFFS_CONFIG__MODULE__NIE |
Nigel Tao | 58c30e9 | 2020-12-23 22:11:04 +1100 | [diff] [blame] | 84 | #define WUFFS_CONFIG__MODULE__PNG |
Nigel Tao | 4c09c77 | 2022-01-14 22:46:57 +1100 | [diff] [blame] | 85 | #define WUFFS_CONFIG__MODULE__TGA |
Nigel Tao | 4feb641 | 2020-01-16 15:02:32 +1100 | [diff] [blame] | 86 | #define WUFFS_CONFIG__MODULE__WBMP |
Nigel Tao | 58c30e9 | 2020-12-23 22:11:04 +1100 | [diff] [blame] | 87 | #define WUFFS_CONFIG__MODULE__ZLIB |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 88 | |
| 89 | // If building this program in an environment that doesn't easily accommodate |
| 90 | // relative includes, you can use the script/inline-c-relative-includes.go |
| 91 | // program to generate a stand-alone C file. |
| 92 | #include "../../release/c/wuffs-unsupported-snapshot.c" |
| 93 | |
Nigel Tao | 2945ef2 | 2021-10-07 12:35:12 +1100 | [diff] [blame] | 94 | // X11 limits its image dimensions to uint16_t and some coordinates to int16_t. |
| 95 | #define MAX_INCL_DIMENSION 32767 |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 96 | |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 97 | #define NUM_BACKGROUND_COLORS 3 |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 98 | #define NUM_ZOOMS 8 |
Nigel Tao | fdac24a | 2020-03-06 21:53:08 +1100 | [diff] [blame] | 99 | #define SRC_BUFFER_ARRAY_SIZE (64 * 1024) |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 100 | |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 101 | wuffs_base__color_u32_argb_premul g_background_colors[NUM_BACKGROUND_COLORS] = { |
| 102 | 0xFF000000, |
| 103 | 0xFFFFFFFF, |
| 104 | 0xFFA9009A, |
| 105 | }; |
| 106 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 107 | uint32_t g_width = 0; |
| 108 | uint32_t g_height = 0; |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 109 | wuffs_aux::MemOwner g_pixbuf_mem_owner(nullptr, &free); |
Nigel Tao | 314d265 | 2021-04-02 23:57:18 +1100 | [diff] [blame] | 110 | wuffs_base__pixel_buffer g_pixbuf = {0}; |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 111 | uint32_t g_background_color_index = 0; |
Nigel Tao | 90d3232 | 2021-10-07 12:59:38 +1100 | [diff] [blame] | 112 | int32_t g_zoom = 0; |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 113 | int32_t g_pos_x = 0; |
| 114 | int32_t g_pos_y = 0; |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 115 | bool g_filter = false; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 116 | |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 117 | struct { |
| 118 | int remaining_argc; |
| 119 | char** remaining_argv; |
| 120 | |
| 121 | double screen_gamma; |
| 122 | } g_flags = {0}; |
| 123 | |
| 124 | static const char* g_usage = |
| 125 | "Usage: imageviewer -flags input0.gif input1.png\n" |
| 126 | "\n" |
| 127 | "Flags:\n" |
| 128 | " -screen_gamma=N.N (default 2.2; 0 disables gamma correction)"; |
| 129 | |
| 130 | const char* // |
| 131 | parse_flags(int argc, char** argv) { |
| 132 | g_flags.screen_gamma = 2.2; |
| 133 | |
| 134 | int c = (argc > 0) ? 1 : 0; // Skip argv[0], the program name. |
| 135 | for (; c < argc; c++) { |
| 136 | char* arg = argv[c]; |
| 137 | if (*arg++ != '-') { |
| 138 | break; |
| 139 | } |
| 140 | |
| 141 | // A double-dash "--foo" is equivalent to a single-dash "-foo". As special |
| 142 | // cases, a bare "-" is not a flag (some programs may interpret it as |
| 143 | // stdin) and a bare "--" means to stop parsing flags. |
| 144 | if (*arg == '\x00') { |
| 145 | break; |
| 146 | } else if (*arg == '-') { |
| 147 | arg++; |
| 148 | if (*arg == '\x00') { |
| 149 | c++; |
| 150 | break; |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | if (!strncmp(arg, "screen_gamma=", 13)) { |
| 155 | g_flags.screen_gamma = atof(arg + 13); |
| 156 | continue; |
| 157 | } |
| 158 | |
| 159 | return g_usage; |
| 160 | } |
| 161 | |
| 162 | g_flags.remaining_argc = argc - c; |
| 163 | g_flags.remaining_argv = argv + c; |
| 164 | return NULL; |
| 165 | } |
| 166 | |
Nigel Tao | b003f9f | 2021-10-19 22:09:21 +1100 | [diff] [blame] | 167 | class MyDecodeImageCallbacks : public wuffs_aux::DecodeImageCallbacks { |
| 168 | public: |
| 169 | MyDecodeImageCallbacks() : m_combined_gamma(1.0) {} |
| 170 | |
| 171 | private: |
| 172 | std::string // |
| 173 | HandleMetadata(const wuffs_base__more_information& minfo, |
Nigel Tao | c754f81 | 2021-10-31 15:03:06 +1100 | [diff] [blame] | 174 | wuffs_base__slice_u8 raw) override { |
Nigel Tao | b003f9f | 2021-10-19 22:09:21 +1100 | [diff] [blame] | 175 | if (minfo.flavor == WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED) { |
| 176 | switch (minfo.metadata__fourcc()) { |
| 177 | case WUFFS_BASE__FOURCC__GAMA: |
| 178 | // metadata_parsed__gama returns the inverse gamma scaled by 1e5. |
| 179 | m_combined_gamma = |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 180 | 1e5 / (g_flags.screen_gamma * minfo.metadata_parsed__gama()); |
Nigel Tao | b003f9f | 2021-10-19 22:09:21 +1100 | [diff] [blame] | 181 | break; |
| 182 | } |
| 183 | } |
Nigel Tao | c754f81 | 2021-10-31 15:03:06 +1100 | [diff] [blame] | 184 | return wuffs_aux::DecodeImageCallbacks::HandleMetadata(minfo, raw); |
Nigel Tao | b003f9f | 2021-10-19 22:09:21 +1100 | [diff] [blame] | 185 | } |
| 186 | |
| 187 | void // |
| 188 | Done(wuffs_aux::DecodeImageResult& result, |
| 189 | wuffs_aux::sync_io::Input& input, |
| 190 | wuffs_aux::IOBuffer& buffer, |
| 191 | wuffs_base__image_decoder::unique_ptr image_decoder) override { |
| 192 | // Apply basic color correction - gamma correction. Proper color correction |
| 193 | // should also involve considering any CHRM, ICCP and SRGB metadata but |
| 194 | // that requires a non-trivial amount of code (such as skcms, Skia's Color |
| 195 | // Management System). To keep this example program simple, we only |
| 196 | // consider GAMA metadata (and 8-bit color channels) here. |
| 197 | // |
| 198 | // This code also assumes that wuffs_aux::DecodeImageCallbacks defaults to |
| 199 | // producing a WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL pixel buffer. |
| 200 | if ((result.pixbuf.pixel_format().repr == |
| 201 | WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL) && |
| 202 | ((m_combined_gamma < 0.9999) || (1.0001 < m_combined_gamma))) { |
| 203 | uint8_t lut[256]; |
| 204 | lut[0x00] = 0x00; |
| 205 | lut[0xFF] = 0xFF; |
| 206 | for (uint32_t i = 1; i < 0xFF; i++) { |
| 207 | lut[i] = |
| 208 | (uint8_t)(floor(255.0 * pow(i / 255.0, m_combined_gamma) + 0.5)); |
| 209 | } |
| 210 | |
| 211 | wuffs_base__table_u8 t = result.pixbuf.plane(0); |
| 212 | size_t w4 = t.width / 4; |
| 213 | for (size_t y = 0; y < t.height; y++) { |
| 214 | uint8_t* ptr = t.ptr + (y * t.stride); |
| 215 | for (size_t x = 0; x < w4; x++) { |
| 216 | ptr[0] = lut[ptr[0]]; |
| 217 | ptr[1] = lut[ptr[1]]; |
| 218 | ptr[2] = lut[ptr[2]]; |
| 219 | ptr += 4; |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | // m_combined_gamma holds the product of the screen gamma and the image |
| 226 | // file's inverse gamma. |
| 227 | double m_combined_gamma; |
| 228 | }; |
| 229 | |
Nigel Tao | 2914bae | 2020-02-26 09:40:30 +1100 | [diff] [blame] | 230 | bool // |
| 231 | load_image(const char* filename) { |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 232 | FILE* file = stdin; |
| 233 | const char* adj_filename = "<stdin>"; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 234 | if (filename) { |
Nigel Tao | 816475b | 2021-07-07 20:28:35 +1000 | [diff] [blame] | 235 | FILE* f = fopen(filename, "rb"); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 236 | if (f == NULL) { |
| 237 | printf("%s: could not open file\n", filename); |
| 238 | return false; |
| 239 | } |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 240 | file = f; |
| 241 | adj_filename = filename; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 242 | } |
| 243 | |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 244 | g_width = 0; |
| 245 | g_height = 0; |
| 246 | g_pixbuf_mem_owner.reset(); |
Nigel Tao | 314d265 | 2021-04-02 23:57:18 +1100 | [diff] [blame] | 247 | g_pixbuf = wuffs_base__null_pixel_buffer(); |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 248 | |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 249 | uint64_t dia_flags = 0; |
| 250 | if (g_flags.screen_gamma > 0) { |
| 251 | dia_flags |= wuffs_aux::DecodeImageArgFlags::REPORT_METADATA_GAMA; |
| 252 | } |
| 253 | |
Nigel Tao | b003f9f | 2021-10-19 22:09:21 +1100 | [diff] [blame] | 254 | MyDecodeImageCallbacks callbacks; |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 255 | wuffs_aux::sync_io::FileInput input(file); |
| 256 | wuffs_aux::DecodeImageResult res = wuffs_aux::DecodeImage( |
Nigel Tao | 2b7b6e1 | 2021-10-18 00:40:11 +1100 | [diff] [blame] | 257 | callbacks, input, wuffs_aux::DecodeImageArgQuirks::DefaultValue(), |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 258 | wuffs_aux::DecodeImageArgFlags(dia_flags), |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 259 | // Use PIXEL_BLEND__SRC_OVER, not the default PIXEL_BLEND__SRC, because |
Nigel Tao | 441e96a | 2021-03-11 15:28:20 +1100 | [diff] [blame] | 260 | // we also pass a background color. |
Nigel Tao | ca33506 | 2021-10-18 00:25:13 +1100 | [diff] [blame] | 261 | wuffs_aux::DecodeImageArgPixelBlend(WUFFS_BASE__PIXEL_BLEND__SRC_OVER), |
| 262 | wuffs_aux::DecodeImageArgBackgroundColor( |
| 263 | g_background_colors[g_background_color_index]), |
| 264 | wuffs_aux::DecodeImageArgMaxInclDimension(MAX_INCL_DIMENSION)); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 265 | if (filename) { |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 266 | fclose(file); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 267 | } |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 268 | |
Nigel Tao | c86ac5b | 2021-04-03 10:59:33 +1100 | [diff] [blame] | 269 | // wuffs_aux::DecodeImageCallbacks's default implementation should give us an |
| 270 | // interleaved (not multi-planar) pixel buffer, so that all of the pixel data |
| 271 | // is in a single 2-dimensional table (plane 0). Later on, we re-interpret |
| 272 | // that table as XCB image data, which isn't something we could do if we had |
| 273 | // e.g. multi-planar YCbCr. |
| 274 | if (!res.pixbuf.pixcfg.pixel_format().is_interleaved()) { |
| 275 | printf("%s: non-interleaved pixbuf\n", adj_filename); |
| 276 | return false; |
| 277 | } |
| 278 | wuffs_base__table_u8 tab = res.pixbuf.plane(0); |
| 279 | if (tab.width != tab.stride) { |
| 280 | // The xcb_image_create_native call, later on, assumes that (tab.height * |
| 281 | // tab.stride) bytes are readable, which isn't quite the same as what |
| 282 | // wuffs_base__table__flattened_length(tab.width, tab.height, tab.stride) |
| 283 | // returns unless the table is tight (its width equals its stride). |
| 284 | printf("%s: could not allocate tight pixbuf\n", adj_filename); |
| 285 | return false; |
| 286 | } |
| 287 | |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 288 | g_width = res.pixbuf.pixcfg.width(); |
| 289 | g_height = res.pixbuf.pixcfg.height(); |
| 290 | g_pixbuf_mem_owner = std::move(res.pixbuf_mem_owner); |
Nigel Tao | 314d265 | 2021-04-02 23:57:18 +1100 | [diff] [blame] | 291 | g_pixbuf = res.pixbuf; |
Nigel Tao | 04f8686 | 2021-03-01 23:01:51 +1100 | [diff] [blame] | 292 | |
| 293 | if (res.error_message.empty()) { |
| 294 | printf("%s: ok (%" PRIu32 " x %" PRIu32 ")\n", adj_filename, g_width, |
| 295 | g_height); |
| 296 | } else { |
| 297 | printf("%s: %s\n", adj_filename, res.error_message.c_str()); |
| 298 | } |
| 299 | return res.pixbuf.pixcfg.is_valid(); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 300 | } |
| 301 | |
Nigel Tao | f45f01f | 2020-06-05 14:06:56 +1000 | [diff] [blame] | 302 | // --------------------------------------------------------------------- |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 303 | |
| 304 | #if defined(__linux__) |
| 305 | #define SUPPORTED_OPERATING_SYSTEM |
| 306 | |
Nigel Tao | 2453a8d | 2021-10-03 13:53:27 +1100 | [diff] [blame] | 307 | #include <xcb/render.h> |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 308 | #include <xcb/xcb.h> |
| 309 | #include <xcb/xcb_image.h> |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 310 | #include <xcb/xcb_renderutil.h> |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 311 | |
| 312 | #define XK_BackSpace 0xFF08 |
| 313 | #define XK_Escape 0xFF1B |
| 314 | #define XK_Return 0xFF0D |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 315 | #define XK_Left 0xFF51 |
| 316 | #define XK_Up 0xFF52 |
| 317 | #define XK_Right 0xFF53 |
| 318 | #define XK_Down 0xFF54 |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 319 | |
Nigel Tao | 2945ef2 | 2021-10-07 12:35:12 +1100 | [diff] [blame] | 320 | uint32_t g_maximum_request_length = 0; // Measured in 4-byte units. |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 321 | xcb_atom_t g_atom_net_wm_name = XCB_NONE; |
| 322 | xcb_atom_t g_atom_utf8_string = XCB_NONE; |
| 323 | xcb_atom_t g_atom_wm_protocols = XCB_NONE; |
| 324 | xcb_atom_t g_atom_wm_delete_window = XCB_NONE; |
| 325 | xcb_pixmap_t g_pixmap = XCB_NONE; |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 326 | xcb_gcontext_t g_pixmap_gc = XCB_NONE; |
| 327 | xcb_render_picture_t g_pixmap_picture = XCB_NONE; |
Nigel Tao | 1cf0df9 | 2021-10-03 15:30:04 +1100 | [diff] [blame] | 328 | xcb_render_pictforminfo_t* g_pictforminfo = NULL; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 329 | xcb_keysym_t* g_keysyms = NULL; |
| 330 | xcb_get_keyboard_mapping_reply_t* g_keyboard_mapping = NULL; |
| 331 | |
Nigel Tao | 2914bae | 2020-02-26 09:40:30 +1100 | [diff] [blame] | 332 | void // |
| 333 | init_keymap(xcb_connection_t* c, const xcb_setup_t* z) { |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 334 | xcb_get_keyboard_mapping_cookie_t cookie = xcb_get_keyboard_mapping( |
| 335 | c, z->min_keycode, z->max_keycode - z->min_keycode + 1); |
| 336 | g_keyboard_mapping = xcb_get_keyboard_mapping_reply(c, cookie, NULL); |
| 337 | g_keysyms = (xcb_keysym_t*)(g_keyboard_mapping + 1); |
| 338 | } |
| 339 | |
Nigel Tao | 2914bae | 2020-02-26 09:40:30 +1100 | [diff] [blame] | 340 | xcb_window_t // |
| 341 | make_window(xcb_connection_t* c, xcb_screen_t* s) { |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 342 | xcb_window_t w = xcb_generate_id(c); |
| 343 | uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; |
| 344 | uint32_t value_list[2]; |
| 345 | value_list[0] = s->black_pixel; |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 346 | value_list[1] = XCB_EVENT_MASK_KEY_PRESS | // |
| 347 | XCB_EVENT_MASK_BUTTON_PRESS | // |
| 348 | XCB_EVENT_MASK_BUTTON_1_MOTION | // |
| 349 | XCB_EVENT_MASK_EXPOSURE; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 350 | xcb_create_window(c, 0, w, s->root, 0, 0, 1024, 768, 0, |
| 351 | XCB_WINDOW_CLASS_INPUT_OUTPUT, s->root_visual, value_mask, |
| 352 | value_list); |
| 353 | xcb_change_property(c, XCB_PROP_MODE_REPLACE, w, g_atom_net_wm_name, |
| 354 | g_atom_utf8_string, 8, 12, "Image Viewer"); |
| 355 | xcb_change_property(c, XCB_PROP_MODE_REPLACE, w, g_atom_wm_protocols, |
| 356 | XCB_ATOM_ATOM, 32, 1, &g_atom_wm_delete_window); |
| 357 | xcb_map_window(c, w); |
| 358 | return w; |
| 359 | } |
| 360 | |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 361 | void // |
| 362 | apply_zoom_and_filter(xcb_connection_t* c) { |
Nigel Tao | 90d3232 | 2021-10-07 12:59:38 +1100 | [diff] [blame] | 363 | static const xcb_render_fixed_t neg_zooms[NUM_ZOOMS] = { |
| 364 | 0x00010000, // 1/1 as 16.16 fixed point |
| 365 | 0x00020000, // 2/1 |
| 366 | 0x00040000, // 4/1 |
| 367 | 0x00080000, // 8/1 |
| 368 | 0x00100000, // 16/1 |
| 369 | 0x00200000, // 32/1 |
| 370 | 0x00400000, // 64/1 |
| 371 | 0x00800000, // 128/1 |
| 372 | }; |
| 373 | static const xcb_render_fixed_t pos_zooms[NUM_ZOOMS] = { |
| 374 | 0x00010000, // 1/1 as 16.16 fixed point |
| 375 | 0x00008000, // 1/2 |
| 376 | 0x00004000, // 1/4 |
| 377 | 0x00002000, // 1/8 |
| 378 | 0x00001000, // 1/16 |
| 379 | 0x00000800, // 1/32 |
| 380 | 0x00000400, // 1/64 |
| 381 | 0x00000200, // 1/128 |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 382 | }; |
| 383 | |
Nigel Tao | 90d3232 | 2021-10-07 12:59:38 +1100 | [diff] [blame] | 384 | xcb_render_fixed_t z = g_zoom < 0 |
| 385 | ? neg_zooms[((uint32_t)(-g_zoom)) % NUM_ZOOMS] |
| 386 | : pos_zooms[((uint32_t)(+g_zoom)) % NUM_ZOOMS]; |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 387 | xcb_render_set_picture_transform(c, g_pixmap_picture, |
| 388 | ((xcb_render_transform_t){ |
| 389 | z, 0, 0, // |
| 390 | 0, z, 0, // |
| 391 | 0, 0, 0x10000, // |
| 392 | })); |
| 393 | |
| 394 | uint16_t f_len = 7; |
| 395 | const char* f_ptr = "nearest"; |
| 396 | if (g_filter && (g_zoom != 0)) { |
| 397 | f_len = 8; |
| 398 | f_ptr = "bilinear"; |
| 399 | } |
| 400 | xcb_render_set_picture_filter(c, g_pixmap_picture, f_len, f_ptr, 0, NULL); |
| 401 | } |
| 402 | |
| 403 | // zoom_shift returns (a << g_zoom), roughly speaking, but saturates at an |
| 404 | // arbitrary value called M. |
| 405 | // |
| 406 | // The final two arguments to xcb_render_composite have uint16_t type (and |
| 407 | // UINT16_MAX is 65535), but in practice, values above M sometimes don't work |
| 408 | // in that the xcb_render_composite call has no visible effect. |
| 409 | // |
| 410 | // Some xrender debugging could potentially derive a more accurate maximum but |
| 411 | // for now, the M = 30000 round number will do. |
| 412 | uint16_t // |
| 413 | zoom_shift(uint32_t a) { |
| 414 | uint16_t M = 30000; |
Nigel Tao | 90d3232 | 2021-10-07 12:59:38 +1100 | [diff] [blame] | 415 | uint64_t b = g_zoom < 0 |
| 416 | ? (((uint64_t)a) >> (((uint32_t)(-g_zoom)) % NUM_ZOOMS)) |
| 417 | : (((uint64_t)a) << (((uint32_t)(+g_zoom)) % NUM_ZOOMS)); |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 418 | return (b < M) ? b : M; |
| 419 | } |
| 420 | |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 421 | int32_t // |
| 422 | calculate_delta(uint16_t state) { |
| 423 | if (state & XCB_MOD_MASK_SHIFT) { |
| 424 | return 256; |
| 425 | } else if (state & XCB_MOD_MASK_CONTROL) { |
| 426 | return 1; |
| 427 | } |
| 428 | return 16; |
| 429 | } |
| 430 | |
Nigel Tao | 2914bae | 2020-02-26 09:40:30 +1100 | [diff] [blame] | 431 | bool // |
Nigel Tao | 2453a8d | 2021-10-03 13:53:27 +1100 | [diff] [blame] | 432 | load(xcb_connection_t* c, xcb_window_t w, const char* filename) { |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 433 | if (g_pixmap != XCB_NONE) { |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 434 | xcb_render_free_picture(c, g_pixmap_picture); |
Nigel Tao | 1cf0df9 | 2021-10-03 15:30:04 +1100 | [diff] [blame] | 435 | xcb_free_gc(c, g_pixmap_gc); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 436 | xcb_free_pixmap(c, g_pixmap); |
| 437 | } |
| 438 | |
| 439 | if (!load_image(filename)) { |
| 440 | return false; |
| 441 | } |
Nigel Tao | 314d265 | 2021-04-02 23:57:18 +1100 | [diff] [blame] | 442 | wuffs_base__table_u8 tab = g_pixbuf.plane(0); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 443 | |
Nigel Tao | 1cf0df9 | 2021-10-03 15:30:04 +1100 | [diff] [blame] | 444 | xcb_create_pixmap(c, g_pictforminfo->depth, g_pixmap, w, g_width, g_height); |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 445 | xcb_create_gc(c, g_pixmap_gc, g_pixmap, 0, NULL); |
Nigel Tao | 1cf0df9 | 2021-10-03 15:30:04 +1100 | [diff] [blame] | 446 | xcb_render_create_picture(c, g_pixmap_picture, g_pixmap, g_pictforminfo->id, |
| 447 | 0, NULL); |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 448 | apply_zoom_and_filter(c); |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 449 | |
Nigel Tao | 2945ef2 | 2021-10-07 12:35:12 +1100 | [diff] [blame] | 450 | // Copy the pixels from the X11 client process (this process) to the X11 |
| 451 | // server process. For large images, this may involve multiple xcb_image_put |
| 452 | // calls, each copying part of the pixels (a strip that has the same width |
| 453 | // but smaller height), to avoid XCB_CONN_CLOSED_REQ_LEN_EXCEED. |
| 454 | if (g_width > 0) { |
| 455 | uint32_t max_strip_height = g_maximum_request_length / g_width; |
| 456 | for (uint32_t y = 0; y < g_height;) { |
| 457 | uint32_t h = g_height - y; |
| 458 | if (h > max_strip_height) { |
| 459 | h = max_strip_height; |
| 460 | } |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 461 | |
Nigel Tao | 2945ef2 | 2021-10-07 12:35:12 +1100 | [diff] [blame] | 462 | // Make libxcb-image interpret WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL as |
| 463 | // XCB_PICT_STANDARD_ARGB_32 with byte_order XCB_IMAGE_ORDER_LSB_FIRST. |
| 464 | xcb_image_t* unconverted = |
| 465 | xcb_image_create(g_width, // width |
| 466 | h, // height |
| 467 | XCB_IMAGE_FORMAT_Z_PIXMAP, // format |
| 468 | 32, // xpad |
| 469 | g_pictforminfo->depth, // depth |
| 470 | 32, // bpp |
| 471 | 32, // unit |
| 472 | XCB_IMAGE_ORDER_LSB_FIRST, // byte_order |
| 473 | XCB_IMAGE_ORDER_MSB_FIRST, // bit_order |
| 474 | NULL, // base |
| 475 | h * tab.stride, // bytes |
| 476 | tab.ptr + (y * tab.stride)); // data |
| 477 | |
| 478 | xcb_image_t* converted = |
| 479 | xcb_image_native(c, unconverted, true); // true means to convert. |
| 480 | if (converted != unconverted) { |
| 481 | xcb_image_destroy(unconverted); |
| 482 | } |
| 483 | xcb_image_put(c, g_pixmap, g_pixmap_gc, converted, 0, y, 0); |
| 484 | xcb_image_destroy(converted); |
| 485 | |
| 486 | y += h; |
| 487 | } |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 488 | } |
| 489 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 490 | return true; |
| 491 | } |
| 492 | |
Nigel Tao | 5d7b113 | 2022-06-08 17:22:29 +1000 | [diff] [blame] | 493 | bool // |
| 494 | intersects(int32_t old_x, |
| 495 | int32_t old_y, |
| 496 | int32_t width, |
| 497 | int32_t height, |
| 498 | int32_t new_x, |
| 499 | int32_t new_y) { |
| 500 | if ((width <= 0) || (height <= 0)) { |
| 501 | return false; |
| 502 | } else if ((old_x > new_x) && ((old_x - new_x) >= width)) { |
| 503 | return false; |
| 504 | } else if ((new_x > old_x) && ((new_x - old_x) >= width)) { |
| 505 | return false; |
| 506 | } else if ((old_y > new_y) && ((old_y - new_y) >= height)) { |
| 507 | return false; |
| 508 | } else if ((new_y > old_y) && ((new_y - old_y) >= height)) { |
| 509 | return false; |
| 510 | } |
| 511 | return true; |
| 512 | } |
| 513 | |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 514 | // clear_area clears the L-shaped difference between old and new rectangles (of |
| 515 | // equal width and height). It does this in up to two xcb_clear_area calls, |
| 516 | // labeled A and B in the example below (with old_x=0, old_y=0, width=5, |
| 517 | // height=4, new_x=2, new_y=2). |
| 518 | // |
Nigel Tao | 5d7b113 | 2022-06-08 17:22:29 +1000 | [diff] [blame] | 519 | // If the old and new rectangles do not intersect then the old rectangle (not |
| 520 | // an L-shape) will be cleared. |
| 521 | // |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 522 | // AAAAA |
| 523 | // AAAAA |
| 524 | // BB+---+ |
| 525 | // BB| | |
| 526 | // | | |
| 527 | // +---+ |
| 528 | void // |
| 529 | clear_area(xcb_connection_t* c, |
| 530 | xcb_window_t w, |
| 531 | int32_t old_x, |
| 532 | int32_t old_y, |
| 533 | int32_t width, |
| 534 | int32_t height, |
| 535 | int32_t new_x, |
| 536 | int32_t new_y) { |
Nigel Tao | 5d7b113 | 2022-06-08 17:22:29 +1000 | [diff] [blame] | 537 | if ((width <= 0) || (height <= 0)) { |
| 538 | return; |
| 539 | } else if (!intersects(old_x, old_y, width, height, new_x, new_y)) { |
| 540 | xcb_clear_area(c, 1, w, old_x, old_y, width, height); |
| 541 | return; |
| 542 | } |
| 543 | |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 544 | int32_t dy = new_y - old_y; |
| 545 | if (dy < 0) { |
| 546 | xcb_clear_area(c, 1, w, old_x, old_y + height + dy, width, -dy); |
| 547 | } else if (dy > 0) { |
| 548 | xcb_clear_area(c, 1, w, old_x, old_y, width, dy); |
| 549 | } |
| 550 | |
Nigel Tao | 8feb270 | 2022-06-03 11:28:39 +1000 | [diff] [blame] | 551 | int32_t y0 = wuffs_base__i32__max(old_y, new_y); |
| 552 | int32_t y1 = wuffs_base__i32__min(old_y + height, new_y + height); |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 553 | |
| 554 | int32_t dx = new_x - old_x; |
| 555 | if (dx < 0) { |
| 556 | xcb_clear_area(c, 1, w, old_x + width + dx, y0, -dx, y1 - y0); |
| 557 | } else if (dx > 0) { |
| 558 | xcb_clear_area(c, 1, w, old_x, y0, dx, y1 - y0); |
| 559 | } |
| 560 | } |
| 561 | |
Nigel Tao | 2914bae | 2020-02-26 09:40:30 +1100 | [diff] [blame] | 562 | int // |
| 563 | main(int argc, char** argv) { |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 564 | const char* status = parse_flags(argc, argv); |
| 565 | if (status) { |
| 566 | fprintf(stderr, "%s\n", status); |
| 567 | return 1; |
| 568 | } |
| 569 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 570 | xcb_connection_t* c = xcb_connect(NULL, NULL); |
Nigel Tao | 2945ef2 | 2021-10-07 12:35:12 +1100 | [diff] [blame] | 571 | |
| 572 | g_maximum_request_length = xcb_get_maximum_request_length(c); |
| 573 | // Our X11 requests (especially xcb_image_put) also need a header, in terms |
| 574 | // of wire format. 256 4-byte units should be big enough. |
| 575 | const uint32_t max_req_len_adjustment = 256; |
| 576 | if (g_maximum_request_length < max_req_len_adjustment) { |
| 577 | printf("XCB failure (maximum request length is too short)\n"); |
| 578 | exit(EXIT_FAILURE); |
| 579 | } |
| 580 | g_maximum_request_length -= max_req_len_adjustment; |
| 581 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 582 | const xcb_setup_t* z = xcb_get_setup(c); |
| 583 | xcb_screen_t* s = xcb_setup_roots_iterator(z).data; |
Nigel Tao | 1cf0df9 | 2021-10-03 15:30:04 +1100 | [diff] [blame] | 584 | |
| 585 | const xcb_render_query_pict_formats_reply_t* pict_formats = |
| 586 | xcb_render_util_query_formats(c); |
| 587 | g_pictforminfo = xcb_render_util_find_standard_format( |
| 588 | pict_formats, XCB_PICT_STANDARD_ARGB_32); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 589 | |
| 590 | { |
| 591 | xcb_intern_atom_cookie_t cookie0 = |
| 592 | xcb_intern_atom(c, 1, 12, "_NET_WM_NAME"); |
| 593 | xcb_intern_atom_cookie_t cookie1 = xcb_intern_atom(c, 1, 11, "UTF8_STRING"); |
| 594 | xcb_intern_atom_cookie_t cookie2 = |
| 595 | xcb_intern_atom(c, 1, 12, "WM_PROTOCOLS"); |
| 596 | xcb_intern_atom_cookie_t cookie3 = |
| 597 | xcb_intern_atom(c, 1, 16, "WM_DELETE_WINDOW"); |
| 598 | xcb_intern_atom_reply_t* reply0 = xcb_intern_atom_reply(c, cookie0, NULL); |
| 599 | xcb_intern_atom_reply_t* reply1 = xcb_intern_atom_reply(c, cookie1, NULL); |
| 600 | xcb_intern_atom_reply_t* reply2 = xcb_intern_atom_reply(c, cookie2, NULL); |
| 601 | xcb_intern_atom_reply_t* reply3 = xcb_intern_atom_reply(c, cookie3, NULL); |
| 602 | g_atom_net_wm_name = reply0->atom; |
| 603 | g_atom_utf8_string = reply1->atom; |
| 604 | g_atom_wm_protocols = reply2->atom; |
| 605 | g_atom_wm_delete_window = reply3->atom; |
| 606 | free(reply0); |
| 607 | free(reply1); |
| 608 | free(reply2); |
| 609 | free(reply3); |
| 610 | } |
| 611 | |
| 612 | xcb_window_t w = make_window(c, s); |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 613 | xcb_render_picture_t p = xcb_generate_id(c); |
Nigel Tao | 2453a8d | 2021-10-03 13:53:27 +1100 | [diff] [blame] | 614 | xcb_render_create_picture( |
| 615 | c, p, w, |
Nigel Tao | 1cf0df9 | 2021-10-03 15:30:04 +1100 | [diff] [blame] | 616 | xcb_render_util_find_visual_format(pict_formats, s->root_visual)->format, |
Nigel Tao | 2453a8d | 2021-10-03 13:53:27 +1100 | [diff] [blame] | 617 | 0, NULL); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 618 | init_keymap(c, z); |
| 619 | xcb_flush(c); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 620 | |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 621 | g_pixmap = xcb_generate_id(c); |
| 622 | g_pixmap_gc = xcb_generate_id(c); |
| 623 | g_pixmap_picture = xcb_generate_id(c); |
| 624 | |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 625 | int32_t button_x = 0; |
| 626 | int32_t button_y = 0; |
| 627 | |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 628 | bool loaded = load( |
| 629 | c, w, (g_flags.remaining_argc > 0) ? g_flags.remaining_argv[0] : NULL); |
| 630 | int arg = 0; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 631 | |
| 632 | while (true) { |
| 633 | xcb_generic_event_t* event = xcb_wait_for_event(c); |
Přemysl Eric Janouch | a1d9c6a | 2021-09-30 07:31:45 +0200 | [diff] [blame] | 634 | if (!event) { |
Nigel Tao | 2453a8d | 2021-10-03 13:53:27 +1100 | [diff] [blame] | 635 | printf("XCB failure (error code %d)\n", xcb_connection_has_error(c)); |
| 636 | exit(EXIT_FAILURE); |
Přemysl Eric Janouch | a1d9c6a | 2021-09-30 07:31:45 +0200 | [diff] [blame] | 637 | } |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 638 | |
Přemysl Eric Janouch | a1d9c6a | 2021-09-30 07:31:45 +0200 | [diff] [blame] | 639 | bool reload = false; |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 640 | int32_t old_pos_x = g_pos_x; |
| 641 | int32_t old_pos_y = g_pos_y; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 642 | switch (event->response_type & 0x7F) { |
| 643 | case XCB_EXPOSE: { |
Nigel Tao | 731350f | 2020-01-24 11:36:44 +1100 | [diff] [blame] | 644 | xcb_expose_event_t* e = (xcb_expose_event_t*)event; |
| 645 | if (loaded && (e->count == 0)) { |
Přemysl Eric Janouch | 550fabd | 2021-09-30 12:14:51 +0200 | [diff] [blame] | 646 | xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, g_pixmap_picture, |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 647 | XCB_NONE, p, 0, 0, 0, 0, g_pos_x, g_pos_y, |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 648 | zoom_shift(g_width), zoom_shift(g_height)); |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 649 | xcb_flush(c); |
| 650 | } |
| 651 | break; |
| 652 | } |
| 653 | |
| 654 | case XCB_KEY_PRESS: { |
| 655 | xcb_key_press_event_t* e = (xcb_key_press_event_t*)event; |
| 656 | uint32_t i = e->detail; |
| 657 | if ((z->min_keycode <= i) && (i <= z->max_keycode)) { |
| 658 | i = g_keysyms[(i - z->min_keycode) * |
| 659 | g_keyboard_mapping->keysyms_per_keycode]; |
| 660 | switch (i) { |
| 661 | case XK_Escape: |
| 662 | return 0; |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 663 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 664 | case ' ': |
| 665 | case XK_BackSpace: |
| 666 | case XK_Return: |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 667 | if (g_flags.remaining_argc <= 1) { |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 668 | break; |
| 669 | } |
| 670 | arg += (i != XK_BackSpace) ? +1 : -1; |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 671 | if (arg < 0) { |
| 672 | arg = g_flags.remaining_argc - 1; |
| 673 | } else if (arg == g_flags.remaining_argc) { |
| 674 | arg = 0; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 675 | } |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 676 | reload = true; |
| 677 | break; |
| 678 | |
| 679 | case ',': |
| 680 | case '.': |
| 681 | g_background_color_index += |
| 682 | (i == ',') ? (NUM_BACKGROUND_COLORS - 1) : 1; |
| 683 | g_background_color_index %= NUM_BACKGROUND_COLORS; |
| 684 | reload = true; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 685 | break; |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 686 | |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 687 | case '`': |
| 688 | g_pos_x = 0; |
| 689 | g_pos_y = 0; |
| 690 | break; |
| 691 | |
| 692 | case XK_Left: |
| 693 | case 'h': |
| 694 | g_pos_x += calculate_delta(e->state); |
| 695 | break; |
| 696 | |
| 697 | case XK_Down: |
| 698 | case 'j': |
| 699 | g_pos_y -= calculate_delta(e->state); |
| 700 | break; |
| 701 | |
| 702 | case XK_Up: |
| 703 | case 'k': |
| 704 | g_pos_y += calculate_delta(e->state); |
| 705 | break; |
| 706 | |
| 707 | case XK_Right: |
| 708 | case 'l': |
| 709 | g_pos_x -= calculate_delta(e->state); |
| 710 | break; |
| 711 | |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 712 | case '0': |
| 713 | case '1': |
| 714 | case '2': |
| 715 | case '3': |
| 716 | case '4': |
| 717 | case '5': |
| 718 | case '6': |
| 719 | case '7': |
| 720 | case '8': |
| 721 | if (i == '0') { |
| 722 | g_filter = !g_filter; |
| 723 | } else { |
Nigel Tao | 90d3232 | 2021-10-07 12:59:38 +1100 | [diff] [blame] | 724 | int32_t z = i - '1'; |
| 725 | if (e->state & XCB_MOD_MASK_SHIFT) { |
| 726 | z = -z; |
| 727 | } |
| 728 | if (g_zoom == z) { |
| 729 | break; |
| 730 | } |
| 731 | g_zoom = z; |
Nigel Tao | 8eaa222 | 2021-10-03 16:42:58 +1100 | [diff] [blame] | 732 | } |
| 733 | apply_zoom_and_filter(c); |
| 734 | xcb_clear_area(c, 1, w, 0, 0, 0xFFFF, 0xFFFF); |
| 735 | xcb_flush(c); |
| 736 | break; |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 737 | } |
| 738 | } |
| 739 | break; |
| 740 | } |
| 741 | |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 742 | case XCB_BUTTON_PRESS: { |
| 743 | xcb_button_press_event_t* e = (xcb_button_press_event_t*)event; |
| 744 | switch (e->detail) { |
| 745 | case 1: |
| 746 | button_x = e->event_x; |
| 747 | button_y = e->event_y; |
| 748 | break; |
| 749 | case 4: |
| 750 | g_pos_y += calculate_delta(e->state); |
| 751 | break; |
| 752 | case 5: |
| 753 | g_pos_y -= calculate_delta(e->state); |
| 754 | break; |
| 755 | case 6: |
| 756 | g_pos_x += calculate_delta(e->state); |
| 757 | break; |
| 758 | case 7: |
| 759 | g_pos_x -= calculate_delta(e->state); |
| 760 | break; |
| 761 | } |
| 762 | break; |
| 763 | } |
| 764 | |
| 765 | case XCB_MOTION_NOTIFY: { |
| 766 | xcb_motion_notify_event_t* e = (xcb_motion_notify_event_t*)event; |
| 767 | g_pos_x += e->event_x - button_x; |
| 768 | g_pos_y += e->event_y - button_y; |
| 769 | button_x = e->event_x; |
| 770 | button_y = e->event_y; |
| 771 | break; |
| 772 | } |
| 773 | |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 774 | case XCB_CLIENT_MESSAGE: { |
| 775 | xcb_client_message_event_t* e = (xcb_client_message_event_t*)event; |
| 776 | if (e->data.data32[0] == g_atom_wm_delete_window) { |
| 777 | return 0; |
| 778 | } |
| 779 | break; |
| 780 | } |
| 781 | } |
| 782 | |
| 783 | free(event); |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 784 | |
| 785 | if (reload) { |
Nigel Tao | 15b6285 | 2021-10-20 15:36:19 +1100 | [diff] [blame] | 786 | loaded = load(c, w, g_flags.remaining_argv[arg]); |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 787 | xcb_clear_area(c, 1, w, 0, 0, 0xFFFF, 0xFFFF); |
| 788 | xcb_flush(c); |
Nigel Tao | 4c5d313 | 2022-05-31 12:25:08 +1000 | [diff] [blame] | 789 | } else if (loaded && // |
| 790 | ((old_pos_x != g_pos_x) || (old_pos_y != g_pos_y))) { |
| 791 | clear_area(c, w, old_pos_x, old_pos_y, zoom_shift(g_width), |
| 792 | zoom_shift(g_height), g_pos_x, g_pos_y); |
| 793 | xcb_render_composite(c, XCB_RENDER_PICT_OP_SRC, g_pixmap_picture, |
| 794 | XCB_NONE, p, 0, 0, 0, 0, g_pos_x, g_pos_y, |
| 795 | zoom_shift(g_width), zoom_shift(g_height)); |
| 796 | xcb_flush(c); |
Nigel Tao | d595980 | 2020-01-24 11:51:54 +1100 | [diff] [blame] | 797 | } |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 798 | } |
| 799 | return 0; |
| 800 | } |
| 801 | |
| 802 | #endif // defined(__linux__) |
| 803 | |
Nigel Tao | f45f01f | 2020-06-05 14:06:56 +1000 | [diff] [blame] | 804 | // --------------------------------------------------------------------- |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 805 | |
| 806 | #if !defined(SUPPORTED_OPERATING_SYSTEM) |
| 807 | |
Nigel Tao | 2914bae | 2020-02-26 09:40:30 +1100 | [diff] [blame] | 808 | int // |
| 809 | main(int argc, char** argv) { |
Nigel Tao | 8f1fe43 | 2020-01-15 14:22:48 +1100 | [diff] [blame] | 810 | printf("unsupported operating system\n"); |
| 811 | return 1; |
| 812 | } |
| 813 | |
| 814 | #endif // !defined(SUPPORTED_OPERATING_SYSTEM) |