├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── raspi2fb.c ├── raspi2fb.init.d ├── raspi2fb@.service ├── syslogUtilities.c └── syslogUtilities.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Libraries 12 | *.lib 13 | *.a 14 | *.la 15 | *.lo 16 | 17 | # Shared objects (inc. Windows DLLs) 18 | *.dll 19 | *.so 20 | *.so.* 21 | *.dylib 22 | 23 | # Executables 24 | *.exe 25 | *.out 26 | *.app 27 | *.i*86 28 | *.x86_64 29 | *.hex 30 | 31 | build 32 | raspi2fb 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(raspi2fb) 3 | 4 | set(CMAKE_BUILD_TYPE Release) 5 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") 6 | 7 | set(BCM_HOST_INCLUDE_DIRS /opt/vc/include) 8 | set(BCM_HOST_LIBRARY_DIRS /opt/vc/lib) 9 | set(BCM_HOST_LIBRARIES bcm_host) 10 | 11 | find_package(PkgConfig) 12 | pkg_check_modules(LIBBSD libbsd) 13 | 14 | include_directories(${BCM_HOST_INCLUDE_DIRS} ${LIBBSD_INCLUDE_DIRS}) 15 | 16 | link_directories(${BCM_HOST_LIBRARY_DIRS} ${LIBBSD_LIBRARY_DIRS}) 17 | 18 | add_executable(${PROJECT_NAME} raspi2fb.c syslogUtilities.c) 19 | 20 | target_link_libraries(${PROJECT_NAME} ${BCM_HOST_LIBRARIES} ${LIBBSD_LIBRARIES}) 21 | 22 | set_property(TARGET ${PROJECT_NAME} PROPERTY SKIP_BUILD_RPATH TRUE) 23 | install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Duncan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repository will no longer be updated. The DispmanX API is no longer supported in recent version of the Raspberry Pi OS. 2 | 3 | # raspi2fb 4 | Program to copy the Raspberry Pi display to a secondary framebuffer. 5 | # usage 6 | 7 | raspi2fb 8 | 9 | --daemon - start in the background as a daemon 10 | --device - framebuffer device (default /dev/fb1) 11 | --display - Raspberry Pi display number (default 0) 12 | --fps - set desired frames per second (default 10 frames per second) 13 | --copyrect - copy only a rectangle the same size as the dest framebuffer 14 | --rectx - copy rectangle from source fb at in copyrect mode (default 0) 15 | --recty - copy rectangle from source fb at in copyrect mode (default 0) 16 | --pidfile - create and lock PID file (if being run as a daemon) 17 | --once - copy only one time, then exit 18 | --help - print usage and exit 19 | 20 | # build prerequisites 21 | ## cmake 22 | You will need to install cmake 23 | 24 | sudo apt-get install cmake 25 | ## libraries 26 | You will need to install libbsd-dev 27 | 28 | sudo apt-get install libbsd-dev 29 | # build 30 | mkdir build 31 | cd build 32 | cmake .. 33 | make 34 | # install 35 | ## Raspian Wheezy 36 | sudo make install 37 | sudo cp ../raspi2fb.init.d /etc/init.d/raspi2fb 38 | sudo update-rc.d raspi2fb defaults 39 | sudo service raspi2fb start 40 | ## Raspian Jessie 41 | sudo make install 42 | sudo cp ../raspi2fb@.service /etc/systemd/system/ 43 | sudo systemctl daemon-reload 44 | sudo systemctl enable raspi2fb@1.service 45 | sudo systemctl start raspi2fb@1 46 | # uninstall 47 | ## Raspian Wheezy 48 | sudo service raspi2fb stop 49 | sudo update-rc.d -f raspi2fb remove 50 | sudo rm /usr/local/bin/raspi2fb 51 | sudo rm /etc/init.d/raspi2fb 52 | ## Raspian Jessie 53 | sudo systemctl stop raspi2fb@1 54 | sudo systemctl disable raspi2fb@1.service 55 | sudo rm /etc/systemd/system/raspi2fb@.service 56 | sudo rm /usr/local/bin/raspi2fb 57 | -------------------------------------------------------------------------------- /raspi2fb.c: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------- 2 | // 3 | // The MIT License (MIT) 4 | // 5 | // Copyright (c) 2015 Andrew Duncan 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a 8 | // copy of this software and associated documentation files (the 9 | // "Software"), to deal in the Software without restriction, including 10 | // without limitation the rights to use, copy, modify, merge, publish, 11 | // distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to 13 | // the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | //------------------------------------------------------------------------- 27 | 28 | #define _GNU_SOURCE 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | 45 | #include 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | #pragma GCC diagnostic push 52 | #pragma GCC diagnostic ignored "-Wunused-but-set-variable" 53 | #include "bcm_host.h" 54 | #pragma GCC diagnostic pop 55 | 56 | #include "syslogUtilities.h" 57 | 58 | //------------------------------------------------------------------------- 59 | 60 | #define DEFAULT_DEVICE "/dev/fb1" 61 | #define DEFAULT_DISPLAY_NUMBER 0 62 | #define DEFAULT_FPS 10 63 | 64 | //------------------------------------------------------------------------- 65 | 66 | volatile bool run = true; 67 | 68 | //------------------------------------------------------------------------- 69 | 70 | void 71 | printUsage( 72 | FILE *fp, 73 | const char *name) 74 | { 75 | fprintf(fp, "\n"); 76 | fprintf(fp, "Usage: %s \n", name); 77 | fprintf(fp, "\n"); 78 | fprintf(fp, " --daemon - start in the background as a daemon\n"); 79 | fprintf(fp, " --device - framebuffer device"); 80 | fprintf(fp, " (default %s)\n", DEFAULT_DEVICE); 81 | fprintf(fp, " --display - Raspberry Pi display number"); 82 | fprintf(fp, " (default %d)\n", DEFAULT_DISPLAY_NUMBER); 83 | fprintf(fp, " --fps - set desired frames per second"); 84 | fprintf(fp, " (default %d frames per second)\n", DEFAULT_FPS); 85 | fprintf(fp, " --copyrect - copy only a rectangle the same size as the dest framebuffer\n"); 86 | fprintf(fp, " --rectx - copy rectangle from source fb at in copyrect mode"); 87 | fprintf(fp, " (default 0)\n"); 88 | fprintf(fp, " --recty - copy rectangle from source fb at in copyrect mode"); 89 | fprintf(fp, " (default 0)\n"); 90 | fprintf(fp, " --pidfile - create and lock PID file"); 91 | fprintf(fp, " (if being run as a daemon)\n"); 92 | fprintf(fp, " --once - copy only one time, then exit\n"); 93 | fprintf(fp, " --help - print usage and exit\n"); 94 | fprintf(fp, "\n"); 95 | } 96 | 97 | //------------------------------------------------------------------------- 98 | 99 | static void 100 | signalHandler( 101 | int signalNumber) 102 | { 103 | switch (signalNumber) 104 | { 105 | case SIGINT: 106 | case SIGTERM: 107 | 108 | run = false; 109 | break; 110 | }; 111 | } 112 | 113 | //------------------------------------------------------------------------- 114 | 115 | int 116 | main( 117 | int argc, 118 | char *argv[]) 119 | { 120 | const char *program = basename(argv[0]); 121 | 122 | int fps = DEFAULT_FPS; 123 | suseconds_t frameDuration = 1000000 / fps; 124 | bool isDaemon = false; 125 | bool copyRect = false; 126 | bool once = false; 127 | uint16_t copyRectX = 0; 128 | uint16_t copyRectY = 0; 129 | uint32_t displayNumber = DEFAULT_DISPLAY_NUMBER; 130 | const char *pidfile = NULL; 131 | const char *device = DEFAULT_DEVICE; 132 | 133 | //--------------------------------------------------------------------- 134 | 135 | static const char *sopts = "df:hn:p:D:"; 136 | static struct option lopts[] = 137 | { 138 | { "daemon", no_argument, NULL, 'd' }, 139 | { "fps", required_argument, NULL, 'f' }, 140 | { "help", no_argument, NULL, 'h' }, 141 | { "display", required_argument, NULL, 'n' }, 142 | { "pidfile", required_argument, NULL, 'p' }, 143 | { "device", required_argument, NULL, 'D' }, 144 | { "copyrect", no_argument, NULL, 'r' }, 145 | { "rectx", required_argument, NULL, 'x' }, 146 | { "recty", required_argument, NULL, 'y' }, 147 | { "once", no_argument, NULL, 'o' }, 148 | { NULL, no_argument, NULL, 0 } 149 | }; 150 | 151 | int opt = 0; 152 | 153 | while ((opt = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) 154 | { 155 | switch (opt) 156 | { 157 | case 'x': 158 | copyRectX = atoi(optarg); 159 | break; 160 | 161 | case 'y': 162 | copyRectY = atoi(optarg); 163 | break; 164 | 165 | case 'r': 166 | copyRect = true; 167 | break; 168 | 169 | case 'd': 170 | 171 | isDaemon = true; 172 | break; 173 | 174 | case 'f': 175 | 176 | fps = atoi(optarg); 177 | 178 | if (fps > 0) 179 | { 180 | frameDuration = 1000000 / fps; 181 | } 182 | else 183 | { 184 | fps = 1000000 / frameDuration; 185 | } 186 | 187 | break; 188 | 189 | case 'h': 190 | 191 | printUsage(stdout, program); 192 | exit(EXIT_SUCCESS); 193 | 194 | break; 195 | 196 | case 'n': 197 | 198 | displayNumber = atoi(optarg); 199 | 200 | break; 201 | 202 | case 'p': 203 | 204 | pidfile = optarg; 205 | 206 | break; 207 | 208 | case 'o': 209 | 210 | once = true; 211 | break; 212 | 213 | case 'D': 214 | 215 | device = optarg; 216 | 217 | break; 218 | 219 | default: 220 | 221 | printUsage(stderr, program); 222 | exit(EXIT_FAILURE); 223 | 224 | break; 225 | } 226 | } 227 | 228 | //--------------------------------------------------------------------- 229 | 230 | struct pidfh *pfh = NULL; 231 | 232 | if (isDaemon) 233 | { 234 | if (pidfile != NULL) 235 | { 236 | pid_t otherpid; 237 | pfh = pidfile_open(pidfile, 0600, &otherpid); 238 | 239 | if (pfh == NULL) 240 | { 241 | fprintf(stderr, 242 | "%s is already running %jd\n", 243 | program, 244 | (intmax_t)otherpid); 245 | exit(EXIT_FAILURE); 246 | } 247 | } 248 | 249 | if (daemon(0, 0) == -1) 250 | { 251 | fprintf(stderr, "Cannot daemonize\n"); 252 | 253 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 254 | } 255 | 256 | if (pfh) 257 | { 258 | pidfile_write(pfh); 259 | } 260 | 261 | openlog(program, LOG_PID, LOG_USER); 262 | } 263 | 264 | //--------------------------------------------------------------------- 265 | 266 | if (signal(SIGINT, signalHandler) == SIG_ERR) 267 | { 268 | perrorLog(isDaemon, program, "installing SIGINT signal handler"); 269 | 270 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 271 | } 272 | 273 | //--------------------------------------------------------------------- 274 | 275 | if (signal(SIGTERM, signalHandler) == SIG_ERR) 276 | { 277 | perrorLog(isDaemon, program, "installing SIGTERM signal handler"); 278 | 279 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 280 | } 281 | 282 | //--------------------------------------------------------------------- 283 | 284 | bcm_host_init(); 285 | 286 | DISPMANX_DISPLAY_HANDLE_T display 287 | = vc_dispmanx_display_open(displayNumber); 288 | 289 | if (display == 0) 290 | { 291 | messageLog(isDaemon, program, LOG_ERR, "cannot open display"); 292 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 293 | } 294 | 295 | DISPMANX_MODEINFO_T info; 296 | 297 | if (vc_dispmanx_display_get_info(display, &info) != 0) 298 | { 299 | messageLog(isDaemon, 300 | program, 301 | LOG_ERR, 302 | "cannot get display dimensions"); 303 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 304 | } 305 | 306 | //--------------------------------------------------------------------- 307 | 308 | int fbfd = open(device, O_RDWR); 309 | 310 | if (fbfd == -1) 311 | { 312 | perrorLog(isDaemon, program, "cannot open framebuffer device"); 313 | 314 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 315 | } 316 | 317 | struct fb_fix_screeninfo finfo; 318 | 319 | if (ioctl(fbfd, FBIOGET_FSCREENINFO, &finfo) == -1) 320 | { 321 | perrorLog(isDaemon, 322 | program, 323 | "cannot get framebuffer fixed information"); 324 | 325 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 326 | } 327 | 328 | struct fb_var_screeninfo vinfo; 329 | 330 | if (ioctl(fbfd, FBIOGET_VSCREENINFO, &vinfo) == -1) 331 | { 332 | perrorLog(isDaemon, 333 | program, 334 | "cannot get framebuffer variable information"); 335 | 336 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 337 | } 338 | 339 | //--------------------------------------------------------------------- 340 | 341 | if ((vinfo.xres * 2) != finfo.line_length) 342 | { 343 | perrorLog(isDaemon, 344 | program, 345 | "assumption failed ... framebuffer lines are padded"); 346 | 347 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 348 | } 349 | 350 | if ((vinfo.xres % 16) != 0) 351 | { 352 | perrorLog(isDaemon, 353 | program, 354 | "framebuffer width must be a multiple of 16"); 355 | 356 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 357 | } 358 | 359 | if (vinfo.bits_per_pixel != 16) 360 | { 361 | perrorLog(isDaemon, 362 | program, 363 | "framebuffer is not 16 bits per pixel"); 364 | 365 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 366 | } 367 | 368 | 369 | if (copyRectX > (info.width - vinfo.xres)) 370 | { 371 | char s[80]; 372 | snprintf(s, 80, "rectx must be between 0 and %d for the configured framebuffers", info.width - vinfo.xres); 373 | perrorLog(isDaemon, 374 | program, 375 | s); 376 | 377 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 378 | } 379 | 380 | if (copyRectY > (info.height - vinfo.yres)) 381 | { 382 | char s[80]; 383 | snprintf(s, 80, "recty must be between 0 and %d for the configured framebuffers", info.height - vinfo.yres); 384 | perrorLog(isDaemon, 385 | program, 386 | s); 387 | 388 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 389 | } 390 | 391 | //--------------------------------------------------------------------- 392 | 393 | uint16_t *fbp = mmap(0, 394 | finfo.smem_len, 395 | PROT_READ | PROT_WRITE, 396 | MAP_SHARED, 397 | fbfd, 398 | 0); 399 | 400 | if (fbp == MAP_FAILED) 401 | { 402 | perrorLog(isDaemon, program, "cannot map framebuffer into memory"); 403 | 404 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 405 | } 406 | 407 | memset(fbp, 0, finfo.smem_len); 408 | 409 | //--------------------------------------------------------------------- 410 | 411 | uint32_t image_ptr; 412 | 413 | DISPMANX_RESOURCE_HANDLE_T resourceHandle; 414 | VC_RECT_T rect; 415 | 416 | if (copyRect) { 417 | resourceHandle = vc_dispmanx_resource_create(VC_IMAGE_RGB565, 418 | info.width, 419 | info.height, 420 | &image_ptr); 421 | vc_dispmanx_rect_set(&rect, 0, 0, info.width, info.height); 422 | } else { 423 | resourceHandle = vc_dispmanx_resource_create(VC_IMAGE_RGB565, 424 | vinfo.xres, 425 | vinfo.yres, 426 | &image_ptr); 427 | vc_dispmanx_rect_set(&rect, 0, 0, vinfo.xres, vinfo.yres); 428 | } 429 | 430 | //--------------------------------------------------------------------- 431 | 432 | uint32_t len = copyRect ? (info.width * info.height * 2) : finfo.smem_len; 433 | 434 | uint16_t *backCopyP = malloc(len); 435 | uint16_t *frontCopyP = malloc(len); 436 | 437 | uint32_t line_len = copyRect ? (info.width * 2) : finfo.line_length; 438 | 439 | if ((backCopyP == NULL) || (frontCopyP == NULL)) 440 | { 441 | perrorLog(isDaemon, program, "cannot allocate offscreen buffers"); 442 | 443 | exitAndRemovePidFile(EXIT_FAILURE, pfh); 444 | } 445 | 446 | memset(backCopyP, 0, copyRect ? (line_len * info.height) : finfo.line_length * vinfo.yres); 447 | 448 | //--------------------------------------------------------------------- 449 | 450 | if (copyRect) 451 | { 452 | messageLog(isDaemon, 453 | program, 454 | LOG_INFO, 455 | "raspi2fb copyrect mode, copying window from source fb[%dx%d @ %d,%d] to dest fb[%dx%d]", 456 | info.width, 457 | info.height, 458 | copyRectX, 459 | copyRectY, 460 | vinfo.xres, 461 | vinfo.yres); 462 | } 463 | else 464 | { 465 | messageLog(isDaemon, 466 | program, 467 | LOG_INFO, 468 | "raspi2fb normal scaling mode, copying from source fb[%dx%d] to dest fb [%dx%d]", 469 | info.width, 470 | info.height, 471 | vinfo.xres, 472 | vinfo.yres); 473 | } 474 | 475 | //--------------------------------------------------------------------- 476 | 477 | struct timeval start_time; 478 | struct timeval end_time; 479 | struct timeval elapsed_time; 480 | 481 | //--------------------------------------------------------------------- 482 | 483 | // pixels = count of destination framebuffer pixels 484 | uint32_t pixels = vinfo.xres * vinfo.yres; 485 | 486 | while (run) 487 | { 488 | gettimeofday(&start_time, NULL); 489 | 490 | //----------------------------------------------------------------- 491 | 492 | vc_dispmanx_snapshot(display, resourceHandle, 0); 493 | 494 | vc_dispmanx_resource_read_data(resourceHandle, 495 | &rect, 496 | frontCopyP, 497 | line_len); 498 | 499 | if (copyRect) 500 | { 501 | // rectangle copying mode - eliminated double buffering, not sure why it is done in 'normal' mode 502 | for (uint16_t pixel_y = 0 ; pixel_y < vinfo.yres ; pixel_y++) 503 | { 504 | uint16_t* rowIter = frontCopyP + ((pixel_y + copyRectY) * info.width) + copyRectX; 505 | uint16_t* fbIter = fbp + (pixel_y * vinfo.xres); 506 | 507 | for (uint16_t pixel_x = 0 ; pixel_x < vinfo.xres ; pixel_x++) 508 | { 509 | *(fbIter++) = *(rowIter++); 510 | } 511 | } 512 | } 513 | else 514 | { 515 | // normal scaled copy mode 516 | uint16_t *fbIter = fbp; 517 | uint16_t *frontCopyIter = frontCopyP; 518 | uint16_t *backCopyIter = backCopyP; 519 | 520 | uint32_t pixel; 521 | for (pixel = 0 ; pixel < pixels ; pixel++) 522 | { 523 | if (*frontCopyIter != *backCopyIter) 524 | { 525 | *fbIter = *frontCopyIter; 526 | } 527 | 528 | ++frontCopyIter; 529 | ++backCopyIter; 530 | ++fbIter; 531 | } 532 | 533 | uint16_t *tmp = backCopyP; 534 | backCopyP = frontCopyP; 535 | frontCopyP = tmp; 536 | } 537 | 538 | //----------------------------------------------------------------- 539 | 540 | if (once) 541 | { 542 | messageLog(isDaemon, 543 | program, 544 | LOG_INFO, 545 | "ran once, exiting now"); 546 | break; 547 | } 548 | 549 | gettimeofday(&end_time, NULL); 550 | timersub(&end_time, &start_time, &elapsed_time); 551 | 552 | if (elapsed_time.tv_sec == 0) 553 | { 554 | if (elapsed_time.tv_usec < frameDuration) 555 | { 556 | usleep(frameDuration - elapsed_time.tv_usec); 557 | } 558 | } 559 | } 560 | 561 | //--------------------------------------------------------------------- 562 | 563 | free(frontCopyP); 564 | free(backCopyP); 565 | 566 | memset(fbp, 0, finfo.smem_len); 567 | 568 | munmap(fbp, finfo.smem_len); 569 | close(fbfd); 570 | 571 | //--------------------------------------------------------------------- 572 | 573 | vc_dispmanx_resource_delete(resourceHandle); 574 | vc_dispmanx_display_close(display); 575 | 576 | //--------------------------------------------------------------------- 577 | 578 | messageLog(isDaemon, program, LOG_INFO, "exiting"); 579 | 580 | if (isDaemon) 581 | { 582 | closelog(); 583 | } 584 | 585 | if (pfh) 586 | { 587 | pidfile_remove(pfh); 588 | } 589 | 590 | //--------------------------------------------------------------------- 591 | 592 | return 0 ; 593 | } 594 | -------------------------------------------------------------------------------- /raspi2fb.init.d: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: raspi2fb 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: System information daemon (mztx LCD) 9 | # Description: System information daemon (mztx LCD) 10 | ### END INIT INFO 11 | 12 | # Author: Andrew Duncan 13 | # 14 | 15 | # Do NOT "set -e" 16 | 17 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 18 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 19 | DESC="Copy Raspberry Pi screen to a secondary framebuffer" 20 | NAME=raspi2fb 21 | DAEMON=/usr/local/bin/$NAME 22 | PIDFILE=/var/run/$NAME.pid 23 | DAEMON_ARGS="--daemon --pidfile $PIDFILE" 24 | SCRIPTNAME=/etc/init.d/$NAME 25 | 26 | # Exit if the package is not installed 27 | [ -x "$DAEMON" ] || exit 0 28 | 29 | # Read configuration variable file if it is present 30 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 31 | 32 | # Load the VERBOSE setting and other rcS variables 33 | . /lib/init/vars.sh 34 | 35 | # Define LSB log_* functions. 36 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present 37 | # and status_of_proc is working. 38 | . /lib/lsb/init-functions 39 | 40 | # 41 | # Function that starts the daemon/service 42 | # 43 | do_start() 44 | { 45 | # Return 46 | # 0 if daemon has been started 47 | # 1 if daemon was already running 48 | # 2 if daemon could not be started 49 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ 50 | || return 1 51 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ 52 | $DAEMON_ARGS \ 53 | || return 2 54 | # Add code here, if necessary, that waits for the process to be ready 55 | # to handle requests from services started subsequently which depend 56 | # on this one. As a last resort, sleep for some time. 57 | } 58 | 59 | # 60 | # Function that stops the daemon/service 61 | # 62 | do_stop() 63 | { 64 | # Return 65 | # 0 if daemon has been stopped 66 | # 1 if daemon was already stopped 67 | # 2 if daemon could not be stopped 68 | # other if a failure occurred 69 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE 70 | RETVAL="$?" 71 | [ "$RETVAL" = 2 ] && return 2 72 | # Wait for children to finish too if this is a daemon that forks 73 | # and if the daemon is only ever run from this initscript. 74 | # If the above conditions are not satisfied then add some other code 75 | # that waits for the process to drop all resources that could be 76 | # needed by services started subsequently. A last resort is to 77 | # sleep for some time. 78 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 79 | [ "$?" = 2 ] && return 2 80 | # Many daemons don't delete their pidfiles when they exit. 81 | rm -f $PIDFILE 82 | return "$RETVAL" 83 | } 84 | 85 | # 86 | # Function that sends a SIGHUP to the daemon/service 87 | # 88 | do_reload() { 89 | # 90 | # If the daemon can reload its configuration without 91 | # restarting (for example, when it is sent a SIGHUP), 92 | # then implement that here. 93 | # 94 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE 95 | return 0 96 | } 97 | 98 | case "$1" in 99 | start) 100 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 101 | do_start 102 | case "$?" in 103 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 104 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 105 | esac 106 | ;; 107 | stop) 108 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 109 | do_stop 110 | case "$?" in 111 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 112 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 113 | esac 114 | ;; 115 | status) 116 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 117 | ;; 118 | #reload|force-reload) 119 | # 120 | # If do_reload() is not implemented then leave this commented out 121 | # and leave 'force-reload' as an alias for 'restart'. 122 | # 123 | #log_daemon_msg "Reloading $DESC" "$NAME" 124 | #do_reload 125 | #log_end_msg $? 126 | #;; 127 | restart|force-reload) 128 | # 129 | # If the "reload" option is implemented then remove the 130 | # 'force-reload' alias 131 | # 132 | log_daemon_msg "Restarting $DESC" "$NAME" 133 | do_stop 134 | case "$?" in 135 | 0|1) 136 | do_start 137 | case "$?" in 138 | 0) log_end_msg 0 ;; 139 | 1) log_end_msg 1 ;; # Old process is still running 140 | *) log_end_msg 1 ;; # Failed to start 141 | esac 142 | ;; 143 | *) 144 | # Failed to stop 145 | log_end_msg 1 146 | ;; 147 | esac 148 | ;; 149 | *) 150 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 151 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 152 | exit 3 153 | ;; 154 | esac 155 | 156 | : 157 | -------------------------------------------------------------------------------- /raspi2fb@.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Copy Raspberry Pi screen to a secondary framebuffer 3 | 4 | [Service] 5 | Type=forking 6 | PIDFile=/var/run/raspi2fb%i.pid 7 | ExecStart=/usr/local/bin/raspi2fb --daemon --device /dev/fb%i --pidfile /var/run/raspi2fb%i.pid 8 | Restart=on-failure 9 | RestartSec=5 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /syslogUtilities.c: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------- 2 | // 3 | // The MIT License (MIT) 4 | // 5 | // Copyright (c) 2015 Andrew Duncan 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a 8 | // copy of this software and associated documentation files (the 9 | // "Software"), to deal in the Software without restriction, including 10 | // without limitation the rights to use, copy, modify, merge, publish, 11 | // distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to 13 | // the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | //------------------------------------------------------------------------- 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include "syslogUtilities.h" 38 | 39 | //------------------------------------------------------------------------- 40 | 41 | void 42 | messageLog( 43 | bool isDaemon, 44 | const char *name, 45 | int priority, 46 | const char *format, 47 | ...) 48 | { 49 | va_list args; 50 | va_start(args, format); 51 | 52 | if (isDaemon) 53 | { 54 | vsyslog(LOG_MAKEPRI(LOG_USER, priority), format, args); 55 | } 56 | else 57 | { 58 | fprintf(stderr, "%s[%d]:", name, getpid()); 59 | 60 | switch (priority) 61 | { 62 | case LOG_DEBUG: 63 | 64 | fprintf(stderr, "debug"); 65 | break; 66 | 67 | case LOG_INFO: 68 | 69 | fprintf(stderr, "info"); 70 | break; 71 | 72 | case LOG_NOTICE: 73 | 74 | fprintf(stderr, "notice"); 75 | break; 76 | 77 | case LOG_WARNING: 78 | 79 | fprintf(stderr, "warning"); 80 | break; 81 | 82 | case LOG_ERR: 83 | 84 | fprintf(stderr, "error"); 85 | break; 86 | 87 | default: 88 | 89 | fprintf(stderr, "unknown(%d)", priority); 90 | break; 91 | } 92 | 93 | fprintf(stderr, ":"); 94 | 95 | vfprintf(stderr, format, args); 96 | 97 | fprintf(stderr, "\n"); 98 | } 99 | 100 | va_end(args); 101 | } 102 | 103 | //------------------------------------------------------------------------- 104 | 105 | void 106 | perrorLog( 107 | bool isDaemon, 108 | const char *name, 109 | const char *s) 110 | { 111 | messageLog(isDaemon, 112 | name, 113 | LOG_ERR, 114 | "%s - %s", 115 | s, 116 | strerror(errno)); 117 | } 118 | 119 | //------------------------------------------------------------------------- 120 | 121 | void 122 | exitAndRemovePidFile( 123 | int status, 124 | struct pidfh *pfh) 125 | { 126 | if (pfh) 127 | { 128 | pidfile_remove(pfh); 129 | } 130 | 131 | exit(status); 132 | } 133 | -------------------------------------------------------------------------------- /syslogUtilities.h: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------- 2 | // 3 | // The MIT License (MIT) 4 | // 5 | // Copyright (c) 2015 Andrew Duncan 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a 8 | // copy of this software and associated documentation files (the 9 | // "Software"), to deal in the Software without restriction, including 10 | // without limitation the rights to use, copy, modify, merge, publish, 11 | // distribute, sublicense, and/or sell copies of the Software, and to 12 | // permit persons to whom the Software is furnished to do so, subject to 13 | // the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included 16 | // in all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | // 26 | //------------------------------------------------------------------------- 27 | 28 | #ifndef SYSLOG_UTILITIES_H 29 | #define SYSLOG_UTILITIES_H 30 | 31 | //------------------------------------------------------------------------- 32 | 33 | #include 34 | 35 | #include 36 | 37 | //------------------------------------------------------------------------- 38 | 39 | void 40 | messageLog( 41 | bool isDaemon, 42 | const char *name, 43 | int priority, 44 | const char *format, 45 | ...); 46 | 47 | void 48 | perrorLog( 49 | bool isDaemon, 50 | const char *name, 51 | const char *s); 52 | 53 | void 54 | exitAndRemovePidFile( 55 | int status, 56 | struct pidfh *pfh); 57 | 58 | //------------------------------------------------------------------------- 59 | 60 | #endif 61 | --------------------------------------------------------------------------------