Skip to content

Commit

Permalink
Initial version.
Browse files Browse the repository at this point in the history
  • Loading branch information
bgaillard committed Dec 8, 2023
0 parents commit 7aa9c04
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 0 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/mclip.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
name: mclip
on: push
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build
run: make build

- name: Upload
uses: actions/upload-artifact@v3
with:
name: mclip
path: build/mclip

- name: Release
uses: cycjimmy/semantic-release-action@v4
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
55 changes: 55 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Prerequisites
*.d

# Object files
*.o
*.ko
*.obj
*.elf

# Linker output
*.ilk
*.map
*.exp

# Precompiled Headers
*.gch
*.pch

# Libraries
*.lib
*.a
*.la
*.lo

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex

# Debug files
*.dSYM/
*.su
*.idb
*.pdb

# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf

# Build dirs
build/*
11 changes: 11 additions & 0 deletions .releaserc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
branches:
- main
plugins:
- "@semantic-release/commit-analyzer"
- "@semantic-release/release-notes-generator"
- "@semantic-release/github"
- "@semantic-release/changelog"
tagFormat: ${version}
dryRun: true
ci: true
13 changes: 13 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CC=gcc

all: mclip

clean:
rm -rf build

build: main.c
mkdir -p build
$(CC) -o build/mclip main.c

install: build
cp build/mclip /usr/bin
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# :clipboard: mclip

mclip (for "**M**emory **Clip**board") is a very simple Linux clipboard manager which stores its data inside a POSIX shared memory object.

Today several clipboad tools exist but almost all of them require a specific dependency (X11, Wayland, Tmux, SSH, etc.). See for example the [Clipboard integration](https://neovim.io/doc/user/provider.html#provider-clipboard) page in the Neovim documentation.

mclip is a tiny binary which only depends on low level Linux system functions like [`shm_open`](https://man7.org/linux/man-pages/man3/shm_open.3.html) and [`mmap`](https://man7.org/linux/man-pages/man2/mmap.2.html). So it should be usable in very basic environments.

## Install

## Usage

This section explains how to use mclip in several tools.

- [CLI](#cli)
- [Neovim](#neovim)
- [Zellij](#zellij)

### CLI

Display the help.

```bash
clip --help
Usage: mclip [OPTION]

Clipboard which stores its data inside shared memory.

h-, --help read text from standard input and write it into the clipboard shared memory (default).
i-, --in read text from standard input and write it into the clipboard shared memory (default).
o-, --out print the content of the clipboard shared memory.
```

To save text into the clipboard just enter it through stdin.

```bash
# Using a pipe
$ echo "Hello world!" | mclip

# Entering characters with the keyboard (press Ctrl+D to finish your input)
$ mclip
I can
enter
text
on
several
lines
<Ctrl+D>
```

You can finally output the content of the clipboard.

```bash
$ mclip -o
Hello world!
```

### Neovim

[Neovim](https://neovim.io) provides a [Clipboard integration](https://neovim.io/doc/user/provider.html#provider-clipboard) which allows to provide any clipboard tool.

You can configure mclip as the Neovim clipboard tool using the following Lua configuration object.

```lua
vim.g.clipboard = {
name = "mclip",
copy = {
["+"] = "mclip",
["*"] = "mclip",
},
paste = {
["+"] = "mclip -o",
["*"] = "mclip -o",
},
cache_enabled = 1,
}
```

## Zellij

TODO
178 changes: 178 additions & 0 deletions main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#define _GNU_SOURCE

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>

static const char SHARED_MEMORY_NAME[] = "/mclip";

/**
* Read text from stdin and write into the shared memory.
*/
void in() {
void *addr = NULL;
int fd = 0;

fd = shm_open(SHARED_MEMORY_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);

if (fd == -1) {
fprintf(stderr, "shm_open() failed\n");
exit(1);
}

char characters[2] = {0};

int offset = 0;
int nb_read = 0;
int total_nb_read = 0;

while(fgets(characters, sizeof(characters), stdin) != NULL) {
nb_read = strlen(characters);
total_nb_read = total_nb_read + nb_read;

// Truncate the file to reserve space in it
if(ftruncate(fd, total_nb_read) == -1) {
fprintf(stderr, "ftruncate() failed\n");
exit(1);
}

if (addr != NULL)
addr = mremap(addr, offset, total_nb_read, MREMAP_MAYMOVE);
else
addr = mmap(NULL, total_nb_read, PROT_WRITE, MAP_SHARED, fd, 0);

sprintf(addr + offset, "%s", characters);

offset = total_nb_read;
}

munmap(addr, total_nb_read);
memset(characters, 0, sizeof(characters));

return;
}

/**
* Read text from the shared memory and write it to stdout.
*/
void out() {
struct stat st;
int fd = 0;
void *addr = NULL;

fd = shm_open(SHARED_MEMORY_NAME, O_RDONLY, S_IRUSR | S_IWUSR);

if (fd == -1) {
fprintf(stderr, "shm_open() failed\n");
exit(1);
}

fstat(fd, &st);

// The shared memory is empty when it has just been initialized the first time with 'shm_open()'.
if (st.st_size != 0) {
addr = mmap(NULL, 1024, PROT_READ, MAP_SHARED, fd, 0);

if (addr == MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
exit(1);
}
}

printf("%s", (char*) addr);
}

/**
* Display the help.
*/
void help() {
printf("Usage: mclip [OPTION]\n\n");
printf("Clipboard which stores its data inside shared memory.\n\n");
printf("\t h-, --help \t read text from standard input and write it into the clipboard shared memory (default).\n");
printf("\t i-, --in \t read text from standard input and write it into the clipboard shared memory (default).\n");
printf("\t o-, --out \t print the content of the clipboard shared memory.\n\n");
}

/**
* Main entry of the mclip program.
*
* @param argc Number of arguments.
* @param argv Array of arguments.
*/
int main(int argc, char *argv[])
{
int option_help = 0;
int option_in = 0;
int option_out = 0;

int c;
int option_index = 0;

static struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"in", no_argument, 0, 'i'},
{"out", no_argument, 0, 'o'},
{0, 0, 0, 0}
};

while(1) {
c = getopt_long(argc, argv, "hio", long_options, &option_index);

// No more options to read
if (c == -1)
break;

switch(c) {

// --help
case 'h':
option_help = 1;
break;

// --in
case 'i':
option_in = 1;
break;

// --out
case 'o':
option_out = 1;
break;

// Error encountered (i.e. bad option specified)
case '?':
exit(1);
}

}

if (option_help == 1 && (option_in == 1 || option_out == 1)) {
fprintf(stderr, "The '--help' option cannot be provided with the '--in' or '--out' option!\n");
return 1;
} else if (option_help == 0 && option_in == 1 && option_out == 1) {
fprintf(stderr, "The '--in' and '--out' options cannot be specified together!\n");
return 1;
} else if (optind < argc) {
fprintf(stderr, "Unknown argument speficied '%s'!\n", argv[optind]);
return 1;
} else if (option_help == 1) {
help();
return 0;
}

if (option_in == 1 || (option_in == 0 && option_out == 0)) {
in();
} else if (option_out == 1) {
out();
} else {
fprintf(stderr, "Unknown program state!\n");
return 1;
}

return 0;
}

0 comments on commit 7aa9c04

Please sign in to comment.