Saturday, May 16, 2020

Keepboard – Clipboard Manager

Keepboard is a free open source clipboard manager. It can save clipboard history (either automatically or on-demand) and provides the ability to quickly search and paste clipboard items copied in the past. It supports text, image and file clipboard items. Keepboard has been tested on Linux (GNOME, KDE...), Mac OS and Windows platforms.



Motivation


Before making Keepboard, I tried using several other clipboard managers, but none of them had everything what I needed.

Since on my daily work I often use both Mac and Linux (and Windows in the past), I had to use different clipboard managers for different platforms, which is a bit inconvenient (different set of features, different UI, hotkeys, etc). Thus I decided to build Keepboard with Java, so that it does not depend on a specific operating system.

Additionally, I was also not very happy with performance and responsiveness of many of other clipboard managers - it is somewhat annoying when a clipboard manager freezes and you wait for it to recover or have to restart it when you want to e.g. quickly paste a saved database script or similar to debug some issue which is halting the production at that moment. :) Thus I invested a lot of effort to make sure that stability and responsiveness are implemented correctly – so far users did not report any issues regarding the performance.



Community


Initially I used Keepboard only for myself, but I decided to publish it as a free open source solution to be available for others who might find it useful too. I am very glad that it gained traction very quickly and that lots of users are happy using it – since the launch in 2011, I added many improvements and enhancements based on feature requests and ideas from the users. Also the editors of Softpedia, cnet and several other software downloads sites have reviewed it and included it in the recommended software tools list.



Main Features


Of course, the main feature of every clipboard manager is to keep history of items that were copied into the clipboard:



Keepboard window can be opened via the Ctrl + ` (back tick) hotkey, which is configurable. Picking the selected item (Enter or double-click) closes Keepboard window and the item is automatically pasted into the currently focused editor of any other application.

Frequently used items can be given custom names and separated into groups so that they can be found and filtered quickly:



Items can be searched by containing text:



Preview area location can be customized (below is my favorite view, i.e. on the right to the history list):



Hotkeys for all variety of actions can be customized:



Installation


Installation from Snap Store on Linux

The easiest way to install Keepboard on Linux is to grab it from Snap Store (or search for it in the software center and alike in your distro that includes Snap Store as a source), or simply install it via command line:

sudo snap install keepboard

Snap takes care of the rest and Keepboard does not require any additional permissions or configuration (it saves all data only locally and does not need network access nor disk access apart from its snap-confined directory), so just launch it after installation. After that it can be activated when needed by clicking on its icon in the system tray or more handily by pressing the Ctrl + ` (back tick) global hotkey.

Standalone Installation (all platforms)

