A => COPYING +24 -0
@@ 1,24 @@
+Copyright (C) 2020 Martijn Braam
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Except as contained in this notice, the name(s) of the above copyright
+holders shall not be used in advertising or otherwise to promote the sale,
+use or other dealings in this Software without prior written
+authorization.
A => build-aux/meson/postinstall.py +21 -0
@@ 1,21 @@
+#!/usr/bin/env python3
+
+from os import environ, path
+from subprocess import call
+
+prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local')
+datadir = path.join(prefix, 'share')
+destdir = environ.get('DESTDIR', '')
+
+# Package managers set this so we don't need to run
+if not destdir:
+ print('Updating icon cache...')
+ call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')])
+
+ print('Updating desktop database...')
+ call(['update-desktop-database', '-q', path.join(datadir, 'applications')])
+
+ print('Compiling GSettings schemas...')
+ call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')])
+
+
A => data/meson.build +45 -0
@@ 1,45 @@
+desktop_file = i18n.merge_file(
+ input: 'nl.brixit.Thumbdrives.desktop.in',
+ output: 'nl.brixit.Thumbdrives.desktop',
+ type: 'desktop',
+ po_dir: '../po',
+ install: true,
+ install_dir: join_paths(get_option('datadir'), 'applications')
+)
+
+desktop_utils = find_program('desktop-file-validate', required: false)
+if desktop_utils.found()
+ test('Validate desktop file', desktop_utils,
+ args: [desktop_file]
+ )
+endif
+
+appstream_file = i18n.merge_file(
+ input: 'nl.brixit.Thumbdrives.appdata.xml.in',
+ output: 'nl.brixit.Thumbdrives.appdata.xml',
+ po_dir: '../po',
+ install: true,
+ install_dir: join_paths(get_option('datadir'), 'appdata')
+)
+
+appstream_util = find_program('appstream-util', required: false)
+if appstream_util.found()
+ test('Validate appstream file', appstream_util,
+ args: ['validate', appstream_file]
+ )
+endif
+
+install_data('nl.brixit.Thumbdrives.gschema.xml',
+ install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas')
+)
+
+compile_schemas = find_program('glib-compile-schemas', required: false)
+if compile_schemas.found()
+ test('Validate schema file', compile_schemas,
+ args: ['--strict', '--dry-run', meson.current_source_dir()]
+ )
+endif
+
+install_data('nl.brixit.Thumbdrives.policy',
+ install_dir: join_paths(get_option('datadir'), 'polkit-1/actions')
+)
A => data/nl.brixit.Thumbdrives.appdata.xml.in +8 -0
@@ 1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop">
+ <id>nl.brixit.Thumbdrives.desktop</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>MIT</project_license>
+ <description>
+ </description>
+</component>
A => data/nl.brixit.Thumbdrives.desktop.in +7 -0
@@ 1,7 @@
+[Desktop Entry]
+Name=thumbdrives
+Exec=thumbdrives
+Terminal=false
+Type=Application
+Categories=GTK;
+StartupNotify=true
A => data/nl.brixit.Thumbdrives.gschema.xml +5 -0
@@ 1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<schemalist gettext-domain="thumbdrives">
+ <schema id="nl.brixit.Thumbdrives" path="/nl/brixit/Thumbdrives/">
+ </schema>
+</schemalist>
A => data/nl.brixit.Thumbdrives.policy +33 -0
@@ 1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE policyconfig PUBLIC
+ "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+ "http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
+<policyconfig>
+
+ <vendor>Thumbdrives</vendor>
+
+ <action id="nl.brixit.Thumbdrives.pkexec.mount">
+ <message>Authentication is required to mount an image</message>
+ <icon_name>nl.brixit.Thumbdrives</icon_name>
+ <defaults>
+ <allow_any>auth_admin</allow_any>
+ <allow_inactive>auth_admin</allow_inactive>
+ <allow_active>auth_admin</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/pk-thumbdrive-action</annotate>
+ <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
+ </action>
+
+ <action id="nl.brixit.Thumbdrives.pkexec.umount">
+ <message>Authentication is required to unmount an image</message>
+ <icon_name>nl.brixit.Thumbdrives</icon_name>
+ <defaults>
+ <allow_any>auth_admin</allow_any>
+ <allow_inactive>auth_admin</allow_inactive>
+ <allow_active>auth_admin</allow_active>
+ </defaults>
+ <annotate key="org.freedesktop.policykit.exec.path">/usr/bin/pk-thumbdrive-action</annotate>
+ <annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
+ </action>
+
+</policyconfig>
A => meson.build +15 -0
@@ 1,15 @@
+project('thumbdrives',
+ version: '0.1.0',
+ meson_version: '>= 0.50.0',
+ default_options: [ 'warning_level=2',
+ ],
+)
+
+i18n = import('i18n')
+
+
+subdir('data')
+subdir('src')
+subdir('po')
+
+meson.add_install_script('build-aux/meson/postinstall.py')<
\ No newline at end of file
A => nl.brixit.Thumbdrives.json +37 -0
@@ 1,37 @@
+{
+ "app-id" : "nl.brixit.Thumbdrives",
+ "runtime" : "org.gnome.Platform",
+ "runtime-version" : "3.34",
+ "sdk" : "org.gnome.Sdk",
+ "command" : "thumbdrives",
+ "finish-args" : [
+ "--share=network",
+ "--share=ipc",
+ "--socket=fallback-x11",
+ "--socket=wayland"
+ ],
+ "cleanup" : [
+ "/include",
+ "/lib/pkgconfig",
+ "/man",
+ "/share/doc",
+ "/share/gtk-doc",
+ "/share/man",
+ "/share/pkgconfig",
+ "*.la",
+ "*.a"
+ ],
+ "modules" : [
+ {
+ "name" : "thumbdrives",
+ "builddir" : true,
+ "buildsystem" : "meson",
+ "sources" : [
+ {
+ "type" : "git",
+ "url" : "file:///home/martijn/Projects/Thumbdrives"
+ }
+ ]
+ }
+ ]
+}
A => po/LINGUAS +0 -0
A => po/POTFILES +7 -0
@@ 1,7 @@
+data/nl.brixit.Thumbdrives.desktop.in
+data/nl.brixit.Thumbdrives.appdata.xml.in
+data/nl.brixit.Thumbdrives.gschema.xml
+src/window.ui
+src/main.py
+src/window.py
+
A => po/meson.build +1 -0
@@ 1,1 @@
+i18n.gettext('thumbdrives', preset: 'glib')
A => src/__init__.py +0 -0
A => src/main.py +53 -0
@@ 1,53 @@
+# main.py
+#
+# Copyright 2020 Martijn Braam
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name(s) of the above copyright
+# holders shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization.
+
+import sys
+import gi
+
+gi.require_version('Gtk', '3.0')
+
+from gi.repository import Gtk, Gio
+
+from .window import ThumbdrivesWindow
+
+
+class Application(Gtk.Application):
+ def __init__(self):
+ super().__init__(application_id='nl.brixit.Thumbdrives',
+ flags=Gio.ApplicationFlags.FLAGS_NONE)
+
+ def do_activate(self):
+ win = self.props.active_window
+ if not win:
+ win = ThumbdrivesWindow(application=self)
+ win.present()
+
+
+def main(version):
+ app = Application()
+ return app.run(sys.argv)
A => src/meson.build +36 -0
@@ 1,36 @@
+pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
+moduledir = join_paths(pkgdatadir, 'thumbdrives')
+gnome = import('gnome')
+
+gnome.compile_resources('thumbdrives',
+ 'thumbdrives.gresource.xml',
+ gresource_bundle: true,
+ install: true,
+ install_dir: pkgdatadir,
+)
+
+python = import('python')
+
+conf = configuration_data()
+conf.set('PYTHON', python.find_installation('python3').path())
+conf.set('VERSION', meson.project_version())
+conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir')))
+conf.set('pkgdatadir', pkgdatadir)
+
+configure_file(
+ input: 'thumbdrives.in',
+ output: 'thumbdrives',
+ configuration: conf,
+ install: true,
+ install_dir: get_option('bindir')
+)
+
+thumbdrives_sources = [
+ '__init__.py',
+ 'main.py',
+ 'vdisk.py',
+ 'window.py',
+]
+
+install_data(thumbdrives_sources, install_dir: moduledir)
+install_data('pk-thumbdrive-action.sh', install_dir: get_option('bindir'), install_mode: 'rwxr-xr-x', rename: ['pk-thumbdrive-action'])
A => src/pk-thumbdrive-action.sh +113 -0
@@ 1,113 @@
+#!/bin/sh
+ACTION=$1
+BACKING_FILE=$2
+CONFIGFS=/sys/kernel/config/usb_gadget
+GADGET=$CONFIGFS/thumbdrives
+
+remove_gadget () {
+ local gadget=$1
+
+ # Disable the gadget
+ echo "" > "$gadget"/UDC
+
+ # Remove the functions from the config
+ for function in "$gadget"/configs/*/*.*
+ do
+ rm "$function"
+ done
+
+ # Remove the language data from the config
+ for lang in "$gadget"/configs/*/strings/*
+ do
+ rmdir "$lang"
+ done
+
+ # Remove the configurations
+ for config in "$gadget"/configs/*/
+ do
+ rmdir "$config"
+ done
+
+ # Remove the defined functions
+ for function in "$gadget"/functions/*/
+ do
+ rmdir "$function"
+ done
+
+ # Remove the defined language data
+ for lang in "$gadget"/strings/*
+ do
+ rmdir "$lang"
+ done
+
+ # Remove the gadget
+ rmdir "$gadget"
+}
+
+disable_existing_gadgets () {
+ for gadget in "$CONFIGFS"/*/UDC
+ do
+ echo "" > "$gadget"
+ done
+}
+
+create_gadget () {
+ local backing="$1"
+ mkdir $GADGET
+ echo "0x1209" > $GADGET/idVendor # Generic
+ echo "0x4202" > $GADGET/idProduct # Random id
+
+ # English locale
+ LOCALE=$GADGET/strings/0x409
+ mkdir $LOCALE || echo "Could not create $LOCALE"
+ echo "Phone" > $LOCALE/manufacturer
+ echo "BLEH" > $LOCALE/product
+ echo "Thumbdrives" > $LOCALE/serialnumber
+
+ # Mass storage function
+ FUNCTION=$GADGET/functions/mass_storage.0
+ LUN=$FUNCTION/lun.1
+ mkdir $FUNCTION || echo "Could not create $FUNCTION"
+ mkdir $LUN || echo "Could not create $LUN"
+
+ # Configuration instance
+ CONFIG=$GADGET/configs/c.1
+ LOCALE=$CONFIG/strings/0x409
+ mkdir $CONFIG || echo "Coud not create $CONFIG"
+ mkdir $LOCALE || echo "Coud not create $LOCALE"
+ echo "Thumbdrive" > $LOCALE/configuration
+
+ # Link mass storage gadget to backing file
+ echo $backing > $LUN/file
+
+ # Mass storage hardware name
+ echo "Thumbdrives" > $LUN/inquiry_string
+
+ # Add mass storage to the configuration
+ ln -s $FUNCTION $CONFIG
+
+ # Link to controller
+ echo "$(ls /sys/class/udc)" > $GADGET/UDC || ( echo "Couldn't write to UDC" )
+}
+
+
+if [ "$ACTION" = "mount-mass-storage" ]
+then
+
+ [ -d $GADGET ] && remove_gadget $GADGET
+ disable_existing_gadgets
+ create_gadget "$BACKING_FILE"
+fi
+
+if [ "$ACTION" = "mount-iso" ]
+then
+
+ [ -d $GADGET ] && remove_gadget $GADGET
+ disable_existing_gadgets
+ create_gadget "$BACKING_FILE"
+fi
+
+if [ "$ACTION" = "umount" ]
+then
+ [ -d $GADGET ] && remove_gadget $GADGET
+fi
A => src/thumbdrives.gresource.xml +6 -0
@@ 1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/nl/brixit/Thumbdrives">
+ <file>window.ui</file>
+ </gresource>
+</gresources>
A => src/thumbdrives.in +52 -0
@@ 1,52 @@
+#!@PYTHON@
+
+# thumbdrives.in
+#
+# Copyright 2020 Martijn Braam
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name(s) of the above copyright
+# holders shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization.
+
+import os
+import sys
+import signal
+import gettext
+
+VERSION = '@VERSION@'
+pkgdatadir = '@pkgdatadir@'
+localedir = '@localedir@'
+
+sys.path.insert(1, pkgdatadir)
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+gettext.install('thumbdrives', localedir)
+
+if __name__ == '__main__':
+ import gi
+
+ from gi.repository import Gio
+ resource = Gio.Resource.load(os.path.join(pkgdatadir, 'thumbdrives.gresource'))
+ resource._register()
+
+ from thumbdrives import main
+ sys.exit(main.main(VERSION))
A => src/vdisk.py +11 -0
@@ 1,11 @@
+import subprocess
+
+def mount(backing_file, cdimage=False):
+ action = 'mount-mass-storage'
+ if cdimage:
+ action = 'mount-iso'
+
+ subprocess.run(['pkexec', 'pk-thumbdrive-action', action, backing_file])
+
+def unmount():
+ subprocess.run(['pkexec', 'pk-thumbdrive-action', 'umount'])
A => src/window.py +49 -0
@@ 1,49 @@
+# window.py
+#
+# Copyright 2020 Martijn Braam
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# Except as contained in this notice, the name(s) of the above copyright
+# holders shall not be used in advertising or otherwise to promote the sale,
+# use or other dealings in this Software without prior written
+# authorization.
+
+from gi.repository import Gtk
+import thumbdrives.vdisk as vdisk
+
+
+@Gtk.Template(resource_path='/nl/brixit/Thumbdrives/window.ui')
+class ThumbdrivesWindow(Gtk.ApplicationWindow):
+ __gtype_name__ = 'ThumbdrivesWindow'
+
+ mount = Gtk.Template.Child()
+ unmount = Gtk.Template.Child()
+
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+
+ def on_mount_clicked(self, widget, args):
+ print("MOUNTING")
+ subprocess.run(['truncate', '-s', '16G', '/tmp/test.img'])
+ vdisk.mount("/tmp/test.img")
+
+ def on_unmount_clicked(self, widget, args):
+ vdisk.unmount()
A => src/window.ui +56 -0
@@ 1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="ThumbdrivesWindow" parent="GtkApplicationWindow">
+ <property name="can_focus">False</property>
+ <property name="default_width">600</property>
+ <property name="default_height">300</property>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="header_bar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="title">Hello, World!</property>
+ <property name="show_close_button">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkButton" id="mount">
+ <property name="label" translatable="yes">Mount</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_mount_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="unmount">
+ <property name="label" translatable="yes">Unmount</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_unmount_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </template>
+</interface>