There is no need to run any custom installation of Keepboard, just download and extract the archive and run it. Keepboard can be displayed by clicking on its icon in the system tray or more conveniently by pressing the Ctrl + ` (back tick) hotkey.

Java Runtime Environment, version 1.8 or newer, is needed to run Keepboard. Also, depending on your OS settings, you may have to grant Keepboard the permissions to run and to write the data to its folder, as well as the necessary permissions to auto-paste content to other applications if you would like to utilize this feature too (enabled by default).

Linux: Run the start.sh script to launch Keepboard (you may have to grant the 'execute' permission to this file).

Windows: Run the Start.VBS script to launch Keepboard.

Mac: Run the Keepboard.app to launch Keepboard. Depending on how you downlaoded and unpacked Keepboard archive, newer versions of Mac OS might translocate the application path internally. If that happens (an error stating that start.sh script cannot be found), to make the application run from the folder where you moved it (so it finds the jar, scripts and writes data to the Keepboard folder only), navigate via command line to the Keepboard directory and execute: 
xattr -dr com.apple.quarantine Keepboard.app

Keepboard saves all data in its working folder (history and prefs folders), which can also be copied together with Keepboard to another machine for example, to keep all the history.


Notes


  • Keepboard does not support automatic clipboard history synchronization through network between different machines (some other clipboard managers do, so you may want to look at the alternatives if this is a desirable feature for you) and does not perform any network communication at all – everything is saved only on the host machine where Keepboard is running, which is good from the security and privacy perspective.
  • Tray icon might not be displayed correctly on some desktop environments/themes and can be simply removed by unticking "Show icon in system tray" in the View menu.



Contribution


Please feel free to write feature requests, bug reports and any ideas about Keepboard improvements. If you are a software developer (Java), please feel free to submit pull requests for review into the Keepboard repository, the entire Keepboard code is open source. Thank you!

Sunday, April 17, 2016

Codility NailingPlanks - Solution with linear time complexity

This is a solution with O(N+M) time complexity to Codility NailingPlanks task. The code is written in Java.

This solution first discards all planks that completely wrap other planks, because the nail used for a wrapped plank can be used for all planks that wrap it. For example, consider the three planks below:


Nail 15 is the minimum nail for plank 2. Planks 1 and 3 can use nails 5 and 10 respectively, but it does not matter because nail 15 must be used for plank 2, and thus it determines the minimum nail for all three planks, so planks 1 and 3 can be eliminated from the calculation.

Once all wrapper planks are eliminated, the remaining planks (maximum 2*M of them) are positioned as below:


That means that plank which starts first will also end first, which makes queue a perfect data structure to store the information when traversing the remaining planks.

Elimination of wrapper planks can be done in two steps:

1) Sorting the planks by start position and eliminating planks that start at the same position by taking the shortest plank for each start position. This is basically a variation of counting sort, with linear time complexity:

int[] planks = new int[2 * C.length + 1];
for (int i = 0; i < A.length; i++) {
    if (planks[A[i]] == 0 || B[i] < planks[A[i]]) {
        planks[A[i]] = B[i];
    }
}


After this loop planks[i] contains end position for a plank that starts at the position i.

2) Filtering out wrapper planks:

Queue stack = new Queue(2 * C.length);
for (int i = 1; i < planks.length; i++) {
    if (planks[i] != 0) {
        while (!stack.isEmpty() && planks[i] <= planks[stack.last()]) {
            stack.removeLast();
        }
        stack.addLast(i);
    }
}


We push start positions to stack (we just reuse end side of the queue as stack here). For each plank we remove all the planks from the top of the stack that wrap the current plank. If the plank at the top of the stack does not wrap the current plank, it is guaranteed that no other planks in the stack wrap the current plank because if there were such a plank, it would also wrap the plank at the top of the stack and thus would have been removed when the plank at the top was added to the stack. The total time complexity of the inner loop is linear because each plank is once added and once visited on the stack.

Now the stack contains only planks that are not wrapped by any other plank, and we store the remaining plank ends in a new array:

int[] ends = new int[planks.length];
while (!stack.isEmpty()) {
    int start = stack.removeLast();
    ends[start] = planks[start];
}


We also create two other helper arrays for plank starts and nail positions:

int[] starts = new int[ends.length];
for (int i = 1; i < ends.length; i++) {
    if (ends[i] > 0) {
        starts[ends[i]] = i;
    }
}

int[] nails = new int[starts.length];
for (int i = 0; i < C.length; i++) {
    if (nails[C[i]] == 0) {
        nails[C[i]] = i + 1;
    }
}


Now we traverse the ordered planks:

int result = -1;
Queue queue = new Queue(ends.length);
for (int i = 1; i < ends.length; i++) {
    if (ends[i] > 0) {
        if (!queue.isEmpty() && ends[queue.last()] == -1) {
             queue.removeLast();
        }
        ends[i] = -1;
        queue.addLast(i);
    } else if (queue.isEmpty()) {
        continue;
    }
    if (nails[i] != 0) {
        if (ends[queue.last()] == -1 || nails[i] < ends[queue.last()]) {
            ends[queue.last()] = nails[i];
            while (queue.size() > 1 && ends[queue.nextToLast()] > nails[i]) {
                queue.removeNextToLast();
            }
        }
    }
    if (starts[i] != 0) {
        int min = ends[queue.first()];
        if (min == -1) {
            return -1;
        }
        if (starts[i] == queue.first()) {
            queue.removeFirst();
        }
        if (result == -1 || min > result) {
            result = min;
        }
    }
}


We store plank starts on the queue and reuse ends array to store minimum nails for corresponding planks in the queue. For each nail we encounter during the iteration, we remove all planks from the queue for which minimum nail is larger than the current minimum nail, because if a plank is in the queue, then it means that it has started but not ended yet, and this nail can be used for that plank as well. The total time complexity of the inner while loop is linear because each element in the queue is added once and removed once.

The implementation of the Queue is straightforward:

private static class Queue {
    private int[] array;
    private int start = 0;
    private int end = -1;
    private int size = 0;

    Queue(int capacity) {
        this.array = new int[capacity];
    }
    void addLast(int element) {
        size++;
        end++;
        if (end == array.length) {
            end = 0;
        }
        array[end] = element;
    }
    int removeLast() {
        size--;
        int result = array[end--];
        if (end < 0) {
            end = array.length - 1;
        }
        return result;
    }
    int removeFirst() {
        size--;
        int result = array[start++];
        if (start == array.length) {
            start = 0;
        }
        return result;
    }
    int removeNextToLast() {
        size--;
        int index = nextToLastIndex();
        int result = array[index];
        array[index] = array[end];
        end = index;
        return result;
    }
    int last() {
        return array[end];
    }
    int first() {
        return array[start];
    }
    int nextToLast() {
        return array[nextToLastIndex()];
    }
    boolean isEmpty() {
        return size == 0;
    }
    int size() {
        return size;
    }
    int nextToLastIndex() {
        return end > 0 ? end - 1 : array.length - 1;
    }
}


So, the time complexity of the loops is either O(N) or O(M), and helper arrays and queues don't take more than 2*M space, so the overall time complexity is O(N+M) and space O(M).

The complete solution is available on the Codility test results page.